aafb9d4929
Wave-B EXEMPLAR. Retire the pre-declared/blob-parse tag model; the driver now builds its RawPath -> definition table from the artifact's RawTagEntry set via a pure mapper. - Contracts: rename ModbusEquipmentTagParser -> ModbusTagDefinitionFactory; TryParse(reference) -> FromTagConfig(tagConfig, rawPath) (def.Name = rawPath, no leading-brace heuristic); add inverse ToTagConfig(def) serializer; keep all strict-enum reads + guards; also read stringByteOrder/deadband/coalesceProhibited so a RawTagEntry round-trips the full authored def. Inspect() unchanged. - Options: Tags(IReadOnlyList<ModbusTagDefinition>) -> RawTags(IReadOnlyList<RawTagEntry>). - Driver: _tagsByName -> _tagsByRawPath (Ordinal); resolver byRawPath-only; build the table from RawTags at Initialize threading entry.WriteIdempotent onto each def; Discover/Teardown updated. - Factory: remove ModbusTagDto/BuildTag/ValidateStringLength + ConfigDto.Tags; bind RawTags from driver-config JSON. - CLI ModbusCommandBase.BuildOptions: serialise typed defs -> RawTagEntry via ToTagConfig. - Tests: migrate Tags= -> RawTags via ModbusRawTags.Entries helper; parser tests -> FromTagConfig(rawPath); factory String-length/enum tests re-seated on the mapper seam. - Propagated rename to ControlPlane EquipmentTagConfigInspector (outside Modbus projects). Modbus.Tests 315/315, Addressing.Tests 161/161, Cli.Tests 72/72 green. Docker-gated Driver.Modbus.IntegrationTests left untouched (won't compile against the new API; expected).
192 lines
7.3 KiB
C#
192 lines
7.3 KiB
C#
using CliFx.Attributes;
|
|
using CliFx.Infrastructure;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Tests;
|
|
|
|
/// <summary>
|
|
/// Covers <see cref="ModbusCommandBase.BuildOptions"/> — the pure, deterministic mapping
|
|
/// from the base's host / port / unit-id / timeout / disable-reconnect flags onto a
|
|
/// <c>ModbusDriverOptions</c>. The CLI is one-shot so the background connectivity probe
|
|
/// must be disabled; <c>AutoReconnect</c> is the inverse of <c>--disable-reconnect</c>.
|
|
/// Also covers the input-range validation introduced for Driver.Modbus.Cli-003.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class ModbusCommandBaseTests
|
|
{
|
|
// Test-only ModbusCommandBase concrete subclass that exposes the protected BuildOptions
|
|
// helper + ValidateEndpoint. The [Command] attribute is required by the CliFx analyzer
|
|
// (CliFx_CommandMustBeAnnotated) — this command is never registered with the CLI app
|
|
// but the analyzer rule fires for every ICommand implementor in the compilation.
|
|
[Command("noop-test", Description = "Test-only probe of ModbusCommandBase.BuildOptions.")]
|
|
private sealed class ProbeOnly : ModbusCommandBase
|
|
{
|
|
/// <inheritdoc />
|
|
public override ValueTask ExecuteAsync(IConsole console) => default;
|
|
|
|
/// <summary>Invokes BuildOptions with the given tags.</summary>
|
|
/// <param name="tags">The list of tag definitions to build options for.</param>
|
|
public ModbusDriverOptions Invoke(IReadOnlyList<ModbusTagDefinition> tags) => BuildOptions(tags);
|
|
|
|
/// <summary>Invokes ValidateEndpoint.</summary>
|
|
public void InvokeValidate() => ValidateEndpoint();
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions disables probe for one-shot CLI runs.</summary>
|
|
[Fact]
|
|
public void BuildOptions_disables_probe_for_one_shot_cli_runs()
|
|
{
|
|
var sut = new ProbeOnly { Host = "10.0.0.5" };
|
|
|
|
var options = sut.Invoke([]);
|
|
|
|
options.Probe.ShouldNotBeNull();
|
|
options.Probe.Enabled.ShouldBeFalse();
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions maps TimeoutMs to Timeout TimeSpan.</summary>
|
|
[Fact]
|
|
public void BuildOptions_maps_TimeoutMs_to_Timeout_TimeSpan()
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", TimeoutMs = 7500 };
|
|
|
|
var options = sut.Invoke([]);
|
|
|
|
options.Timeout.ShouldBe(TimeSpan.FromMilliseconds(7500));
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions AutoReconnect defaults to true when flag is unset.</summary>
|
|
[Fact]
|
|
public void BuildOptions_AutoReconnect_defaults_to_true_when_flag_unset()
|
|
{
|
|
var sut = new ProbeOnly { Host = "h" };
|
|
|
|
var options = sut.Invoke([]);
|
|
|
|
options.AutoReconnect.ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions AutoReconnect becomes false when disable reconnect flag is set.</summary>
|
|
[Fact]
|
|
public void BuildOptions_AutoReconnect_becomes_false_when_disable_reconnect_flag_set()
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", DisableAutoReconnect = true };
|
|
|
|
var options = sut.Invoke([]);
|
|
|
|
options.AutoReconnect.ShouldBeFalse();
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions flows host, port, and unit through correctly.</summary>
|
|
[Fact]
|
|
public void BuildOptions_flows_host_port_unit_through()
|
|
{
|
|
var sut = new ProbeOnly { Host = "plc.shop.local", Port = 5020, UnitId = 17, TimeoutMs = 3000 };
|
|
|
|
var options = sut.Invoke([]);
|
|
|
|
options.Host.ShouldBe("plc.shop.local");
|
|
options.Port.ShouldBe(5020);
|
|
options.UnitId.ShouldBe((byte)17);
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions serialises each typed tag into a RawTagEntry (RawPath = name).</summary>
|
|
[Fact]
|
|
public void BuildOptions_serialises_tags_into_raw_tag_entries()
|
|
{
|
|
var sut = new ProbeOnly { Host = "h" };
|
|
var tag = new ModbusTagDefinition(
|
|
Name: "T", Region: ModbusRegion.HoldingRegisters, Address: 0, DataType: ModbusDataType.UInt16);
|
|
|
|
var options = sut.Invoke([tag]);
|
|
|
|
options.RawTags.Count.ShouldBe(1);
|
|
options.RawTags[0].RawPath.ShouldBe("T");
|
|
// The blob the driver's factory reads round-trips back to the same definition.
|
|
ModbusTagDefinitionFactory.FromTagConfig(options.RawTags[0].TagConfig, "T", out var def).ShouldBeTrue();
|
|
def.Region.ShouldBe(ModbusRegion.HoldingRegisters);
|
|
def.DataType.ShouldBe(ModbusDataType.UInt16);
|
|
}
|
|
|
|
// --- Driver.Modbus.Cli-003: parse-time endpoint validation -------------------------------
|
|
|
|
/// <summary>Verifies that ValidateEndpoint rejects ports outside the range 1 to 65535.</summary>
|
|
/// <param name="port">The port value that should be rejected.</param>
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(65536)]
|
|
[InlineData(int.MinValue)]
|
|
[InlineData(int.MaxValue)]
|
|
public void ValidateEndpoint_rejects_port_outside_1_to_65535(int port)
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", Port = port };
|
|
|
|
Should.Throw<CliFx.Exceptions.CommandException>(() => sut.InvokeValidate());
|
|
}
|
|
|
|
/// <summary>Verifies that ValidateEndpoint accepts ports in the valid range.</summary>
|
|
/// <param name="port">The port value that should be accepted.</param>
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(502)]
|
|
[InlineData(65535)]
|
|
public void ValidateEndpoint_accepts_port_in_range(int port)
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", Port = port };
|
|
|
|
Should.NotThrow(() => sut.InvokeValidate());
|
|
}
|
|
|
|
/// <summary>Verifies that ValidateEndpoint rejects non-positive timeout values.</summary>
|
|
/// <param name="timeoutMs">The timeout value in milliseconds that should be rejected.</param>
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(-2000)]
|
|
public void ValidateEndpoint_rejects_non_positive_timeout(int timeoutMs)
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", TimeoutMs = timeoutMs };
|
|
|
|
Should.Throw<CliFx.Exceptions.CommandException>(() => sut.InvokeValidate());
|
|
}
|
|
|
|
/// <summary>Verifies that ValidateEndpoint rejects unit IDs outside the range 1 to 247.</summary>
|
|
/// <param name="unitId">The unit ID value that should be rejected.</param>
|
|
[Theory]
|
|
[InlineData(0)] // broadcast — disallowed for unicast read/write requests
|
|
[InlineData(248)]
|
|
[InlineData(255)]
|
|
public void ValidateEndpoint_rejects_unit_id_outside_1_to_247(byte unitId)
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", UnitId = unitId };
|
|
|
|
Should.Throw<CliFx.Exceptions.CommandException>(() => sut.InvokeValidate());
|
|
}
|
|
|
|
/// <summary>Verifies that ValidateEndpoint accepts unit IDs in the valid range.</summary>
|
|
/// <param name="unitId">The unit ID value that should be accepted.</param>
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(247)]
|
|
[InlineData(50)]
|
|
public void ValidateEndpoint_accepts_unit_id_in_range(byte unitId)
|
|
{
|
|
var sut = new ProbeOnly { Host = "h", UnitId = unitId };
|
|
|
|
Should.NotThrow(() => sut.InvokeValidate());
|
|
}
|
|
|
|
/// <summary>Verifies that ValidateEndpoint accepts default options.</summary>
|
|
[Fact]
|
|
public void ValidateEndpoint_accepts_default_options()
|
|
{
|
|
// Defaults: Port=502, UnitId=1, TimeoutMs=2000. All inside the valid ranges.
|
|
var sut = new ProbeOnly { Host = "h" };
|
|
|
|
Should.NotThrow(() => sut.InvokeValidate());
|
|
}
|
|
}
|