feat(adminui): isHistorized + historianTagname as first-class Tag fields
This commit is contained in:
+21
-35
@@ -16,66 +16,38 @@ public sealed class HistorianWonderwareTagConfigModelTests
|
||||
var m = HistorianWonderwareTagConfigModel.FromJson(json);
|
||||
|
||||
m.FullName.ShouldBe("");
|
||||
m.IsHistorized.ShouldBeFalse();
|
||||
m.HistorianTagname.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_reads_all_fields()
|
||||
public void FromJson_reads_FullName()
|
||||
{
|
||||
var m = HistorianWonderwareTagConfigModel.FromJson(
|
||||
"""{"FullName":"SysTimeSec","isHistorized":true,"historianTagname":"Reactor1.Temp"}""");
|
||||
"""{"FullName":"Reactor1.Temp"}""");
|
||||
|
||||
m.FullName.ShouldBe("SysTimeSec");
|
||||
m.IsHistorized.ShouldBeTrue();
|
||||
m.HistorianTagname.ShouldBe("Reactor1.Temp");
|
||||
m.FullName.ShouldBe("Reactor1.Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Round_trip_preserves_all_fields()
|
||||
public void Round_trip_preserves_FullName()
|
||||
{
|
||||
var m = new HistorianWonderwareTagConfigModel
|
||||
{
|
||||
FullName = "Reactor1.Temp",
|
||||
IsHistorized = true,
|
||||
HistorianTagname = "Reactor1.Temp.Override",
|
||||
};
|
||||
var m = new HistorianWonderwareTagConfigModel { FullName = "Reactor1.Temp" };
|
||||
|
||||
var json = m.ToJson();
|
||||
var m2 = HistorianWonderwareTagConfigModel.FromJson(json);
|
||||
|
||||
m2.FullName.ShouldBe("Reactor1.Temp");
|
||||
m2.IsHistorized.ShouldBeTrue();
|
||||
m2.HistorianTagname.ShouldBe("Reactor1.Temp.Override");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_emits_PascalCase_FullName_and_camelCase_history_keys()
|
||||
public void ToJson_emits_PascalCase_FullName()
|
||||
{
|
||||
var m = new HistorianWonderwareTagConfigModel
|
||||
{
|
||||
FullName = "Reactor1.Temp",
|
||||
IsHistorized = true,
|
||||
HistorianTagname = "Reactor1.Temp.Override",
|
||||
};
|
||||
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);
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"Reactor1.Temp.Override\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_omits_history_keys_when_default()
|
||||
{
|
||||
var json = new HistorianWonderwareTagConfigModel { FullName = "Reactor1.Temp" }.ToJson();
|
||||
|
||||
json.ShouldContain("\"FullName\":\"Reactor1.Temp\"");
|
||||
json.ShouldNotContain("isHistorized");
|
||||
json.ShouldNotContain("historianTagname");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -91,6 +63,20 @@ public sealed class HistorianWonderwareTagConfigModelTests
|
||||
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()
|
||||
{
|
||||
|
||||
+20
-34
@@ -16,66 +16,38 @@ public sealed class OpcUaClientTagConfigModelTests
|
||||
var m = OpcUaClientTagConfigModel.FromJson(json);
|
||||
|
||||
m.FullName.ShouldBe("");
|
||||
m.IsHistorized.ShouldBeFalse();
|
||||
m.HistorianTagname.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromJson_reads_all_fields()
|
||||
public void FromJson_reads_FullName()
|
||||
{
|
||||
var m = OpcUaClientTagConfigModel.FromJson(
|
||||
"""{"FullName":"nsu=urn:srv;s=Line3.Temp","isHistorized":true,"historianTagname":"Line3_Temp"}""");
|
||||
"""{"FullName":"nsu=urn:srv;s=Line3.Temp"}""");
|
||||
|
||||
m.FullName.ShouldBe("nsu=urn:srv;s=Line3.Temp");
|
||||
m.IsHistorized.ShouldBeTrue();
|
||||
m.HistorianTagname.ShouldBe("Line3_Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Round_trip_preserves_all_fields()
|
||||
public void Round_trip_preserves_FullName()
|
||||
{
|
||||
var m = new OpcUaClientTagConfigModel
|
||||
{
|
||||
FullName = "ns=2;s=Line3.Temp",
|
||||
IsHistorized = true,
|
||||
HistorianTagname = "Line3_Temp",
|
||||
};
|
||||
var m = new OpcUaClientTagConfigModel { FullName = "ns=2;s=Line3.Temp" };
|
||||
|
||||
var json = m.ToJson();
|
||||
var m2 = OpcUaClientTagConfigModel.FromJson(json);
|
||||
|
||||
m2.FullName.ShouldBe("ns=2;s=Line3.Temp");
|
||||
m2.IsHistorized.ShouldBeTrue();
|
||||
m2.HistorianTagname.ShouldBe("Line3_Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_emits_PascalCase_FullName_and_camelCase_history_keys()
|
||||
public void ToJson_emits_PascalCase_FullName()
|
||||
{
|
||||
var m = new OpcUaClientTagConfigModel
|
||||
{
|
||||
FullName = "ns=2;s=Line3.Temp",
|
||||
IsHistorized = true,
|
||||
HistorianTagname = "Line3_Temp",
|
||||
};
|
||||
var m = new OpcUaClientTagConfigModel { FullName = "ns=2;s=Line3.Temp" };
|
||||
|
||||
var json = m.ToJson();
|
||||
|
||||
// FullName is the composer/walker contract key — PascalCase, case-sensitive.
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=Line3.Temp\"");
|
||||
json.ShouldNotContain("\"fullName\"", Case.Sensitive);
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_omits_history_keys_when_default()
|
||||
{
|
||||
var json = new OpcUaClientTagConfigModel { FullName = "ns=2;s=X" }.ToJson();
|
||||
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
|
||||
json.ShouldNotContain("isHistorized");
|
||||
json.ShouldNotContain("historianTagname");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -91,6 +63,20 @@ public sealed class OpcUaClientTagConfigModelTests
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
|
||||
}
|
||||
|
||||
[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 = OpcUaClientTagConfigModel
|
||||
.FromJson("""{"FullName":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""")
|
||||
.ToJson();
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_trims_FullName()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the driver-agnostic <see cref="TagHistorizeConfig"/> merge helper — the pure seam the
|
||||
/// TagModal uses to read/write the root <c>isHistorized</c> / <c>historianTagname</c> history keys on the
|
||||
/// canonical TagConfig JSON WITHOUT disturbing the driver's own (typed-editor) fields. Both this helper and
|
||||
/// every typed editor preserve unknown keys, so they compose over the same JSON blob.
|
||||
/// </summary>
|
||||
public sealed class TagHistorizeConfigTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("{}")]
|
||||
[InlineData("not json")]
|
||||
public void Read_returns_defaults_for_empty_or_malformed(string? json)
|
||||
{
|
||||
var h = TagHistorizeConfig.Read(json);
|
||||
|
||||
h.IsHistorized.ShouldBeFalse();
|
||||
h.HistorianTagname.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Read_reads_both_keys()
|
||||
{
|
||||
var h = TagHistorizeConfig.Read("""{"FullName":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""");
|
||||
|
||||
h.IsHistorized.ShouldBeTrue();
|
||||
h.HistorianTagname.ShouldBe("Line3_Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_true_with_tagname_writes_both_camelCase_keys()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set("""{"FullName":"ns=2;s=X"}""", isHistorized: true, historianTagname: "Line3_Temp");
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
|
||||
// The driver field is untouched.
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_true_round_trips_through_Read()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set("{}", isHistorized: true, historianTagname: "Reactor1.Temp");
|
||||
|
||||
var h = TagHistorizeConfig.Read(json);
|
||||
h.IsHistorized.ShouldBeTrue();
|
||||
h.HistorianTagname.ShouldBe("Reactor1.Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_false_removes_isHistorized_key()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set(
|
||||
"""{"FullName":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""",
|
||||
isHistorized: false, historianTagname: "Line3_Temp");
|
||||
|
||||
json.ShouldNotContain("isHistorized");
|
||||
json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_blank_tagname_removes_historianTagname_key()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set(
|
||||
"""{"isHistorized":true,"historianTagname":"Old"}""",
|
||||
isHistorized: true, historianTagname: " ");
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldNotContain("historianTagname");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_null_tagname_removes_historianTagname_key()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set(
|
||||
"""{"isHistorized":true,"historianTagname":"Old"}""",
|
||||
isHistorized: true, historianTagname: null);
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldNotContain("historianTagname");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_trims_historianTagname()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set("{}", isHistorized: true, historianTagname: " Line3_Temp ");
|
||||
|
||||
json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_preserves_driver_specific_unknown_keys()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set(
|
||||
"""{"register":40001,"scale":0.1,"nested":{"a":1}}""",
|
||||
isHistorized: true, historianTagname: "T");
|
||||
|
||||
json.ShouldContain("register");
|
||||
json.ShouldContain("40001");
|
||||
json.ShouldContain("scale");
|
||||
json.ShouldContain("0.1");
|
||||
json.ShouldContain("nested");
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
json.ShouldContain("\"historianTagname\":\"T\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_on_malformed_input_starts_from_empty_object()
|
||||
{
|
||||
var json = TagHistorizeConfig.Set("not json", isHistorized: true, historianTagname: null);
|
||||
|
||||
json.ShouldContain("\"isHistorized\":true");
|
||||
// No crash, valid object emitted.
|
||||
json.ShouldStartWith("{");
|
||||
json.ShouldEndWith("}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user