c4086c243c
- S7DriverPage.FormModel now preserves Tags through Form ↔ Options translation (was hard-coding Tags = [] on every save, silently destroying any tag list that operators had configured). - Add FormModel_RoundTrip tests for OpcUaClient and Historian mirror classes — both were translating Options ↔ form-model entirely untested. - Surface S7 Tags in the round-trip test so this regression can't reach merge again.
157 lines
7.0 KiB
C#
157 lines
7.0 KiB
C#
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<OpcUaClientDriverOptions>(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<OpcUaClientDriverOptions>(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<string> { "opc.tcp://primary:4840", "opc.tcp://backup:4840" };
|
|
var unsMappingTable = new Dictionary<string, string>
|
|
{
|
|
["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);
|
|
}
|
|
}
|