diff --git a/.github/workflows/unity-tests.yml b/.github/workflows/unity-tests.yml new file mode 100644 index 00000000..10eb8cf2 --- /dev/null +++ b/.github/workflows/unity-tests.yml @@ -0,0 +1,27 @@ +name: Unity GSDK Tests + +on: + pull_request: + paths: + - 'UnityGsdk/**' + - '.github/workflows/unity-tests.yml' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Build + run: dotnet build UnityGsdk/Tests/Tests.csproj + + - name: Test + run: dotnet test UnityGsdk/Tests/Tests.csproj --no-build diff --git a/UnityGsdk/.gitignore b/UnityGsdk/.gitignore index 7b71e413..3eb3ed13 100644 --- a/UnityGsdk/.gitignore +++ b/UnityGsdk/.gitignore @@ -64,6 +64,9 @@ sysinfo.txt # Crashlytics generated file crashlytics-build.properties +# Allow test project files +!Tests/Tests.csproj + # Packed Addressables /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* diff --git a/UnityGsdk/README.md b/UnityGsdk/README.md index 20344703..b02d9b2e 100644 --- a/UnityGsdk/README.md +++ b/UnityGsdk/README.md @@ -8,7 +8,13 @@ Yup, PlayFab SDK and GSDK would reside in different folders in your Unity projec ### What do I need to do in order to use the Unity GSDK in a new project? -You can drag the `PlayFabSDK` folder to your Unity project. After that, you need to enable the scripring directive `ENABLE_PLAYFABSERVER_API` on your Build settings, like in [this screenshot](https://user-images.githubusercontent.com/8256138/81462605-a6d7ac80-9168-11ea-9748-110ed01095c2.png) +You can drag the `Assets/PlayFabSdk` folder to your Unity project. After that, you need to enable the scripting directive `ENABLE_PLAYFABSERVER_API` on your Build settings, like in [this screenshot](https://user-images.githubusercontent.com/8256138/81462605-a6d7ac80-9168-11ea-9748-110ed01095c2.png). + +### What is the top-level `Tests/` folder? + +The `Tests/` folder at the root of `UnityGsdk/` (next to `Assets/`) contains a standalone .NET (MSTest) project used by SDK maintainers and CI to validate the GSDK source files outside of the Unity engine. Because it lives **outside** the `Assets/` folder, the Unity Editor does not import or compile it, and it has no effect on your game project. You do **not** need to copy it when integrating the Unity GSDK — only the `Assets/PlayFabSdk` folder is required. + +> Note: this is different from `Assets/Tests/`, which is an in-engine NUnit test assembly gated behind the `UNIT_TESTING` define and only compiled when that define is set. ### Heartbeats are failing when I run in container mode (no heartbeat error), what should I do? diff --git a/UnityGsdk/Tests/GSDKConfigurationTests.cs b/UnityGsdk/Tests/GSDKConfigurationTests.cs new file mode 100644 index 00000000..2a8865cc --- /dev/null +++ b/UnityGsdk/Tests/GSDKConfigurationTests.cs @@ -0,0 +1,227 @@ +namespace PlayFab.MultiplayerAgent.Tests +{ + using System; + using System.Collections.Generic; + using Helpers; + using Model; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class GSDKConfigurationTests + { + private static readonly SimpleJsonInstance _jsonInstance = new SimpleJsonInstance(); + + [TestMethod] + public void ReadConfiguration_EmptyGameCertificates_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""gameCertificates"": {} + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.GameCertificates); + Assert.AreEqual(0, config.GameCertificates.Count); + } + + [TestMethod] + public void ReadConfiguration_MultipleGameCertificates_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""gameCertificates"": { + ""gameCert"": ""onetwothree"", + ""gameCert2"": ""threefourfive"" + } + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.GameCertificates); + Assert.AreEqual(2, config.GameCertificates.Count); + Assert.AreEqual("onetwothree", config.GameCertificates["gameCert"]); + Assert.AreEqual("threefourfive", config.GameCertificates["gameCert2"]); + } + + [TestMethod] + public void ReadConfiguration_EmptyGamePorts_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""gamePorts"": {} + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.GamePorts); + Assert.AreEqual(0, config.GamePorts.Count); + } + + [TestMethod] + public void ReadConfiguration_MultipleGamePorts_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""gamePorts"": { + ""8080"": ""debug"", + ""8081"": ""game"" + } + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.GamePorts); + Assert.AreEqual(2, config.GamePorts.Count); + Assert.AreEqual("debug", config.GamePorts["8080"]); + Assert.AreEqual("game", config.GamePorts["8081"]); + } + + [TestMethod] + public void ReadConfiguration_BuildMetadata_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""buildMetadata"": { + ""key1"": ""value1"", + ""key2"": ""value2"" + } + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.BuildMetadata); + Assert.AreEqual(2, config.BuildMetadata.Count); + Assert.AreEqual("value1", config.BuildMetadata["key1"]); + Assert.AreEqual("value2", config.BuildMetadata["key2"]); + } + + [TestMethod] + public void ReadConfiguration_AllFieldsSet_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""testEndpoint"", + ""sessionHostId"": ""testServerId"", + ""vmId"": ""testVmId"", + ""logFolder"": ""testLogFolder"", + ""sharedContentFolder"": ""testSharedContentFolder"", + ""certificateFolder"": ""testCertFolder"", + ""publicIpV4Address"": ""1.2.3.4"", + ""fullyQualifiedDomainName"": ""test.example.com"", + ""gameCertificates"": { ""cert1"": ""thumbprint1"" }, + ""buildMetadata"": { ""key1"": ""value1"" }, + ""gamePorts"": { ""port1"": ""1111"" } + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.AreEqual("testEndpoint", config.HeartbeatEndpoint); + Assert.AreEqual("testServerId", config.SessionHostId); + Assert.AreEqual("testVmId", config.VmId); + Assert.AreEqual("testLogFolder", config.LogFolder); + Assert.AreEqual("testSharedContentFolder", config.SharedContentFolder); + Assert.AreEqual("testCertFolder", config.CertificateFolder); + Assert.AreEqual("1.2.3.4", config.PublicIpV4Address); + Assert.AreEqual("test.example.com", config.FullyQualifiedDomainName); + Assert.AreEqual("thumbprint1", config.GameCertificates["cert1"]); + Assert.AreEqual("value1", config.BuildMetadata["key1"]); + Assert.AreEqual("1111", config.GamePorts["port1"]); + } + + [TestMethod] + public void ReadConfiguration_EnvironmentVariables_Captured() + { + string originalTitleId = Environment.GetEnvironmentVariable("PF_TITLE_ID"); + string originalBuildId = Environment.GetEnvironmentVariable("PF_BUILD_ID"); + string originalRegion = Environment.GetEnvironmentVariable("PF_REGION"); + + Environment.SetEnvironmentVariable("PF_TITLE_ID", "testTitleId"); + Environment.SetEnvironmentVariable("PF_BUILD_ID", "testBuildId"); + Environment.SetEnvironmentVariable("PF_REGION", "testRegion"); + + try + { + var config = new GSDKConfiguration(); + Assert.AreEqual("testTitleId", config.TitleId); + Assert.AreEqual("testBuildId", config.BuildId); + Assert.AreEqual("testRegion", config.Region); + } + finally + { + Environment.SetEnvironmentVariable("PF_TITLE_ID", originalTitleId); + Environment.SetEnvironmentVariable("PF_BUILD_ID", originalBuildId); + Environment.SetEnvironmentVariable("PF_REGION", originalRegion); + } + } + + [TestMethod] + public void ReadConfiguration_GameServerConnectionInfo_Parsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""heartbeatendpoint"", + ""sessionHostId"": ""serverid"", + ""gameServerConnectionInfo"": { + ""publicIpV4Address"": ""10.0.0.1"", + ""gamePortsConfiguration"": [ + { + ""name"": ""game"", + ""serverListeningPort"": 7777, + ""clientConnectionPort"": 30000 + } + ] + } + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.IsNotNull(config.GameServerConnectionInfo); + Assert.AreEqual("10.0.0.1", config.GameServerConnectionInfo.PublicIPv4Address); + } + + [TestMethod] + public void ReadConfiguration_OptionalFieldsNull_DefaultsUsed() + { + string json = @" + { + ""heartbeatEndpoint"": ""testEndpoint"", + ""sessionHostId"": ""testServerId"" + }"; + + GSDKConfiguration config = _jsonInstance.DeserializeObject(json); + Assert.AreEqual("testEndpoint", config.HeartbeatEndpoint); + Assert.AreEqual("testServerId", config.SessionHostId); + Assert.IsNull(config.LogFolder); + Assert.IsNull(config.SharedContentFolder); + Assert.IsNull(config.CertificateFolder); + } + + [TestMethod] + public void SerializeAndDeserialize_RoundTrip_PreservesData() + { + var config = new GSDKConfiguration + { + HeartbeatEndpoint = "testEndpoint", + SessionHostId = "testServerId", + LogFolder = "testLogFolder" + }; + config.GameCertificates["cert1"] = "thumbprint1"; + config.GamePorts["port1"] = "1111"; + config.BuildMetadata["key1"] = "value1"; + + string json = _jsonInstance.SerializeObject(config); + GSDKConfiguration deserialized = _jsonInstance.DeserializeObject(json); + + Assert.AreEqual(config.HeartbeatEndpoint, deserialized.HeartbeatEndpoint); + Assert.AreEqual(config.SessionHostId, deserialized.SessionHostId); + Assert.AreEqual(config.LogFolder, deserialized.LogFolder); + Assert.AreEqual("thumbprint1", deserialized.GameCertificates["cert1"]); + Assert.AreEqual("1111", deserialized.GamePorts["port1"]); + Assert.AreEqual("value1", deserialized.BuildMetadata["key1"]); + } + } +} diff --git a/UnityGsdk/Tests/SerializationTests.cs b/UnityGsdk/Tests/SerializationTests.cs new file mode 100644 index 00000000..60427013 --- /dev/null +++ b/UnityGsdk/Tests/SerializationTests.cs @@ -0,0 +1,311 @@ +namespace PlayFab.MultiplayerAgent.Tests +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using Helpers; + using Model; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SerializationTests + { + private static readonly SimpleJsonInstance _jsonInstance = new SimpleJsonInstance(); + + [TestMethod] + public void HeartbeatRequest_Serialization_RoundTrip() + { + var request = new HeartbeatRequest + { + CurrentGameState = GameState.Active, + CurrentGameHealth = "Healthy", + CurrentPlayers = new List + { + new ConnectedPlayer("player1"), + new ConnectedPlayer("player2") + } + }; + + string json = _jsonInstance.SerializeObject(request); + var deserialized = _jsonInstance.DeserializeObject(json); + + Assert.AreEqual(GameState.Active, deserialized.CurrentGameState); + Assert.AreEqual("Healthy", deserialized.CurrentGameHealth); + Assert.AreEqual(2, deserialized.CurrentPlayers.Count); + Assert.AreEqual("player1", deserialized.CurrentPlayers[0].PlayerId); + Assert.AreEqual("player2", deserialized.CurrentPlayers[1].PlayerId); + } + + [TestMethod] + public void HeartbeatRequest_InitialState_SerializesCorrectly() + { + var request = new HeartbeatRequest + { + CurrentGameState = GameState.Initializing, + CurrentGameHealth = "Healthy", + CurrentPlayers = new List() + }; + + string json = _jsonInstance.SerializeObject(request); + var deserialized = _jsonInstance.DeserializeObject(json); + + Assert.AreEqual(GameState.Initializing, deserialized.CurrentGameState); + Assert.AreEqual("Healthy", deserialized.CurrentGameHealth); + Assert.AreEqual(0, deserialized.CurrentPlayers.Count); + } + + [TestMethod] + public void HeartbeatResponse_Deserialization_ActiveOperation() + { + string responseJson = @" + { + ""operation"": ""Active"", + ""sessionConfig"": { + ""sessionId"": ""eca7e870-da2e-45f9-bb66-30d89064313a"", + ""sessionCookie"": ""OreoCookie"" + }, + ""nextScheduledMaintenanceUtc"": ""2018-04-12T16:58:30.145Z"", + ""nextHeartbeatIntervalMs"": 30000 + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + + Assert.AreEqual(GameOperation.Active, response.Operation); + Assert.IsNotNull(response.SessionConfig); + Assert.AreEqual("eca7e870-da2e-45f9-bb66-30d89064313a", response.SessionConfig.SessionId); + Assert.AreEqual("OreoCookie", response.SessionConfig.SessionCookie); + Assert.AreEqual("2018-04-12T16:58:30.145Z", response.NextScheduledMaintenanceUtc); + } + + [TestMethod] + public void HeartbeatResponse_Deserialization_TerminateOperation() + { + string responseJson = @" + { + ""operation"": ""Terminate"", + ""sessionConfig"": { + ""sessionId"": ""test-session"" + } + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + Assert.AreEqual(GameOperation.Terminate, response.Operation); + } + + [TestMethod] + public void HeartbeatResponse_Deserialization_ContinueOperation() + { + string responseJson = @" + { + ""operation"": ""Continue"" + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + Assert.AreEqual(GameOperation.Continue, response.Operation); + } + + [TestMethod] + public void HeartbeatResponse_WithInitialPlayers_Parsed() + { + string responseJson = @" + { + ""operation"": ""Active"", + ""sessionConfig"": { + ""sessionId"": ""eca7e870-da2e-45f9-bb66-30d89064313a"", + ""sessionCookie"": ""OreoCookie"", + ""initialPlayers"": [""player0"", ""player1"", ""player2""] + } + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + + Assert.IsNotNull(response.SessionConfig.InitialPlayers); + Assert.AreEqual(3, response.SessionConfig.InitialPlayers.Count); + Assert.AreEqual("player0", response.SessionConfig.InitialPlayers[0]); + Assert.AreEqual("player1", response.SessionConfig.InitialPlayers[1]); + Assert.AreEqual("player2", response.SessionConfig.InitialPlayers[2]); + } + + [TestMethod] + public void HeartbeatResponse_WithSessionMetadata_Parsed() + { + string responseJson = @" + { + ""operation"": ""Active"", + ""sessionConfig"": { + ""sessionId"": ""eca7e870-da2e-45f9-bb66-30d89064313a"", + ""sessionCookie"": ""OreoCookie"", + ""metadata"": { + ""testKey"": ""testValue"" + } + } + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + + Assert.IsNotNull(response.SessionConfig.Metadata); + Assert.AreEqual("testValue", response.SessionConfig.Metadata["testKey"]); + } + + [TestMethod] + public void HeartbeatResponse_WithMaintenanceSchedule_Parsed() + { + string responseJson = @" + { + ""operation"": ""Active"", + ""sessionConfig"": { + ""sessionId"": ""eca7e870-da2e-45f9-bb66-30d89064313a"" + }, + ""maintenanceSchedule"": { + ""documentIncarnation"": ""IncarnationID"", + ""Events"": [ + { + ""eventId"": ""eventID"", + ""eventType"": ""Reboot"", + ""resourceType"": ""VirtualMachine"", + ""Resources"": [""resourceName""], + ""eventStatus"": ""Scheduled"", + ""notBefore"": ""2018-04-12T16:58:30.145Z"", + ""description"": ""eventDescription"", + ""eventSource"": ""Platform"", + ""durationInSeconds"": 3600 + } + ] + } + }"; + + var response = _jsonInstance.DeserializeObject(responseJson); + + Assert.IsNotNull(response.MaintenanceSchedule); + Assert.AreEqual("IncarnationID", response.MaintenanceSchedule.DocumentIncarnation); + Assert.AreEqual(1, response.MaintenanceSchedule.Events.Count); + + var evt = response.MaintenanceSchedule.Events[0]; + Assert.AreEqual("eventID", evt.EventId); + Assert.AreEqual("Reboot", evt.EventType); + Assert.AreEqual("VirtualMachine", evt.ResourceType); + Assert.AreEqual(1, evt.Resources.Count); + Assert.AreEqual("resourceName", evt.Resources[0]); + Assert.AreEqual("Scheduled", evt.EventStatus); + Assert.IsNotNull(evt.NotBefore); + Assert.AreEqual("eventDescription", evt.Description); + Assert.AreEqual("Platform", evt.EventSource); + Assert.AreEqual(3600, evt.DurationInSeconds); + } + + [TestMethod] + public void ConnectedPlayer_Serialization_RoundTrip() + { + var player = new ConnectedPlayer("testPlayer"); + string json = _jsonInstance.SerializeObject(player); + var deserialized = _jsonInstance.DeserializeObject(json); + + Assert.AreEqual("testPlayer", deserialized.PlayerId); + } + + [TestMethod] + public void ConnectedPlayer_DefaultConstructor_NullPlayerId() + { + var player = new ConnectedPlayer(); + Assert.IsNull(player.PlayerId); + } + + [TestMethod] + public void InvalidJson_DoesNotCrash() + { + string responseJson = @" + { + ""operation"": ""Active"", + ""sessionConfig"": { + ""sessionId"": ""eca7e870-da2e-45f9-bb66-30d89064313a"", + ""sessionCookie"": ""OreoCookie"" + }, + ""nextScheduledMaintenanceUtc"": ""2018-04-12T16:58:30.145Z"", + }"; + + // Should not throw - SimpleJson is lenient with trailing commas + _jsonInstance.DeserializeObject(responseJson); + } + + [TestMethod] + public void MaintenanceSchedule_Serialization_RoundTrip() + { + var schedule = new MaintenanceSchedule + { + DocumentIncarnation = "TestIncarnation", + Events = new List + { + new MaintenanceEvent + { + EventId = "evt1", + EventType = "Reboot", + ResourceType = "VirtualMachine", + Resources = new List { "resource1" }, + EventStatus = "Scheduled", + Description = "Test event", + EventSource = "Platform", + DurationInSeconds = 1800 + } + } + }; + + string json = _jsonInstance.SerializeObject(schedule); + var deserialized = _jsonInstance.DeserializeObject(json); + + Assert.AreEqual("TestIncarnation", deserialized.DocumentIncarnation); + Assert.AreEqual(1, deserialized.Events.Count); + Assert.AreEqual("evt1", deserialized.Events[0].EventId); + Assert.AreEqual("Reboot", deserialized.Events[0].EventType); + Assert.AreEqual(1800, deserialized.Events[0].DurationInSeconds); + } + + [TestMethod] + public void GameServerConnectionInfo_Deserialization_Parsed() + { + string json = @" + { + ""publicIpV4Address"": ""10.0.0.1"", + ""gamePortsConfiguration"": [ + { + ""name"": ""game"", + ""serverListeningPort"": 7777, + ""clientConnectionPort"": 30000 + }, + { + ""name"": ""debug"", + ""serverListeningPort"": 8888, + ""clientConnectionPort"": 30001 + } + ] + }"; + + var connInfo = _jsonInstance.DeserializeObject(json); + Assert.AreEqual("10.0.0.1", connInfo.PublicIPv4Address); + } + + [TestMethod] + public void GameState_AllValues_SerializeCorrectly() + { + foreach (GameState state in Enum.GetValues(typeof(GameState))) + { + var request = new HeartbeatRequest { CurrentGameState = state }; + string json = _jsonInstance.SerializeObject(request); + var deserialized = _jsonInstance.DeserializeObject(json); + Assert.AreEqual(state, deserialized.CurrentGameState, + $"GameState {state} should survive round-trip serialization."); + } + } + + [TestMethod] + public void GameOperation_AllValues_DeserializeCorrectly() + { + foreach (GameOperation op in Enum.GetValues(typeof(GameOperation))) + { + string json = $@"{{ ""operation"": ""{op}"" }}"; + var response = _jsonInstance.DeserializeObject(json); + Assert.AreEqual(op, response.Operation, + $"GameOperation {op} should deserialize correctly."); + } + } + } +} diff --git a/UnityGsdk/Tests/SessionConfigTests.cs b/UnityGsdk/Tests/SessionConfigTests.cs new file mode 100644 index 00000000..08e13b0b --- /dev/null +++ b/UnityGsdk/Tests/SessionConfigTests.cs @@ -0,0 +1,172 @@ +namespace PlayFab.MultiplayerAgent.Tests +{ + using System.Collections.Generic; + using Model; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SessionConfigTests + { + [TestMethod] + public void CopyNonNullFields_NullInput_NoChange() + { + var target = new SessionConfig + { + SessionId = "original", + SessionCookie = "cookie" + }; + + target.CopyNonNullFields(null); + + Assert.AreEqual("original", target.SessionId); + Assert.AreEqual("cookie", target.SessionCookie); + } + + [TestMethod] + public void CopyNonNullFields_AllFieldsSet_Copied() + { + var target = new SessionConfig(); + var source = new SessionConfig + { + SessionId = "newId", + SessionCookie = "newCookie", + InitialPlayers = new List { "player1", "player2" }, + Metadata = new Dictionary { { "key1", "value1" } } + }; + + target.CopyNonNullFields(source); + + Assert.AreEqual("newId", target.SessionId); + Assert.AreEqual("newCookie", target.SessionCookie); + Assert.AreEqual(2, target.InitialPlayers.Count); + Assert.AreEqual("player1", target.InitialPlayers[0]); + Assert.AreEqual("player2", target.InitialPlayers[1]); + Assert.AreEqual("value1", target.Metadata["key1"]); + } + + [TestMethod] + public void CopyNonNullFields_PartialFields_OnlyNonNullCopied() + { + var target = new SessionConfig + { + SessionId = "original", + SessionCookie = "originalCookie" + }; + + var source = new SessionConfig + { + SessionId = "newId" + }; + + target.CopyNonNullFields(source); + + Assert.AreEqual("newId", target.SessionId); + Assert.AreEqual("originalCookie", target.SessionCookie); + } + + [TestMethod] + public void CopyNonNullFields_EmptyPlayers_NotCopied() + { + var target = new SessionConfig + { + InitialPlayers = new List { "existingPlayer" } + }; + + var source = new SessionConfig + { + InitialPlayers = new List() + }; + + target.CopyNonNullFields(source); + + Assert.AreEqual(1, target.InitialPlayers.Count); + Assert.AreEqual("existingPlayer", target.InitialPlayers[0]); + } + + [TestMethod] + public void CopyNonNullFields_EmptyMetadata_NotCopied() + { + var target = new SessionConfig + { + Metadata = new Dictionary { { "existingKey", "existingValue" } } + }; + + var source = new SessionConfig + { + Metadata = new Dictionary() + }; + + target.CopyNonNullFields(source); + + Assert.AreEqual(1, target.Metadata.Count); + Assert.AreEqual("existingValue", target.Metadata["existingKey"]); + } + + [TestMethod] + public void Equals_SameValues_ReturnsTrue() + { + var config1 = new SessionConfig + { + SessionId = "id1", + SessionCookie = "cookie1" + }; + + var config2 = new SessionConfig + { + SessionId = "id1", + SessionCookie = "cookie1" + }; + + Assert.IsTrue(config1.Equals(config2)); + Assert.IsTrue(config1 == config2); + } + + [TestMethod] + public void Equals_DifferentValues_ReturnsFalse() + { + var config1 = new SessionConfig + { + SessionId = "id1", + SessionCookie = "cookie1" + }; + + var config2 = new SessionConfig + { + SessionId = "id2", + SessionCookie = "cookie2" + }; + + Assert.IsFalse(config1.Equals(config2)); + Assert.IsTrue(config1 != config2); + } + + [TestMethod] + public void Equals_NullComparison_ReturnsFalse() + { + var config = new SessionConfig + { + SessionId = "id1" + }; + + Assert.IsFalse(config.Equals(null)); + } + + [TestMethod] + public void GetHashCode_SameValues_SameHash() + { + var config1 = new SessionConfig + { + SessionId = "id1", + SessionCookie = "cookie1" + }; + + var config2 = new SessionConfig + { + SessionId = "id1", + SessionCookie = "cookie1" + }; + + Assert.AreEqual(config1.GetHashCode(), config2.GetHashCode()); + } + } +} diff --git a/UnityGsdk/Tests/Tests.csproj b/UnityGsdk/Tests/Tests.csproj new file mode 100644 index 00000000..95af5fa9 --- /dev/null +++ b/UnityGsdk/Tests/Tests.csproj @@ -0,0 +1,28 @@ + + + net10.0 + false + + + + + + + + + + + + + + + + + + + + + + + +