From 2220bfcf5859cdd87b7174a4feb33a2684c5bd3d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 12 May 2026 00:38:56 -0400 Subject: [PATCH] test(commons): failing tests for OpcUaEndpointConfigSerializer --- .../OpcUaEndpointConfigSerializerTests.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs diff --git a/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs b/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs new file mode 100644 index 0000000..bd1aefe --- /dev/null +++ b/tests/ScadaLink.Commons.Tests/Types/DataConnections/OpcUaEndpointConfigSerializerTests.cs @@ -0,0 +1,163 @@ +using ScadaLink.Commons.Serialization; +using ScadaLink.Commons.Types.DataConnections; + +namespace ScadaLink.Commons.Tests.Types.DataConnections; + +public class OpcUaEndpointConfigSerializerTests +{ + [Fact] + public void Serialize_TypedRoundtrip_PreservesAllFields() + { + var original = new OpcUaEndpointConfig + { + EndpointUrl = "opc.tcp://plant-a:4840", + SecurityMode = OpcUaSecurityMode.SignAndEncrypt, + AutoAcceptUntrustedCerts = false, + SessionTimeoutMs = 90000, + OperationTimeoutMs = 20000, + PublishingIntervalMs = 500, + SamplingIntervalMs = 250, + QueueSize = 50, + KeepAliveCount = 5, + LifetimeCount = 15, + MaxNotificationsPerPublish = 200, + Heartbeat = new OpcUaHeartbeatConfig { TagPath = "Sensors.HB", MaxSilenceSeconds = 60 } + }; + + var json = OpcUaEndpointConfigSerializer.Serialize(original); + var (round, isLegacy) = OpcUaEndpointConfigSerializer.Deserialize(json); + + Assert.False(isLegacy); + Assert.Equal(original.EndpointUrl, round.EndpointUrl); + Assert.Equal(original.SecurityMode, round.SecurityMode); + Assert.Equal(original.AutoAcceptUntrustedCerts, round.AutoAcceptUntrustedCerts); + Assert.Equal(original.SessionTimeoutMs, round.SessionTimeoutMs); + Assert.Equal(original.OperationTimeoutMs, round.OperationTimeoutMs); + Assert.Equal(original.PublishingIntervalMs, round.PublishingIntervalMs); + Assert.Equal(original.SamplingIntervalMs, round.SamplingIntervalMs); + Assert.Equal(original.QueueSize, round.QueueSize); + Assert.Equal(original.KeepAliveCount, round.KeepAliveCount); + Assert.Equal(original.LifetimeCount, round.LifetimeCount); + Assert.Equal(original.MaxNotificationsPerPublish, round.MaxNotificationsPerPublish); + Assert.NotNull(round.Heartbeat); + Assert.Equal("Sensors.HB", round.Heartbeat!.TagPath); + Assert.Equal(60, round.Heartbeat.MaxSilenceSeconds); + } + + [Fact] + public void Serialize_UsesCamelCase() + { + var config = new OpcUaEndpointConfig { EndpointUrl = "opc.tcp://x:4840" }; + var json = OpcUaEndpointConfigSerializer.Serialize(config); + Assert.Contains("\"endpointUrl\"", json); + Assert.DoesNotContain("\"EndpointUrl\"", json); + } + + [Fact] + public void Serialize_SecurityModeAsCamelCaseString() + { + var config = new OpcUaEndpointConfig { SecurityMode = OpcUaSecurityMode.SignAndEncrypt }; + var json = OpcUaEndpointConfigSerializer.Serialize(config); + Assert.Contains("\"securityMode\":\"signAndEncrypt\"", json); + } + + [Fact] + public void Deserialize_NullOrEmpty_ReturnsDefaults() + { + var (config1, legacy1) = OpcUaEndpointConfigSerializer.Deserialize(null); + var (config2, legacy2) = OpcUaEndpointConfigSerializer.Deserialize(""); + var (config3, legacy3) = OpcUaEndpointConfigSerializer.Deserialize(" "); + + Assert.False(legacy1); + Assert.False(legacy2); + Assert.False(legacy3); + Assert.Equal("", config1.EndpointUrl); + Assert.Equal(60000, config1.SessionTimeoutMs); + Assert.Null(config1.Heartbeat); + } + + [Fact] + public void Deserialize_LegacyFlatDict_IsLegacyTrue_PopulatesFields() + { + var legacyJson = """ + { + "endpoint": "opc.tcp://legacy:4840", + "SessionTimeoutMs": "45000", + "SamplingIntervalMs": "500", + "SecurityMode": "Sign", + "AutoAcceptUntrustedCerts": "false", + "HeartbeatTagPath": "Old.Heartbeat", + "HeartbeatMaxSilence": "20" + } + """; + + var (config, isLegacy) = OpcUaEndpointConfigSerializer.Deserialize(legacyJson); + + Assert.True(isLegacy); + Assert.Equal("opc.tcp://legacy:4840", config.EndpointUrl); + Assert.Equal(45000, config.SessionTimeoutMs); + Assert.Equal(500, config.SamplingIntervalMs); + Assert.Equal(OpcUaSecurityMode.Sign, config.SecurityMode); + Assert.False(config.AutoAcceptUntrustedCerts); + Assert.NotNull(config.Heartbeat); + Assert.Equal("Old.Heartbeat", config.Heartbeat!.TagPath); + Assert.Equal(20, config.Heartbeat.MaxSilenceSeconds); + } + + [Fact] + public void Deserialize_LegacyWithEndpointUrlPascalKey_Works() + { + var legacyJson = """{"EndpointUrl":"opc.tcp://x:4840"}"""; + var (config, isLegacy) = OpcUaEndpointConfigSerializer.Deserialize(legacyJson); + Assert.True(isLegacy); + Assert.Equal("opc.tcp://x:4840", config.EndpointUrl); + } + + [Fact] + public void ToFlatDict_OmitsNullHeartbeat() + { + var config = new OpcUaEndpointConfig { EndpointUrl = "opc.tcp://x:4840" }; + var dict = OpcUaEndpointConfigSerializer.ToFlatDict(config); + + Assert.Equal("opc.tcp://x:4840", dict["endpoint"]); + Assert.Equal("60000", dict["SessionTimeoutMs"]); + Assert.Equal("None", dict["SecurityMode"]); + Assert.Equal("True", dict["AutoAcceptUntrustedCerts"]); + Assert.False(dict.ContainsKey("HeartbeatTagPath")); + Assert.False(dict.ContainsKey("HeartbeatMaxSilence")); + } + + [Fact] + public void ToFlatDict_IncludesHeartbeat_WhenSet() + { + var config = new OpcUaEndpointConfig + { + EndpointUrl = "opc.tcp://x:4840", + Heartbeat = new OpcUaHeartbeatConfig { TagPath = "HB.Tag", MaxSilenceSeconds = 45 } + }; + var dict = OpcUaEndpointConfigSerializer.ToFlatDict(config); + + Assert.Equal("HB.Tag", dict["HeartbeatTagPath"]); + Assert.Equal("45", dict["HeartbeatMaxSilence"]); + } + + [Fact] + public void FromFlatDict_RoundTripsAllKeys() + { + var dict = new Dictionary + { + ["endpoint"] = "opc.tcp://x:4840", + ["SessionTimeoutMs"] = "30000", + ["SecurityMode"] = "Sign", + ["HeartbeatTagPath"] = "Hb", + ["HeartbeatMaxSilence"] = "15" + }; + var config = OpcUaEndpointConfigSerializer.FromFlatDict(dict); + + Assert.Equal("opc.tcp://x:4840", config.EndpointUrl); + Assert.Equal(30000, config.SessionTimeoutMs); + Assert.Equal(OpcUaSecurityMode.Sign, config.SecurityMode); + Assert.NotNull(config.Heartbeat); + Assert.Equal("Hb", config.Heartbeat!.TagPath); + } +}