using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests; /// /// CONV-4 (Modbus leg) — Modbus keys per-host resilience per SLAVE UNIT /// (host:port/unitN). An equipment tag must carry its own unitId so multi-unit /// equipment tags key their own breaker; must resolve /// the equipment ref through the resolver rather than only matching authored names. Scope: /// unitId ONLY — the parser's other strictness gaps are R2-11's. /// [Trait("Category", "Unit")] public sealed class ModbusUnitIdResolveHostTests { private sealed class NoopTransport : IModbusTransport { public Task ConnectAsync(CancellationToken ct) => Task.CompletedTask; public Task SendAsync(byte unitId, byte[] pdu, CancellationToken ct) => Task.FromResult(new byte[] { 0x03, 0x00 }); public ValueTask DisposeAsync() => ValueTask.CompletedTask; } private static ModbusDriver NewDriver() => new( new ModbusDriverOptions { Host = "10.0.0.1", Port = 502, UnitId = 1, Probe = new ModbusProbeOptions { Enabled = false } }, "modbus-1", _ => new NoopTransport()); /// The parser reads an optional unitId into the transient definition. [Fact] public void Parser_reads_optional_unitId() { var json = """{"region":"HoldingRegisters","address":0,"dataType":"Int16","unitId":7}"""; ModbusEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue(); def.UnitId.ShouldBe((byte)7); } /// An absent unitId leaves the definition on the driver-level default (null override). [Fact] public void Parser_absent_unitId_is_null() { var json = """{"region":"HoldingRegisters","address":0,"dataType":"Int16"}"""; ModbusEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue(); def.UnitId.ShouldBeNull(); } /// An equipment ref carrying a unitId keys its own per-unit host name. [Fact] public void EquipmentTag_WithUnitId_ResolvesPerUnitHostName() { var drv = NewDriver(); var json = """{"region":"HoldingRegisters","address":0,"dataType":"Int16","unitId":7}"""; drv.ResolveHost(json).ShouldBe("10.0.0.1:502/unit7"); } /// An equipment ref without a unitId keys the driver-level unit host name. [Fact] public void EquipmentTag_WithoutUnitId_KeysDriverDefaultUnit() { var drv = NewDriver(); var json = """{"region":"HoldingRegisters","address":0,"dataType":"Int16"}"""; drv.ResolveHost(json).ShouldBe("10.0.0.1:502/unit1"); } }