refactor(historian-gateway): retire Wonderware historian projects (gateway is sole backend)
The HistorianGateway driver is now the sole historian read/write+alarm backend, so the Wonderware sidecar projects are dead code. Removes the 5 Wonderware projects (driver, .Client, .Client.Contracts, + their 2 test projects) from the solution and tree, and fully retires the vestigial 'Historian.Wonderware' driver type (UI/probe-only; it had no driver factory): the Host probe registration, the AdminUI driver-config surface (driver page, tag-config editor/model/validator entry, address picker/builder, driver-type catalog + dropdown + edit-router entries), and their tests. Prunes the now-unused Wonderware connection fields (Host/Port/UseTls/ServerCertThumbprint/SharedSecret) from AlarmHistorianOptions (keeping Enabled + the SQLite store-and-forward knobs) and refreshes the stale XML docs that named Wonderware as the production backend. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
@@ -62,7 +62,7 @@ public sealed class DriverPageJsonConverterTests
|
||||
var allDriverPages = typeof(S7DriverPage).Assembly.GetTypes()
|
||||
.Where(t => t.Name.EndsWith("DriverPage", StringComparison.Ordinal) && !t.IsAbstract)
|
||||
.ToList();
|
||||
allDriverPages.Count.ShouldBeGreaterThanOrEqualTo(9, "reflection should discover the full driver-page fleet");
|
||||
allDriverPages.Count.ShouldBeGreaterThanOrEqualTo(8, "reflection should discover the full driver-page fleet");
|
||||
DriverPageTypes.Count.ShouldBe(allDriverPages.Count,
|
||||
"every *DriverPage must declare a _jsonOpts config serializer so the string-enum converter guard covers it");
|
||||
}
|
||||
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
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(
|
||||
Host: "historian-prod.zb.local",
|
||||
Port: 32569,
|
||||
SharedSecret: "t0ps3cr3t",
|
||||
PeerName: "OtOpcUa-Primary",
|
||||
ConnectTimeout: TimeSpan.FromSeconds(20),
|
||||
CallTimeout: TimeSpan.FromSeconds(60))
|
||||
{
|
||||
ProbeTimeoutSeconds = 25,
|
||||
UseTls = true,
|
||||
ServerCertThumbprint = "A1B2C3D4E5F60718293A4B5C6D7E8F9012345678",
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(original, _opts);
|
||||
var back = JsonSerializer.Deserialize<WonderwareHistorianClientOptions>(json, _opts);
|
||||
|
||||
back.ShouldNotBeNull();
|
||||
back.Host.ShouldBe("historian-prod.zb.local");
|
||||
back.Port.ShouldBe(32569);
|
||||
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);
|
||||
back.UseTls.ShouldBeTrue();
|
||||
back.ServerCertThumbprint.ShouldBe("A1B2C3D4E5F60718293A4B5C6D7E8F9012345678");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundTrip_NullTimeouts_UsesDefaults()
|
||||
{
|
||||
var original = new WonderwareHistorianClientOptions(
|
||||
Host: "localhost",
|
||||
Port: 32569,
|
||||
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));
|
||||
back.UseTls.ShouldBeFalse();
|
||||
back.ServerCertThumbprint.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_DropsUnknownFields()
|
||||
{
|
||||
var jsonWithExtra = """
|
||||
{
|
||||
"unknownField": "old-value",
|
||||
"host": "historian.zb.local",
|
||||
"port": 32569,
|
||||
"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.Host.ShouldBe("historian.zb.local");
|
||||
back.Port.ShouldBe(32569);
|
||||
}
|
||||
|
||||
[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(
|
||||
Host: "historian-prod.zb.local",
|
||||
Port: 32570,
|
||||
SharedSecret: "sup3rs3cr3t",
|
||||
PeerName: "OtOpcUa-Redundant",
|
||||
ConnectTimeout: TimeSpan.FromSeconds(18),
|
||||
CallTimeout: TimeSpan.FromSeconds(45))
|
||||
{
|
||||
ProbeTimeoutSeconds = 30,
|
||||
UseTls = true,
|
||||
ServerCertThumbprint = "0011223344556677889AABBCCDDEEFF001122334",
|
||||
};
|
||||
|
||||
var form = HistorianWonderwareDriverPage.WonderwareHistorianClientFormModel.FromRecord(original);
|
||||
var result = form.ToRecord();
|
||||
|
||||
result.Host.ShouldBe("historian-prod.zb.local");
|
||||
result.Port.ShouldBe(32570);
|
||||
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);
|
||||
result.UseTls.ShouldBeTrue();
|
||||
result.ServerCertThumbprint.ShouldBe("0011223344556677889AABBCCDDEEFF001122334");
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Pickers;
|
||||
|
||||
public sealed class HistorianWonderwareAddressBuilderTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("SysTimeHour", "Cyclic", 60, "SysTimeHour?mode=Cyclic&interval=60")]
|
||||
[InlineData("ReactorTemp", "Last", 1, "ReactorTemp?mode=Last&interval=1")]
|
||||
[InlineData("FlowRate", "Delta", 30, "FlowRate?mode=Delta&interval=30")]
|
||||
public void Build_Canonical(string tag, string mode, int interval, string expected)
|
||||
=> HistorianWonderwareAddressBuilder.Build(tag, mode, interval).ShouldBe(expected);
|
||||
|
||||
/// <summary>A tag name carrying query-reserved characters is percent-encoded so the produced
|
||||
/// address stays a well-formed query string (AdminUI-005). With "A&B?C" the '&' and '?'
|
||||
/// must not be read as a query separator / start, so they are escaped.</summary>
|
||||
[Fact]
|
||||
public void Build_escapes_reserved_characters_in_tag_name()
|
||||
{
|
||||
var result = HistorianWonderwareAddressBuilder.Build("A&B?C", "Cyclic", 60);
|
||||
|
||||
// The only literal '?' is the query separator the builder inserts; the only literal '&'
|
||||
// is the one between mode and interval. The reserved characters in the name are escaped.
|
||||
result.ShouldBe("A%26B%3FC?mode=Cyclic&interval=60");
|
||||
result.IndexOf('?').ShouldBe(result.IndexOf("?mode=", System.StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||
|
||||
public sealed class HistorianWonderwareTagConfigModelTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("{}")]
|
||||
public void FromJson_returns_defaults_for_empty_input(string? json)
|
||||
{
|
||||
var m = HistorianWonderwareTagConfigModel.FromJson(json);
|
||||
|
||||
m.FullName.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_reads_FullName()
|
||||
{
|
||||
var m = HistorianWonderwareTagConfigModel.FromJson(
|
||||
"""{"FullName":"Reactor1.Temp"}""");
|
||||
|
||||
m.FullName.ShouldBe("Reactor1.Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Round_trip_preserves_FullName()
|
||||
{
|
||||
var m = new HistorianWonderwareTagConfigModel { FullName = "Reactor1.Temp" };
|
||||
|
||||
var json = m.ToJson();
|
||||
var m2 = HistorianWonderwareTagConfigModel.FromJson(json);
|
||||
|
||||
m2.FullName.ShouldBe("Reactor1.Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_emits_PascalCase_FullName()
|
||||
{
|
||||
var m = new HistorianWonderwareTagConfigModel { FullName = "Reactor1.Temp" };
|
||||
|
||||
var json = m.ToJson();
|
||||
|
||||
// FullName is the composer/walker contract key — PascalCase, case-sensitive.
|
||||
json.ShouldContain("\"FullName\":\"Reactor1.Temp\"");
|
||||
json.ShouldNotContain("\"fullName\"", Case.Sensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_then_ToJson_preserves_unknown_keys()
|
||||
{
|
||||
var json = HistorianWonderwareTagConfigModel
|
||||
.FromJson("""{"FullName":"Reactor1.Temp","deadband":0.5}""")
|
||||
.ToJson();
|
||||
|
||||
json.ShouldContain("deadband");
|
||||
json.ShouldContain("0.5");
|
||||
// and the exposed field still round-trips
|
||||
json.ShouldContain("\"FullName\":\"Reactor1.Temp\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_then_ToJson_preserves_TagModal_merged_history_keys()
|
||||
{
|
||||
// The TagModal-merge seam writes isHistorized/historianTagname at the TagConfig root; this model
|
||||
// does NOT model them, so they must survive a load→save untouched as preserved unknown keys.
|
||||
var json = HistorianWonderwareTagConfigModel
|
||||
.FromJson("""{"FullName":"Reactor1.Temp","isHistorized":true,"historianTagname":"Reactor1.Temp.Override"}""")
|
||||
.ToJson();
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"Reactor1.Temp.Override\"");
|
||||
json.ShouldContain("\"FullName\":\"Reactor1.Temp\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_trims_FullName()
|
||||
{
|
||||
var json = new HistorianWonderwareTagConfigModel { FullName = " Reactor1.Temp " }.ToJson();
|
||||
|
||||
json.ShouldContain("\"FullName\":\"Reactor1.Temp\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_returns_error_when_FullName_blank()
|
||||
{
|
||||
new HistorianWonderwareTagConfigModel().Validate().ShouldNotBeNullOrEmpty();
|
||||
new HistorianWonderwareTagConfigModel { FullName = " " }.Validate().ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_returns_null_when_FullName_present()
|
||||
{
|
||||
new HistorianWonderwareTagConfigModel { FullName = "Reactor1.Temp" }.Validate().ShouldBeNull();
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ public sealed class TagConfigValidatorTests
|
||||
[InlineData("TwinCat")]
|
||||
[InlineData("Focas")]
|
||||
[InlineData("OpcUaClient")]
|
||||
[InlineData("Historian.Wonderware")]
|
||||
public void Required_field_blank_is_rejected(string driverType)
|
||||
{
|
||||
TagConfigValidator.Validate(driverType, "{}").ShouldNotBeNullOrEmpty();
|
||||
@@ -42,10 +41,6 @@ public sealed class TagConfigValidatorTests
|
||||
public void OpcUaClient_with_full_name_is_valid()
|
||||
=> TagConfigValidator.Validate("OpcUaClient", """{"FullName":"ns=2;s=Line3.Temp"}""").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void HistorianWonderware_with_full_name_is_valid()
|
||||
=> TagConfigValidator.Validate("Historian.Wonderware", """{"FullName":"Reactor1.Temp"}""").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void S7_with_address_is_valid()
|
||||
=> TagConfigValidator.Validate("S7", """{"address":"DB1.DBW0"}""").ShouldBeNull();
|
||||
|
||||
Reference in New Issue
Block a user