Merge #608/#602/#596: stale WaveC TODOs resolved, FOCAS/TwinCAT factory logging, Modbus health asymmetry documented, AbCip browsePageDelayMs field, Device1 by-design

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 06:53:59 -04:00
17 changed files with 192 additions and 50 deletions
@@ -306,4 +306,26 @@ public sealed class AbCipDriverPageFormSerializationTests
opts.ShouldNotBeNull();
opts.Devices[0].HostAddress.ShouldBe("ab://10.0.0.1/1,0");
}
// ---- #596: browsePageDelayMs is form-authorable, reaches the factory, and unknown keys survive ----
[Fact]
public void BrowsePageDelayMs_is_loaded_edited_and_bound_by_the_factory_without_dropping_unknown_keys()
{
var original = """{"browsePageDelayMs":40,"futureOption":"keep-me","devices":[{"hostAddress":"ab://10.0.0.1/1,0"}]}""";
var loaded = AbCipDriverForm.TryDeserialize(original)!;
var form = AbCipDriverForm.FormModel.FromJson(original, loaded);
form.BrowsePageDelayMs.ShouldBe(40); // the form SEES the factory-keyed value
var untouched = AbCipDriverForm.Serialize(form.ToOptions(loaded.Devices), original);
ZB.MOM.WW.OtOpcUa.Driver.AbCip.AbCipDriverFactoryExtensions.ParseOptions("t", untouched)
.BrowsePageDelay.ShouldBe(TimeSpan.FromMilliseconds(40));
form.BrowsePageDelayMs = 125;
var saved = AbCipDriverForm.Serialize(form.ToOptions(loaded.Devices), original);
ZB.MOM.WW.OtOpcUa.Driver.AbCip.AbCipDriverFactoryExtensions.ParseOptions("t", saved)
.BrowsePageDelay.ShouldBe(TimeSpan.FromMilliseconds(125));
saved.ShouldContain("futureOption");
System.Text.Json.Nodes.JsonNode.Parse(saved)!.AsObject().ContainsKey("browsePageDelay").ShouldBeFalse();
}
}