using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Clusters.Drivers; using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests; public sealed class OpcUaClientDriverPageFormSerializationTests { private static readonly JsonSerializerOptions _opts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false, }; [Fact] public void RoundTrip_PreservesKnownFields() { var original = new OpcUaClientDriverOptions { EndpointUrl = "opc.tcp://plc.internal:4840", ApplicationUri = "urn:plc:OtOpcUa:GatewayClient", SessionName = "MySession", SecurityMode = OpcUaSecurityMode.SignAndEncrypt, SecurityPolicy = OpcUaSecurityPolicy.Basic256Sha256, AuthType = OpcUaAuthType.Username, Username = "operator", Password = "s3cr3t", PerEndpointConnectTimeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(20), SessionTimeout = TimeSpan.FromSeconds(180), KeepAliveInterval = TimeSpan.FromSeconds(10), ReconnectPeriod = TimeSpan.FromSeconds(15), AutoAcceptCertificates = true, BrowseRoot = "i=85", MaxDiscoveredNodes = 5000, MaxBrowseDepth = 6, TargetNamespaceKind = OpcUaTargetNamespaceKind.SystemPlatform, ProbeTimeoutSeconds = 20, }; var json = JsonSerializer.Serialize(original, _opts); var back = JsonSerializer.Deserialize(json, _opts); back.ShouldNotBeNull(); back.EndpointUrl.ShouldBe("opc.tcp://plc.internal:4840"); back.ApplicationUri.ShouldBe("urn:plc:OtOpcUa:GatewayClient"); back.SessionName.ShouldBe("MySession"); back.SecurityMode.ShouldBe(OpcUaSecurityMode.SignAndEncrypt); back.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.Basic256Sha256); back.AuthType.ShouldBe(OpcUaAuthType.Username); back.Username.ShouldBe("operator"); back.Password.ShouldBe("s3cr3t"); back.PerEndpointConnectTimeout.ShouldBe(TimeSpan.FromSeconds(5)); back.Timeout.ShouldBe(TimeSpan.FromSeconds(20)); back.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(180)); back.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(10)); back.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(15)); back.AutoAcceptCertificates.ShouldBeTrue(); back.BrowseRoot.ShouldBe("i=85"); back.MaxDiscoveredNodes.ShouldBe(5000); back.MaxBrowseDepth.ShouldBe(6); back.TargetNamespaceKind.ShouldBe(OpcUaTargetNamespaceKind.SystemPlatform); back.ProbeTimeoutSeconds.ShouldBe(20); } [Fact] public void Deserialize_DropsUnknownFields() { var jsonWithExtra = """{"unknownField":"old-value","probeTimeoutSeconds":20}"""; var optsWithSkip = new JsonSerializerOptions(_opts) { UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, }; var back = JsonSerializer.Deserialize(jsonWithExtra, optsWithSkip); back.ShouldNotBeNull(); back.ProbeTimeoutSeconds.ShouldBe(20); } [Fact] public void FormModel_RoundTrip_PreservesAllFields() { // Construct options with non-default values for every editable property plus // non-empty EndpointUrls and UnsMappingTable — both are "read-only" in the form // but must survive the FormModel translation unchanged. var endpointUrls = new List { "opc.tcp://primary:4840", "opc.tcp://backup:4840" }; var unsMappingTable = new Dictionary { ["Line1/"] = "Site/Area1/Line1", ["Line2/"] = "Site/Area1/Line2", }; var original = new OpcUaClientDriverOptions { EndpointUrl = "opc.tcp://fallback:4840", EndpointUrls = endpointUrls, ApplicationUri = "urn:test:OtOpcUa:GatewayClient", SessionName = "TestSession", SecurityMode = OpcUaSecurityMode.SignAndEncrypt, SecurityPolicy = OpcUaSecurityPolicy.Basic256Sha256, AuthType = OpcUaAuthType.Username, Username = "opcuser", Password = "p@ssw0rd", UserCertificatePath = @"C:\certs\user.pfx", UserCertificatePassword = "certpass", PerEndpointConnectTimeout = TimeSpan.FromSeconds(4), Timeout = TimeSpan.FromSeconds(12), SessionTimeout = TimeSpan.FromSeconds(150), KeepAliveInterval = TimeSpan.FromSeconds(7), ReconnectPeriod = TimeSpan.FromSeconds(8), AutoAcceptCertificates = true, BrowseRoot = "i=85", MaxDiscoveredNodes = 3000, MaxBrowseDepth = 5, TargetNamespaceKind = OpcUaTargetNamespaceKind.SystemPlatform, UnsMappingTable = unsMappingTable, ProbeTimeoutSeconds = 25, }; var form = OpcUaClientDriverPage.OpcUaClientFormModel.FromRecord(original); var result = form.ToRecord(); result.EndpointUrl.ShouldBe("opc.tcp://fallback:4840"); result.EndpointUrls.Count.ShouldBe(2); result.EndpointUrls[0].ShouldBe("opc.tcp://primary:4840"); result.EndpointUrls[1].ShouldBe("opc.tcp://backup:4840"); result.ApplicationUri.ShouldBe("urn:test:OtOpcUa:GatewayClient"); result.SessionName.ShouldBe("TestSession"); result.SecurityMode.ShouldBe(OpcUaSecurityMode.SignAndEncrypt); result.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.Basic256Sha256); result.AuthType.ShouldBe(OpcUaAuthType.Username); result.Username.ShouldBe("opcuser"); result.Password.ShouldBe("p@ssw0rd"); result.UserCertificatePath.ShouldBe(@"C:\certs\user.pfx"); result.UserCertificatePassword.ShouldBe("certpass"); result.PerEndpointConnectTimeout.ShouldBe(TimeSpan.FromSeconds(4)); result.Timeout.ShouldBe(TimeSpan.FromSeconds(12)); result.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(150)); result.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(7)); result.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(8)); result.AutoAcceptCertificates.ShouldBeTrue(); result.BrowseRoot.ShouldBe("i=85"); result.MaxDiscoveredNodes.ShouldBe(3000); result.MaxBrowseDepth.ShouldBe(5); result.TargetNamespaceKind.ShouldBe(OpcUaTargetNamespaceKind.SystemPlatform); result.UnsMappingTable.Count.ShouldBe(2); result.UnsMappingTable["Line1/"].ShouldBe("Site/Area1/Line1"); result.UnsMappingTable["Line2/"].ShouldBe("Site/Area1/Line2"); result.ProbeTimeoutSeconds.ShouldBe(25); } }