diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Contracts/MTConnectDriverOptions.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Contracts/MTConnectDriverOptions.cs index 8b9a9031..0b1dce3b 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Contracts/MTConnectDriverOptions.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Contracts/MTConnectDriverOptions.cs @@ -54,6 +54,21 @@ public sealed class MTConnectDriverOptions /// public int HeartbeatMs { get; init; } = 10000; + /// + /// The authored tags this driver instance serves — one + /// per tag, keyed by the DataItem id it binds + /// (). The factory config DTO carries these and + /// builds them into the options; the driver indexes them to know each tag's target + /// when coercing an Agent observation + /// (whose wire form is always text) into a published value. + /// + /// Defaults to empty, never null: a driver instance may legitimately be + /// authored with no tags yet, and a null collection here would surface as a + /// NullReferenceException at deploy time from operator-authored config. + /// + /// + public IReadOnlyList Tags { get; init; } = []; + /// /// Background connectivity-probe settings. When /// is true the driver periodically issues a cheap /probe request and raises diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests/MTConnectDriverOptionsTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests/MTConnectDriverOptionsTests.cs index 0b77db18..91d1c4ca 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests/MTConnectDriverOptionsTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests/MTConnectDriverOptionsTests.cs @@ -56,6 +56,47 @@ public sealed class MTConnectDriverOptionsTests reconnect.BackoffMultiplier.ShouldBeGreaterThan(1.0); } + /// + /// defaults to an empty collection, never + /// null — a null collection here is an operator-authorable NullReferenceException at + /// deploy, since a driver instance authored with no tags binds this default. + /// + [Fact] + public void Options_tags_default_to_empty_and_are_never_null() + { + var o = new MTConnectDriverOptions { AgentUri = "http://agent:5000" }; + + o.Tags.ShouldNotBeNull(); + o.Tags.ShouldBeEmpty(); + } + + /// + /// A populated options object carries the authored tag definitions through unchanged — + /// the observation index (Task 8) coerces each present observation to its tag's + /// DriverDataType, so the definitions must reach the driver from the factory config. + /// + [Fact] + public void Options_round_trip_the_authored_tag_definitions() + { + var pos = new MTConnectTagDefinition( + FullName: "dev1_pos", + DriverDataType: ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float64, + MtCategory: "SAMPLE", + MtType: "POSITION", + Units: "MILLIMETER"); + var partCount = new MTConnectTagDefinition( + FullName: "dev1_partcount", + DriverDataType: ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int64, + MtCategory: "EVENT", + MtType: "PART_COUNT"); + + var o = new MTConnectDriverOptions { AgentUri = "http://agent:5000", Tags = [pos, partCount] }; + + o.Tags.Count.ShouldBe(2); + o.Tags[0].ShouldBe(pos); + o.Tags[1].ShouldBe(partCount); + } + /// MTConnectTagDefinition round-trips its positional fields, including MTConnect metadata. [Fact] public void TagDefinition_carries_dataItemId_and_mtconnect_metadata()