using System; using System.Collections.Generic; using CliFx.Attributes; using CliFx.Infrastructure; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Cli.Tests; /// /// Locks in : probe disabled, /// device shape populated from --gateway + --plc-type, tag list /// forwarded verbatim, and timeout propagated from --timeout-ms. A /// regression here silently changes every AbLegacy CLI command's behaviour, so /// covering it explicitly closes the test gap called out by finding /// Driver.AbLegacy.Cli-007. /// [Trait("Category", "Unit")] public sealed class BuildOptionsTests { // Concrete subclass needed because AbLegacyCommandBase is abstract. Exposes the // protected BuildOptions via a public surface for the test. // [Command] satisfies CliFx's analyzer (ICommand subtypes must be annotated); // we never run it through CliFx, only invoke Build() directly. [Command("test-build-options")] private sealed class TestCommand : AbLegacyCommandBase { public AbLegacyDriverOptions Build(IReadOnlyList tags) => BuildOptions(tags); public override System.Threading.Tasks.ValueTask ExecuteAsync(IConsole console) => throw new NotSupportedException("TestCommand is for BuildOptions inspection only."); } private static readonly IReadOnlyList SampleTags = [ new AbLegacyTagDefinition( Name: "N7:0:Int", DeviceHostAddress: "ab://192.168.1.20/1,0", Address: "N7:0", DataType: AbLegacyDataType.Int, Writable: false), new AbLegacyTagDefinition( Name: "F8:0:Float", DeviceHostAddress: "ab://192.168.1.20/1,0", Address: "F8:0", DataType: AbLegacyDataType.Float, Writable: true), ]; [Fact] public void BuildOptions_disables_probe_for_cli_oneshot_runs() { var cmd = new TestCommand { Gateway = "ab://192.168.1.20/1,0", PlcType = AbLegacyPlcFamily.Slc500, TimeoutMs = 5000, }; var options = cmd.Build(SampleTags); options.Probe.ShouldNotBeNull(); options.Probe.Enabled.ShouldBeFalse( "CLI commands are one-shot; the background probe loop is unwanted overhead."); } [Fact] public void BuildOptions_populates_single_device_from_gateway_and_plc_type() { var cmd = new TestCommand { Gateway = "ab://10.0.0.5/1,0", PlcType = AbLegacyPlcFamily.MicroLogix, TimeoutMs = 5000, }; var options = cmd.Build(SampleTags); options.Devices.Count.ShouldBe(1); options.Devices[0].HostAddress.ShouldBe("ab://10.0.0.5/1,0"); options.Devices[0].PlcFamily.ShouldBe(AbLegacyPlcFamily.MicroLogix); options.Devices[0].DeviceName.ShouldBe("cli-MicroLogix"); } [Fact] public void BuildOptions_forwards_tag_list_verbatim() { var cmd = new TestCommand { Gateway = "ab://192.168.1.20/1,0", PlcType = AbLegacyPlcFamily.Slc500, TimeoutMs = 5000, }; var options = cmd.Build(SampleTags); options.Tags.ShouldBe(SampleTags); } [Fact] public void BuildOptions_propagates_timeout_ms() { var cmd = new TestCommand { Gateway = "ab://192.168.1.20/1,0", PlcType = AbLegacyPlcFamily.Slc500, TimeoutMs = 7500, }; var options = cmd.Build(SampleTags); options.Timeout.ShouldBe(TimeSpan.FromMilliseconds(7500)); } [Fact] public void BuildOptions_with_empty_tag_list_yields_empty_tags_collection() { var cmd = new TestCommand { Gateway = "ab://192.168.1.20/1,0", PlcType = AbLegacyPlcFamily.Plc5, TimeoutMs = 5000, }; var options = cmd.Build([]); options.Tags.ShouldBeEmpty(); options.Devices.Count.ShouldBe(1); options.Devices[0].PlcFamily.ShouldBe(AbLegacyPlcFamily.Plc5); } }