Merge R2-11 TagConfig consolidation (arch-review round 2) [PR #434]
v2-ci / build (push) Successful in 3m55s
v2-ci / unit-tests (push) Failing after 8m14s

Findings 01/C-1, 01/P-1 (single TagConfigIntent.Parse in Commons, 4 copies
delegate, parse-once) + 05 CONV-2/UNDER-1/UNDER-6 (shared strict TagConfigJson
readers, per-driver Inspect() + writable key, FOCAS capability pre-flight,
Modbus probe timeoutMs, deploy-gate Warn|Error). Phase C runtime-strict flip
deferred. T22/T24 deferred. Auto-merged AddressSpaceApplier.cs + DriverHostActor.cs
with R2-04/R2-10; verified OpcUaServer.Tests 286/286 + Runtime.Tests 396/396.
Build clean.
This commit is contained in:
Joseph Doherty
2026-07-13 11:29:32 -04:00
74 changed files with 2228 additions and 682 deletions
@@ -0,0 +1,88 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>
/// R2-11 (05/UNDER-6): the new optional <c>writable</c> key round-trips through the driver-typed tag
/// config models — a value the editors' new checkbox authors. Absent stays absent (so existing configs
/// are untouched); an explicit <c>false</c> round-trips; unknown keys are preserved.
/// </summary>
public sealed class TagConfigModelWritableTests
{
[Fact]
public void Modbus_absent_writable_stays_omitted()
{
var m = ModbusTagConfigModel.FromJson("{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Int16\"}");
m.Writable.ShouldBeNull();
m.ToJson().ShouldNotContain("writable");
}
[Fact]
public void Modbus_explicit_false_round_trips()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1,\"writable\":false}");
m.Writable.ShouldBe(false);
var json = ModbusTagConfigModel.FromJson(m.ToJson());
json.Writable.ShouldBe(false);
}
[Fact]
public void Modbus_setting_writable_true_writes_the_key()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1}");
m.Writable = true;
ModbusTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(true);
}
[Fact]
public void Writable_preserves_unknown_keys()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1,\"writable\":false,\"foo\":\"bar\"}");
m.ToJson().ShouldContain("foo");
m.ToJson().ShouldContain("bar");
}
[Fact]
public void S7_writable_round_trips()
{
var m = S7TagConfigModel.FromJson("{\"address\":\"DB1.DBW0\",\"writable\":false}");
m.Writable.ShouldBe(false);
S7TagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void AbCip_writable_round_trips()
{
var m = AbCipTagConfigModel.FromJson("{\"tagPath\":\"Motor.Speed\",\"writable\":false}");
m.Writable.ShouldBe(false);
AbCipTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void AbLegacy_writable_round_trips()
{
var m = AbLegacyTagConfigModel.FromJson("{\"address\":\"N7:0\",\"writable\":false}");
m.Writable.ShouldBe(false);
AbLegacyTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void TwinCAT_writable_round_trips()
{
var m = TwinCATTagConfigModel.FromJson("{\"symbolPath\":\"MAIN.x\",\"writable\":false}");
m.Writable.ShouldBe(false);
TwinCATTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void Absent_writable_omitted_across_models()
{
ModbusTagConfigModel.FromJson("{\"address\":1}").ToJson().ShouldNotContain("writable");
S7TagConfigModel.FromJson("{\"address\":\"DB1.DBW0\"}").ToJson().ShouldNotContain("writable");
AbCipTagConfigModel.FromJson("{\"tagPath\":\"M.S\"}").ToJson().ShouldNotContain("writable");
AbLegacyTagConfigModel.FromJson("{\"address\":\"N7:0\"}").ToJson().ShouldNotContain("writable");
TwinCATTagConfigModel.FromJson("{\"symbolPath\":\"MAIN.x\"}").ToJson().ShouldNotContain("writable");
}
}