From 7c16772beb57a0e5a0a7a3a9f456520d6d400a64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:36:51 +0000 Subject: [PATCH 1/3] Initial plan From edec6661af41b7c7478547b1af9175476f5cdd55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:41:32 +0000 Subject: [PATCH 2/3] Initial plan for Java GSDK unit tests Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com> --- java/gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 java/gradlew diff --git a/java/gradlew b/java/gradlew old mode 100644 new mode 100755 From f45d84848071973c5c66a2ae70ad97ab089d7777 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:45:00 +0000 Subject: [PATCH 3/3] Add Java GSDK unit tests and CI workflow Add 84 unit tests across 8 test classes covering: - ConnectedPlayer data class - SessionHostHeartbeatInfo getters/setters and JSON deserialization - SessionConfig including ToMapAllStrings - Operation enum Gson serialization with @SerializedName casings - MaintenanceSchedule/MaintenanceEvent deserialization - JsonFileConfiguration config file parsing and validation - GameHostHealth/SessionHostStatus enum values - GameserverSDK public API constant keys Add .github/workflows/java-tests.yml CI workflow to run tests on PRs. Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com> --- .github/workflows/java-tests.yml | 28 ++ .gitignore | 3 + .../azure/gaming/ConnectedPlayerTest.java | 32 ++ .../com/microsoft/azure/gaming/EnumTest.java | 55 +++ .../gaming/GameserverSDKConstantsTest.java | 62 ++++ .../gaming/JsonFileConfigurationTest.java | 319 ++++++++++++++++++ .../azure/gaming/MaintenanceScheduleTest.java | 160 +++++++++ .../microsoft/azure/gaming/OperationTest.java | 154 +++++++++ .../azure/gaming/SessionConfigTest.java | 94 ++++++ .../gaming/SessionHostHeartbeatInfoTest.java | 180 ++++++++++ 10 files changed, 1087 insertions(+) create mode 100644 .github/workflows/java-tests.yml create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/ConnectedPlayerTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/EnumTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/GameserverSDKConstantsTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/JsonFileConfigurationTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/MaintenanceScheduleTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/OperationTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionConfigTest.java create mode 100644 java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionHostHeartbeatInfoTest.java diff --git a/.github/workflows/java-tests.yml b/.github/workflows/java-tests.yml new file mode 100644 index 00000000..7ef64085 --- /dev/null +++ b/.github/workflows/java-tests.yml @@ -0,0 +1,28 @@ +name: Java GSDK Tests + +on: + pull_request: + paths: + - 'java/**' + - '.github/workflows/java-tests.yml' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '8' + + - name: Build + run: cd java && ./gradlew build + + - name: Test + run: cd java && ./gradlew :gameserverSDK:test diff --git a/.gitignore b/.gitignore index 669f9b9a..ebd20741 100644 --- a/.gitignore +++ b/.gitignore @@ -320,6 +320,9 @@ gradle-app.setting ## End Android +# GSDK log output files generated during tests +GSDK_output_*.txt + # Maven /java/gameserverSDK/target/* pom.xml.releaseBackup diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/ConnectedPlayerTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/ConnectedPlayerTest.java new file mode 100644 index 00000000..3257fca6 --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/ConnectedPlayerTest.java @@ -0,0 +1,32 @@ +package com.microsoft.azure.gaming; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class ConnectedPlayerTest { + + @Test + public void constructor_setsPlayerId() { + ConnectedPlayer player = new ConnectedPlayer("player1"); + assertEquals("player1", player.getPlayerId()); + } + + @Test + public void setPlayerId_updatesPlayerId() { + ConnectedPlayer player = new ConnectedPlayer("player1"); + player.setPlayerId("player2"); + assertEquals("player2", player.getPlayerId()); + } + + @Test + public void constructor_handlesEmptyString() { + ConnectedPlayer player = new ConnectedPlayer(""); + assertEquals("", player.getPlayerId()); + } + + @Test + public void constructor_handlesNullPlayerId() { + ConnectedPlayer player = new ConnectedPlayer(null); + assertNull(player.getPlayerId()); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/EnumTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/EnumTest.java new file mode 100644 index 00000000..e8c511b8 --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/EnumTest.java @@ -0,0 +1,55 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.Gson; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for GameHostHealth and SessionHostStatus enums. + */ +public class EnumTest { + + @Test + public void gameHostHealth_hasExpectedValues() { + GameHostHealth[] values = GameHostHealth.values(); + assertEquals(2, values.length); + assertEquals(GameHostHealth.Healthy, GameHostHealth.valueOf("Healthy")); + assertEquals(GameHostHealth.Unhealthy, GameHostHealth.valueOf("Unhealthy")); + } + + @Test + public void sessionHostStatus_hasExpectedValues() { + SessionHostStatus[] values = SessionHostStatus.values(); + assertEquals(7, values.length); + assertNotNull(SessionHostStatus.valueOf("Invalid")); + assertNotNull(SessionHostStatus.valueOf("Initializing")); + assertNotNull(SessionHostStatus.valueOf("StandingBy")); + assertNotNull(SessionHostStatus.valueOf("Active")); + assertNotNull(SessionHostStatus.valueOf("Terminating")); + assertNotNull(SessionHostStatus.valueOf("Terminated")); + assertNotNull(SessionHostStatus.valueOf("Quarantined")); + } + + @Test + public void gameHostHealth_gsonSerialization() { + Gson gson = new Gson(); + assertEquals("\"Healthy\"", gson.toJson(GameHostHealth.Healthy)); + assertEquals("\"Unhealthy\"", gson.toJson(GameHostHealth.Unhealthy)); + } + + @Test + public void gameHostHealth_gsonDeserialization() { + Gson gson = new Gson(); + assertEquals(GameHostHealth.Healthy, gson.fromJson("\"Healthy\"", GameHostHealth.class)); + assertEquals(GameHostHealth.Unhealthy, gson.fromJson("\"Unhealthy\"", GameHostHealth.class)); + } + + @Test + public void sessionHostStatus_gsonSerialization() { + Gson gson = new Gson(); + assertEquals("\"Initializing\"", gson.toJson(SessionHostStatus.Initializing)); + assertEquals("\"StandingBy\"", gson.toJson(SessionHostStatus.StandingBy)); + assertEquals("\"Active\"", gson.toJson(SessionHostStatus.Active)); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/GameserverSDKConstantsTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/GameserverSDKConstantsTest.java new file mode 100644 index 00000000..dac0b7db --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/GameserverSDKConstantsTest.java @@ -0,0 +1,62 @@ +package com.microsoft.azure.gaming; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for GameserverSDK constant key values. + * These constants are part of the public API and must remain stable. + */ +public class GameserverSDKConstantsTest { + + @Test + public void heartbeatEndpointKey_hasExpectedValue() { + assertEquals("heartbeatEndpoint", GameserverSDK.HEARTBEAT_ENDPOINT_KEY); + } + + @Test + public void serverIdKey_hasExpectedValue() { + assertEquals("serverId", GameserverSDK.SERVER_ID_KEY); + } + + @Test + public void logFolderKey_hasExpectedValue() { + assertEquals("logFolder", GameserverSDK.LOG_FOLDER_KEY); + } + + @Test + public void certificateFolderKey_hasExpectedValue() { + assertEquals("certificateFolder", GameserverSDK.CERTIFICATE_FOLDER_KEY); + } + + @Test + public void titleIdKey_hasExpectedValue() { + assertEquals("titleId", GameserverSDK.TITLE_ID_KEY); + } + + @Test + public void buildIdKey_hasExpectedValue() { + assertEquals("buildId", GameserverSDK.BUILD_ID_KEY); + } + + @Test + public void regionKey_hasExpectedValue() { + assertEquals("region", GameserverSDK.REGION_KEY); + } + + @Test + public void sessionCookieKey_hasExpectedValue() { + assertEquals("sessionCookie", GameserverSDK.SESSION_COOKIE_KEY); + } + + @Test + public void sessionIdKey_hasExpectedValue() { + assertEquals("sessionId", GameserverSDK.SESSION_ID_KEY); + } + + @Test + public void vmIdKey_hasExpectedValue() { + assertEquals("vmId", GameserverSDK.VM_ID_KEY); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/JsonFileConfigurationTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/JsonFileConfigurationTest.java new file mode 100644 index 00000000..7b398a2d --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/JsonFileConfigurationTest.java @@ -0,0 +1,319 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileWriter; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Tests for JsonFileConfiguration config file parsing and validation. + * Uses a temp config file and environment variable manipulation to test. + */ +public class JsonFileConfigurationTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private void setEnvironmentVariable(String key, String value) throws Exception { + Map env = System.getenv(); + Field field = env.getClass().getDeclaredField("m"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + Map writableEnv = (Map) field.get(env); + if (value == null) { + writableEnv.remove(key); + } else { + writableEnv.put(key, value); + } + } + + private File createConfigFile(String jsonContent) throws Exception { + File configFile = tempFolder.newFile("gsdkConfig.json"); + try (FileWriter writer = new FileWriter(configFile)) { + writer.write(jsonContent); + } + return configFile; + } + + @Before + public void setUp() throws Exception { + // Clean up any previous env vars + setEnvironmentVariable("PF_TITLE_ID", "testTitleId"); + setEnvironmentVariable("PF_BUILD_ID", "testBuildId"); + setEnvironmentVariable("PF_REGION", "WestUS"); + } + + @Test + public void validConfig_parsesAllFields() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"," + + "\"logFolder\":\"/logs\"," + + "\"sharedContentFolder\":\"/shared\"," + + "\"certificateFolder\":\"/certs\"," + + "\"vmId\":\"vm-001\"," + + "\"buildMetadata\":{\"key1\":\"value1\",\"key2\":\"value2\"}," + + "\"gamePorts\":{\"gamePort\":\"8080\",\"queryPort\":\"27015\"}" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("localhost:56001", config.getHeartbeatEndpoint()); + assertEquals("server123", config.getServerId()); + assertEquals("/logs", config.getLogFolder()); + assertEquals("/shared", config.getSharedContentFolder()); + assertEquals("/certs", config.getCertificateFolder()); + assertEquals("vm-001", config.getVmId()); + assertNotNull(config.getBuildMetadata()); + assertEquals("value1", config.getBuildMetadata().get("key1")); + assertEquals("value2", config.getBuildMetadata().get("key2")); + assertNotNull(config.getGamePorts()); + assertEquals("8080", config.getGamePorts().get("gamePort")); + assertEquals("27015", config.getGamePorts().get("queryPort")); + } + + @Test + public void validConfig_alternateKeyCasing_HeartbeatEndpoint() throws Exception { + String json = "{" + + "\"HeartbeatEndpoint\":\"localhost:56001\"," + + "\"SessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("localhost:56001", config.getHeartbeatEndpoint()); + assertEquals("server123", config.getServerId()); + } + + @Test + public void validConfig_allUpperCaseKeys() throws Exception { + String json = "{" + + "\"HEARTBEATENDPOINT\":\"localhost:56001\"," + + "\"SESSIONHOSTID\":\"server123\"," + + "\"LOGFOLDER\":\"/logs\"," + + "\"CERTIFICATEFOLDER\":\"/certs\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("localhost:56001", config.getHeartbeatEndpoint()); + assertEquals("server123", config.getServerId()); + assertEquals("/logs", config.getLogFolder()); + assertEquals("/certs", config.getCertificateFolder()); + } + + @Test + public void validConfig_readsTitleIdFromEnv() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + setEnvironmentVariable("PF_TITLE_ID", "myTitleId"); + setEnvironmentVariable("PF_BUILD_ID", "myBuildId"); + setEnvironmentVariable("PF_REGION", "EastUS"); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("myTitleId", config.getTitleId()); + assertEquals("myBuildId", config.getBuildId()); + assertEquals("EastUS", config.getRegion()); + } + + @Test + public void validate_validConfig_doesNotThrow() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); // Should not throw + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_missingBothEndpointAndServerId_throws() throws Exception { + String json = "{}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_missingHeartbeatEndpoint_throws() throws Exception { + String json = "{\"sessionHostId\":\"server123\"}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_missingServerId_throws() throws Exception { + String json = "{\"heartbeatEndpoint\":\"localhost:56001\"}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_emptyHeartbeatEndpoint_throws() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"\"," + + "\"sessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_emptyServerId_throws() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test(expected = GameserverSDKInitializationException.class) + public void validate_emptyBothFields_throws() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"\"," + + "\"sessionHostId\":\"\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + config.validate(); + } + + @Test + public void validConfig_nullBuildMetadata_returnsNull() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + assertNull(config.getBuildMetadata()); + } + + @Test + public void validConfig_nullGamePorts_returnsNull() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + assertNull(config.getGamePorts()); + } + + @Test(expected = com.google.gson.JsonSyntaxException.class) + public void invalidJsonFile_throwsJsonSyntaxException() throws Exception { + String invalidJson = "not valid json {{{"; + + File configFile = createConfigFile(invalidJson); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + new JsonFileConfiguration(); + } + + @Test + public void validConfig_alternateKeyCasing_BuildMetadata() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"," + + "\"BuildMetadata\":{\"property1\":\"value1\"}" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertNotNull(config.getBuildMetadata()); + assertEquals("value1", config.getBuildMetadata().get("property1")); + } + + @Test + public void validConfig_alternateKeyCasing_VmId() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"," + + "\"VmId\":\"vm-test-001\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("vm-test-001", config.getVmId()); + } + + @Test + public void validConfig_alternateKeyCasing_SharedContentFolder() throws Exception { + String json = "{" + + "\"heartbeatEndpoint\":\"localhost:56001\"," + + "\"sessionHostId\":\"server123\"," + + "\"SharedContentFolder\":\"/shared/content\"" + + "}"; + + File configFile = createConfigFile(json); + setEnvironmentVariable(JsonFileConfiguration.CONFIG_FILE_VARIABLE_NAME, configFile.getAbsolutePath()); + + JsonFileConfiguration config = new JsonFileConfiguration(); + + assertEquals("/shared/content", config.getSharedContentFolder()); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/MaintenanceScheduleTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/MaintenanceScheduleTest.java new file mode 100644 index 00000000..515edb79 --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/MaintenanceScheduleTest.java @@ -0,0 +1,160 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.*; +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Tests for MaintenanceSchedule and MaintenanceEvent deserialization. + */ +public class MaintenanceScheduleTest { + + private Gson createGsonWithDateSupport() { + return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, + (JsonDeserializer) (json, type, ctx) -> + ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString()) + .withZoneSameLocal(ZoneId.of("UTC"))).create(); + } + + @Test + public void deserialization_maintenanceSchedule_parsesDocumentIncarnation() { + String json = "{" + + "\"documentIncarnation\":\"IncarnationID\"," + + "\"Events\":[]" + + "}"; + + MaintenanceSchedule schedule = new Gson().fromJson(json, MaintenanceSchedule.class); + assertEquals("IncarnationID", schedule.getDocumentIncarnation()); + } + + @Test + public void deserialization_maintenanceSchedule_parsesEvents() { + String json = "{" + + "\"documentIncarnation\":\"IncarnationID\"," + + "\"Events\":[{" + + " \"eventId\":\"eventID\"," + + " \"eventType\":\"Reboot\"," + + " \"resourceType\":\"VirtualMachine\"," + + " \"Resources\":[\"resourceName\"]," + + " \"eventStatus\":\"Scheduled\"," + + " \"notBefore\":\"2024-01-15T10:30:00Z\"," + + " \"description\":\"eventDescription\"," + + " \"eventSource\":\"Platform\"," + + " \"durationInSeconds\":3600" + + "}]" + + "}"; + + Gson gson = createGsonWithDateSupport(); + MaintenanceSchedule schedule = gson.fromJson(json, MaintenanceSchedule.class); + + assertNotNull(schedule.getMaintenanceEvents()); + assertEquals(1, schedule.getMaintenanceEvents().size()); + + MaintenanceEvent event = schedule.getMaintenanceEvents().get(0); + assertEquals("eventID", event.getEventId()); + assertEquals("Reboot", event.getEventType()); + assertEquals("VirtualMachine", event.getResourceType()); + assertEquals(1, event.getResources().size()); + assertEquals("resourceName", event.getResources().get(0)); + assertEquals("Scheduled", event.getEventStatus()); + assertEquals("eventDescription", event.getDescription()); + assertEquals("Platform", event.getEventSource()); + assertEquals(3600, event.getDurationInSeconds()); + } + + @Test + public void deserialization_maintenanceEvent_parsesNotBeforeDate() { + String json = "{" + + "\"eventId\":\"eventID\"," + + "\"notBefore\":\"2024-01-15T10:30:00Z\"" + + "}"; + + Gson gson = createGsonWithDateSupport(); + MaintenanceEvent event = gson.fromJson(json, MaintenanceEvent.class); + + assertNotNull(event.getNotBefore()); + assertEquals(2024, event.getNotBefore().getYear()); + assertEquals(1, event.getNotBefore().getMonthValue()); + assertEquals(15, event.getNotBefore().getDayOfMonth()); + assertEquals(10, event.getNotBefore().getHour()); + assertEquals(30, event.getNotBefore().getMinute()); + } + + @Test + public void deserialization_multipleEvents_parsesAll() { + String json = "{" + + "\"documentIncarnation\":\"Inc1\"," + + "\"Events\":[" + + " {\"eventId\":\"event1\",\"eventType\":\"Reboot\",\"durationInSeconds\":1800}," + + " {\"eventId\":\"event2\",\"eventType\":\"Freeze\",\"durationInSeconds\":600}" + + "]" + + "}"; + + MaintenanceSchedule schedule = new Gson().fromJson(json, MaintenanceSchedule.class); + + assertEquals(2, schedule.getMaintenanceEvents().size()); + assertEquals("event1", schedule.getMaintenanceEvents().get(0).getEventId()); + assertEquals("Reboot", schedule.getMaintenanceEvents().get(0).getEventType()); + assertEquals(1800, schedule.getMaintenanceEvents().get(0).getDurationInSeconds()); + assertEquals("event2", schedule.getMaintenanceEvents().get(1).getEventId()); + assertEquals("Freeze", schedule.getMaintenanceEvents().get(1).getEventType()); + assertEquals(600, schedule.getMaintenanceEvents().get(1).getDurationInSeconds()); + } + + @Test + public void deserialization_emptyEvents_parsesEmptyList() { + String json = "{" + + "\"documentIncarnation\":\"Inc1\"," + + "\"Events\":[]" + + "}"; + + MaintenanceSchedule schedule = new Gson().fromJson(json, MaintenanceSchedule.class); + + assertNotNull(schedule.getMaintenanceEvents()); + assertEquals(0, schedule.getMaintenanceEvents().size()); + } + + @Test + public void deserialization_multipleResources_parsesAll() { + String json = "{" + + "\"eventId\":\"event1\"," + + "\"Resources\":[\"resource1\",\"resource2\",\"resource3\"]" + + "}"; + + MaintenanceEvent event = new Gson().fromJson(json, MaintenanceEvent.class); + + assertEquals(3, event.getResources().size()); + assertEquals("resource1", event.getResources().get(0)); + assertEquals("resource2", event.getResources().get(1)); + assertEquals("resource3", event.getResources().get(2)); + } + + @Test + public void deserialization_inHeartbeatResponse_parsesMaintenanceSchedule() { + String json = "{" + + "\"nextHeartbeatIntervalMs\":1000," + + "\"operation\":\"Continue\"," + + "\"maintenanceSchedule\":{" + + " \"documentIncarnation\":\"Inc1\"," + + " \"Events\":[{" + + " \"eventId\":\"event1\"," + + " \"eventType\":\"Reboot\"," + + " \"durationInSeconds\":3600" + + " }]" + + "}" + + "}"; + + SessionHostHeartbeatInfo info = new Gson().fromJson(json, SessionHostHeartbeatInfo.class); + + assertNotNull(info.getMaintenanceSchedule()); + assertEquals("Inc1", info.getMaintenanceSchedule().getDocumentIncarnation()); + assertEquals(1, info.getMaintenanceSchedule().getMaintenanceEvents().size()); + assertEquals("event1", info.getMaintenanceSchedule().getMaintenanceEvents().get(0).getEventId()); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/OperationTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/OperationTest.java new file mode 100644 index 00000000..bd225ddc --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/OperationTest.java @@ -0,0 +1,154 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.Gson; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for the Operation enum, especially Gson deserialization + * with the @SerializedName annotations that support multiple casings. + */ +public class OperationTest { + + @Test + public void deserialization_lowercaseContinue() { + String json = "\"continue\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.CONTINUE, op); + } + + @Test + public void deserialization_titleCaseContinue() { + String json = "\"Continue\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.CONTINUE, op); + } + + @Test + public void deserialization_uppercaseContinue() { + String json = "\"CONTINUE\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.CONTINUE, op); + } + + @Test + public void deserialization_lowercaseActive() { + String json = "\"active\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.ACTIVE, op); + } + + @Test + public void deserialization_titleCaseActive() { + String json = "\"Active\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.ACTIVE, op); + } + + @Test + public void deserialization_uppercaseActive() { + String json = "\"ACTIVE\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.ACTIVE, op); + } + + @Test + public void deserialization_lowercaseTerminate() { + String json = "\"terminate\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.TERMINATE, op); + } + + @Test + public void deserialization_titleCaseTerminate() { + String json = "\"Terminate\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.TERMINATE, op); + } + + @Test + public void deserialization_uppercaseTerminate() { + String json = "\"TERMINATE\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.TERMINATE, op); + } + + @Test + public void deserialization_lowercaseInvalid() { + String json = "\"invalid\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.INVALID, op); + } + + @Test + public void deserialization_titleCaseInvalid() { + String json = "\"Invalid\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.INVALID, op); + } + + @Test + public void deserialization_lowercaseGetManifest() { + String json = "\"getmanifest\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.GETMANIFEST, op); + } + + @Test + public void deserialization_titleCaseGetManifest() { + String json = "\"GetManifest\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.GETMANIFEST, op); + } + + @Test + public void deserialization_lowercaseQuarantine() { + String json = "\"quarantine\""; + Operation op = new Gson().fromJson(json, Operation.class); + assertEquals(Operation.QUARANTINE, op); + } + + @Test + public void serialization_producesLowercaseValue() { + Gson gson = new Gson(); + String json = gson.toJson(Operation.CONTINUE); + assertEquals("\"continue\"", json); + } + + @Test + public void serialization_activeProducesLowercaseValue() { + Gson gson = new Gson(); + String json = gson.toJson(Operation.ACTIVE); + assertEquals("\"active\"", json); + } + + @Test + public void serialization_terminateProducesLowercaseValue() { + Gson gson = new Gson(); + String json = gson.toJson(Operation.TERMINATE); + assertEquals("\"terminate\"", json); + } + + @Test + public void allEnumValues_exist() { + Operation[] values = Operation.values(); + assertEquals(6, values.length); + } + + @Test + public void deserialization_inHeartbeatResponse_parsesCorrectly() { + String json = "{\"operation\":\"Active\",\"nextHeartbeatIntervalMs\":1000}"; + Gson gson = new Gson(); + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + assertEquals(Operation.ACTIVE, info.getOperation()); + } + + @Test + public void deserialization_inHeartbeatResponse_lowercaseOperation() { + String json = "{\"operation\":\"terminate\",\"nextHeartbeatIntervalMs\":1000}"; + Gson gson = new Gson(); + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + assertEquals(Operation.TERMINATE, info.getOperation()); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionConfigTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionConfigTest.java new file mode 100644 index 00000000..02d0299e --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionConfigTest.java @@ -0,0 +1,94 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.Gson; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.*; + +public class SessionConfigTest { + + @Test + public void gettersAndSetters_sessionId() { + SessionConfig config = new SessionConfig(); + UUID id = UUID.randomUUID(); + config.setSessionId(id); + assertEquals(id, config.getSessionId()); + } + + @Test + public void gettersAndSetters_sessionCookie() { + SessionConfig config = new SessionConfig(); + config.setSessionCookie("testCookie"); + assertEquals("testCookie", config.getSessionCookie()); + } + + @Test + public void gettersAndSetters_initialPlayers() { + SessionConfig config = new SessionConfig(); + config.setInitialPlayers(Arrays.asList("player1", "player2", "player3")); + assertEquals(3, config.getInitialPlayers().size()); + assertEquals("player1", config.getInitialPlayers().get(0)); + assertEquals("player2", config.getInitialPlayers().get(1)); + assertEquals("player3", config.getInitialPlayers().get(2)); + } + + @Test + public void toMapAllStrings_containsSessionIdAndCookie() { + SessionConfig config = new SessionConfig(); + UUID id = UUID.fromString("a1b2c3d4-e5f6-7890-abcd-ef1234567890"); + config.setSessionId(id); + config.setSessionCookie("awesomeCookie"); + + Map map = config.ToMapAllStrings(); + + assertEquals("a1b2c3d4-e5f6-7890-abcd-ef1234567890", map.get(GameserverSDK.SESSION_ID_KEY)); + assertEquals("awesomeCookie", map.get(GameserverSDK.SESSION_COOKIE_KEY)); + } + + @Test + public void toMapAllStrings_hasTwoEntries() { + SessionConfig config = new SessionConfig(); + config.setSessionId(UUID.randomUUID()); + config.setSessionCookie("cookie"); + + Map map = config.ToMapAllStrings(); + + assertEquals(2, map.size()); + assertTrue(map.containsKey(GameserverSDK.SESSION_ID_KEY)); + assertTrue(map.containsKey(GameserverSDK.SESSION_COOKIE_KEY)); + } + + @Test + public void deserialization_fromJson_parsesCorrectly() { + String json = "{" + + "\"sessionId\":\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"," + + "\"sessionCookie\":\"testCookie\"," + + "\"initialPlayers\":[\"player1\",\"player2\"]" + + "}"; + + Gson gson = new Gson(); + SessionConfig config = gson.fromJson(json, SessionConfig.class); + + assertEquals(UUID.fromString("a1b2c3d4-e5f6-7890-abcd-ef1234567890"), config.getSessionId()); + assertEquals("testCookie", config.getSessionCookie()); + assertEquals(2, config.getInitialPlayers().size()); + assertEquals("player1", config.getInitialPlayers().get(0)); + } + + @Test + public void deserialization_noInitialPlayers_returnsNull() { + String json = "{" + + "\"sessionId\":\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"," + + "\"sessionCookie\":\"testCookie\"" + + "}"; + + Gson gson = new Gson(); + SessionConfig config = gson.fromJson(json, SessionConfig.class); + + assertNull(config.getInitialPlayers()); + } +} diff --git a/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionHostHeartbeatInfoTest.java b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionHostHeartbeatInfoTest.java new file mode 100644 index 00000000..7d7814f9 --- /dev/null +++ b/java/gameserverSDK/src/test/java/com/microsoft/azure/gaming/SessionHostHeartbeatInfoTest.java @@ -0,0 +1,180 @@ +package com.microsoft.azure.gaming; + +import com.google.gson.*; +import org.junit.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class SessionHostHeartbeatInfoTest { + + @Test + public void gettersAndSetters_currentGameState() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + info.setCurrentGameState(SessionHostStatus.Active); + assertEquals(SessionHostStatus.Active, info.getCurrentGameState()); + } + + @Test + public void gettersAndSetters_currentGameHealth() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + info.setCurrentGameHealth(GameHostHealth.Healthy); + assertEquals(GameHostHealth.Healthy, info.getCurrentGameHealth()); + } + + @Test + public void gettersAndSetters_nextHeartbeatIntervalMs() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + info.setNextHeartbeatIntervalMs(5000); + assertEquals(Integer.valueOf(5000), info.getNextHeartbeatIntervalMs()); + } + + @Test + public void gettersAndSetters_operation() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + info.setOperation(Operation.ACTIVE); + assertEquals(Operation.ACTIVE, info.getOperation()); + } + + @Test + public void gettersAndSetters_connectedPlayers() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + List players = Arrays.asList( + new ConnectedPlayer("player1"), + new ConnectedPlayer("player2") + ); + info.setConnectedPlayers(players); + assertEquals(2, info.getCurrentPlayers().size()); + assertEquals("player1", info.getCurrentPlayers().get(0).getPlayerId()); + assertEquals("player2", info.getCurrentPlayers().get(1).getPlayerId()); + } + + @Test + public void gettersAndSetters_sessionConfig() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + SessionConfig config = new SessionConfig(); + config.setSessionCookie("testCookie"); + info.setSessionConfig(config); + assertEquals("testCookie", info.getSessionConfig().getSessionCookie()); + } + + @Test + public void gettersAndSetters_nextScheduledMaintenanceUtc() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + ZonedDateTime maintTime = ZonedDateTime.of(2024, 1, 15, 10, 30, 0, 0, ZoneId.of("UTC")); + info.setNextScheduledMaintenanceUtc(maintTime); + assertEquals(maintTime, info.getNextScheduledMaintenanceUtc()); + } + + @Test + public void gettersAndSetters_maintenanceSchedule() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + MaintenanceSchedule schedule = new MaintenanceSchedule(); + info.setMaintenanceSchedule(schedule); + assertNotNull(info.getMaintenanceSchedule()); + } + + @Test + public void deserialization_heartbeatResponse_parsesCorrectly() { + String json = "{" + + "\"currentGameState\":\"StandingBy\"," + + "\"nextHeartbeatIntervalMs\":1000," + + "\"operation\":\"Continue\"," + + "\"sessionConfig\":{" + + " \"sessionId\":\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"," + + " \"sessionCookie\":\"testCookie\"" + + "}" + + "}"; + + Gson gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (jsonEl, type, ctx) -> + ZonedDateTime.parse(jsonEl.getAsJsonPrimitive().getAsString()).withZoneSameLocal(ZoneId.of("UTC"))).create(); + + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + + assertEquals(Integer.valueOf(1000), info.getNextHeartbeatIntervalMs()); + assertEquals(Operation.CONTINUE, info.getOperation()); + assertNotNull(info.getSessionConfig()); + assertEquals("testCookie", info.getSessionConfig().getSessionCookie()); + } + + @Test + public void deserialization_withNextScheduledMaintenance_parsesDateTime() { + String json = "{" + + "\"nextHeartbeatIntervalMs\":1000," + + "\"operation\":\"Continue\"," + + "\"nextScheduledMaintenanceUtc\":\"2024-01-15T10:30:00Z\"" + + "}"; + + Gson gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (jsonEl, type, ctx) -> + ZonedDateTime.parse(jsonEl.getAsJsonPrimitive().getAsString()).withZoneSameLocal(ZoneId.of("UTC"))).create(); + + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + + assertNotNull(info.getNextScheduledMaintenanceUtc()); + assertEquals(2024, info.getNextScheduledMaintenanceUtc().getYear()); + assertEquals(1, info.getNextScheduledMaintenanceUtc().getMonthValue()); + assertEquals(15, info.getNextScheduledMaintenanceUtc().getDayOfMonth()); + assertEquals(10, info.getNextScheduledMaintenanceUtc().getHour()); + assertEquals(30, info.getNextScheduledMaintenanceUtc().getMinute()); + } + + @Test + public void deserialization_withActiveOperation_parsesCorrectly() { + String json = "{" + + "\"nextHeartbeatIntervalMs\":2000," + + "\"operation\":\"Active\"" + + "}"; + + Gson gson = new Gson(); + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + + assertEquals(Operation.ACTIVE, info.getOperation()); + assertEquals(Integer.valueOf(2000), info.getNextHeartbeatIntervalMs()); + } + + @Test + public void deserialization_withTerminateOperation_parsesCorrectly() { + String json = "{" + + "\"nextHeartbeatIntervalMs\":500," + + "\"operation\":\"Terminate\"" + + "}"; + + Gson gson = new Gson(); + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + + assertEquals(Operation.TERMINATE, info.getOperation()); + } + + @Test + public void deserialization_nullFields_handledGracefully() { + String json = "{" + + "\"nextHeartbeatIntervalMs\":1000," + + "\"operation\":\"Continue\"" + + "}"; + + Gson gson = new Gson(); + SessionHostHeartbeatInfo info = gson.fromJson(json, SessionHostHeartbeatInfo.class); + + assertNull(info.getSessionConfig()); + assertNull(info.getNextScheduledMaintenanceUtc()); + assertNull(info.getMaintenanceSchedule()); + assertNull(info.getCurrentPlayers()); + } + + @Test + public void serialization_heartbeatRequest_producesExpectedJson() { + SessionHostHeartbeatInfo info = new SessionHostHeartbeatInfo(); + info.setCurrentGameState(SessionHostStatus.StandingBy); + info.setCurrentGameHealth(GameHostHealth.Healthy); + + Gson gson = new Gson(); + String json = gson.toJson(info); + + assertTrue(json.contains("\"currentGameState\":\"StandingBy\"")); + assertTrue(json.contains("\"currentGameHealth\":\"Healthy\"")); + } +}