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.
113 lines
4.1 KiB
C#
113 lines
4.1 KiB
C#
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.Historian.Wonderware.Client;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
|
|
|
public sealed class HistorianWonderwareDriverPageFormSerializationTests
|
|
{
|
|
private static readonly JsonSerializerOptions _opts = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
WriteIndented = false,
|
|
};
|
|
|
|
[Fact]
|
|
public void RoundTrip_PreservesKnownFields()
|
|
{
|
|
var original = new WonderwareHistorianClientOptions(
|
|
PipeName: "otopcua-historian-prod",
|
|
SharedSecret: "t0ps3cr3t",
|
|
PeerName: "OtOpcUa-Primary",
|
|
ConnectTimeout: TimeSpan.FromSeconds(20),
|
|
CallTimeout: TimeSpan.FromSeconds(60))
|
|
{
|
|
ProbeTimeoutSeconds = 25,
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(original, _opts);
|
|
var back = JsonSerializer.Deserialize<WonderwareHistorianClientOptions>(json, _opts);
|
|
|
|
back.ShouldNotBeNull();
|
|
back.PipeName.ShouldBe("otopcua-historian-prod");
|
|
back.SharedSecret.ShouldBe("t0ps3cr3t");
|
|
back.PeerName.ShouldBe("OtOpcUa-Primary");
|
|
back.ConnectTimeout.ShouldBe(TimeSpan.FromSeconds(20));
|
|
back.CallTimeout.ShouldBe(TimeSpan.FromSeconds(60));
|
|
back.EffectiveConnectTimeout.ShouldBe(TimeSpan.FromSeconds(20));
|
|
back.EffectiveCallTimeout.ShouldBe(TimeSpan.FromSeconds(60));
|
|
back.ProbeTimeoutSeconds.ShouldBe(25);
|
|
}
|
|
|
|
[Fact]
|
|
public void RoundTrip_NullTimeouts_UsesDefaults()
|
|
{
|
|
var original = new WonderwareHistorianClientOptions(
|
|
PipeName: "otopcua-historian",
|
|
SharedSecret: "secret");
|
|
|
|
var json = JsonSerializer.Serialize(original, _opts);
|
|
var back = JsonSerializer.Deserialize<WonderwareHistorianClientOptions>(json, _opts);
|
|
|
|
back.ShouldNotBeNull();
|
|
back.ConnectTimeout.ShouldBeNull();
|
|
back.CallTimeout.ShouldBeNull();
|
|
back.EffectiveConnectTimeout.ShouldBe(TimeSpan.FromSeconds(10));
|
|
back.EffectiveCallTimeout.ShouldBe(TimeSpan.FromSeconds(30));
|
|
}
|
|
|
|
[Fact]
|
|
public void Deserialize_DropsUnknownFields()
|
|
{
|
|
var jsonWithExtra = """
|
|
{
|
|
"unknownField": "old-value",
|
|
"pipeName": "otopcua-historian",
|
|
"sharedSecret": "s3cr3t",
|
|
"probeTimeoutSeconds": 20
|
|
}
|
|
""";
|
|
|
|
var optsWithSkip = new JsonSerializerOptions(_opts)
|
|
{
|
|
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
|
|
};
|
|
|
|
var back = JsonSerializer.Deserialize<WonderwareHistorianClientOptions>(jsonWithExtra, optsWithSkip);
|
|
back.ShouldNotBeNull();
|
|
back.ProbeTimeoutSeconds.ShouldBe(20);
|
|
back.PipeName.ShouldBe("otopcua-historian");
|
|
}
|
|
|
|
[Fact]
|
|
public void FormModel_RoundTrip_PreservesAllFields()
|
|
{
|
|
// Construct a record with non-default values for every property and verify
|
|
// that WonderwareHistorianClientFormModel.FromRecord → ToRecord is lossless.
|
|
var original = new WonderwareHistorianClientOptions(
|
|
PipeName: "otopcua-historian-prod",
|
|
SharedSecret: "sup3rs3cr3t",
|
|
PeerName: "OtOpcUa-Redundant",
|
|
ConnectTimeout: TimeSpan.FromSeconds(18),
|
|
CallTimeout: TimeSpan.FromSeconds(45))
|
|
{
|
|
ProbeTimeoutSeconds = 30,
|
|
};
|
|
|
|
var form = HistorianWonderwareDriverPage.WonderwareHistorianClientFormModel.FromRecord(original);
|
|
var result = form.ToRecord();
|
|
|
|
result.PipeName.ShouldBe("otopcua-historian-prod");
|
|
result.SharedSecret.ShouldBe("sup3rs3cr3t");
|
|
result.PeerName.ShouldBe("OtOpcUa-Redundant");
|
|
result.ConnectTimeout.ShouldBe(TimeSpan.FromSeconds(18));
|
|
result.CallTimeout.ShouldBe(TimeSpan.FromSeconds(45));
|
|
result.EffectiveConnectTimeout.ShouldBe(TimeSpan.FromSeconds(18));
|
|
result.EffectiveCallTimeout.ShouldBe(TimeSpan.FromSeconds(45));
|
|
result.ProbeTimeoutSeconds.ShouldBe(30);
|
|
}
|
|
}
|