64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
256 lines
9.2 KiB
C#
256 lines
9.2 KiB
C#
using System.Reflection;
|
|
using CliFx.Attributes;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Commands;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Tests;
|
|
|
|
/// <summary>
|
|
/// Covers <see cref="TwinCATCommandBase"/> / <see cref="TwinCATTagCommandBase"/> wiring:
|
|
/// the canonical gateway string, the driver instance id, the BuildOptions field projection
|
|
/// (Driver.TwinCAT.Cli-006), and the up-front range validation guards
|
|
/// (Driver.TwinCAT.Cli-001).
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class TwinCATCommandBaseTests
|
|
{
|
|
/// <summary>Verifies that the gateway string uses canonical ADS scheme with port.</summary>
|
|
[Fact]
|
|
public void Gateway_uses_canonical_ads_scheme_with_port()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "192.168.1.40.1.1",
|
|
AmsPort = 851,
|
|
SymbolPath = "MAIN.bRunning",
|
|
};
|
|
cmd.GatewayForTest.ShouldBe("ads://192.168.1.40.1.1:851");
|
|
}
|
|
|
|
/// <summary>Verifies that the gateway string round-trips through TwinCATAmsAddress.TryParse.</summary>
|
|
[Fact]
|
|
public void Gateway_round_trips_through_TwinCATAmsAddress_TryParse()
|
|
{
|
|
// Driver.TwinCAT.Cli-006: a regression in the Gateway string breaks every command
|
|
// because the driver's TwinCATAmsAddress.TryParse refuses anything not shaped
|
|
// ads://{netId}:{port}.
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "5.23.91.23.1.1",
|
|
AmsPort = 852,
|
|
SymbolPath = "MAIN.x",
|
|
};
|
|
var parsed = TwinCAT.TwinCATAmsAddress.TryParse(cmd.GatewayForTest);
|
|
parsed.ShouldNotBeNull();
|
|
parsed!.NetId.ShouldBe("5.23.91.23.1.1");
|
|
parsed.Port.ShouldBe(852);
|
|
}
|
|
|
|
/// <summary>Verifies that the driver instance ID includes the AMS target.</summary>
|
|
[Fact]
|
|
public void DriverInstanceId_includes_ams_target()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
AmsPort = 851,
|
|
SymbolPath = "MAIN.x",
|
|
};
|
|
cmd.DriverInstanceIdForTest.ShouldBe("twincat-cli-127.0.0.1.1.1:851");
|
|
}
|
|
|
|
/// <summary>Verifies that timeout is a projection of TimeoutMs and initialization is a no-op.</summary>
|
|
[Fact]
|
|
public void Timeout_is_projection_of_TimeoutMs_and_init_is_noop()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
TimeoutMs = 7777,
|
|
SymbolPath = "MAIN.x",
|
|
};
|
|
cmd.Timeout.ShouldBe(TimeSpan.FromMilliseconds(7777));
|
|
}
|
|
|
|
/// <summary>Verifies that BuildOptions wires device tags, timeout, and disables probe.</summary>
|
|
[Fact]
|
|
public void BuildOptions_wires_device_tags_timeout_and_disables_probe()
|
|
{
|
|
// Driver.TwinCAT.Cli-006: cover the property-by-property wiring that the four runtime
|
|
// commands depend on. Probe must be disabled (CLI is one-shot — the probe loop would
|
|
// race the operator's own reads) and controller-browse must stay off.
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "10.0.0.1.1.1",
|
|
AmsPort = 851,
|
|
TimeoutMs = 4321,
|
|
SymbolPath = "MAIN.x",
|
|
};
|
|
var tag = new TwinCAT.TwinCATTagDefinition(
|
|
Name: "n1",
|
|
DeviceHostAddress: cmd.GatewayForTest,
|
|
SymbolPath: "MAIN.x",
|
|
DataType: TwinCAT.TwinCATDataType.DInt,
|
|
Writable: false);
|
|
|
|
var options = cmd.BuildOptionsForTest([tag]);
|
|
|
|
options.Devices.Count.ShouldBe(1);
|
|
options.Devices[0].HostAddress.ShouldBe("ads://10.0.0.1.1.1:851");
|
|
options.Devices[0].DeviceName.ShouldBe("cli-10.0.0.1.1.1:851");
|
|
options.Tags.ShouldBe([tag]);
|
|
options.Timeout.ShouldBe(TimeSpan.FromMilliseconds(4321));
|
|
options.Probe.Enabled.ShouldBeFalse();
|
|
options.EnableControllerBrowse.ShouldBeFalse();
|
|
// Default UseNativeNotifications = true (no --poll-only).
|
|
options.UseNativeNotifications.ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>Verifies that the PollOnly flag flips UseNativeNotifications off.</summary>
|
|
[Fact]
|
|
public void BuildOptions_PollOnly_flips_UseNativeNotifications_off()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "10.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
PollOnly = true,
|
|
};
|
|
cmd.BuildOptionsForTest([]).UseNativeNotifications.ShouldBeFalse();
|
|
}
|
|
|
|
// ---- Driver.TwinCAT.Cli-001 (range validation) ----
|
|
|
|
/// <summary>Verifies that validation rejects zero timeout.</summary>
|
|
[Fact]
|
|
public void Validate_rejects_zero_timeout()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
TimeoutMs = 0,
|
|
};
|
|
var ex = Should.Throw<CliFx.Exceptions.CommandException>(() => cmd.ValidateForTest());
|
|
ex.Message.ShouldContain("--timeout-ms");
|
|
}
|
|
|
|
/// <summary>Verifies that validation rejects negative timeout.</summary>
|
|
[Fact]
|
|
public void Validate_rejects_negative_timeout()
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
TimeoutMs = -1,
|
|
};
|
|
Should.Throw<CliFx.Exceptions.CommandException>(() => cmd.ValidateForTest());
|
|
}
|
|
|
|
/// <summary>Verifies that validation rejects out-of-range AMS port values.</summary>
|
|
/// <param name="port">The out-of-range AMS port value to test.</param>
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(65536)]
|
|
[InlineData(100000)]
|
|
public void Validate_rejects_out_of_range_ams_port(int port)
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
AmsPort = port,
|
|
};
|
|
var ex = Should.Throw<CliFx.Exceptions.CommandException>(() => cmd.ValidateForTest());
|
|
ex.Message.ShouldContain("--ams-port");
|
|
}
|
|
|
|
/// <summary>Verifies that validation accepts in-range AMS port values.</summary>
|
|
/// <param name="port">The valid AMS port value to test.</param>
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(801)]
|
|
[InlineData(851)]
|
|
[InlineData(65535)]
|
|
public void Validate_accepts_in_range_ams_port(int port)
|
|
{
|
|
var cmd = new ProbeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
AmsPort = port,
|
|
};
|
|
Should.NotThrow(() => cmd.ValidateForTest());
|
|
}
|
|
|
|
/// <summary>Verifies that SubscribeCommand validation rejects zero interval.</summary>
|
|
[Fact]
|
|
public void SubscribeCommand_validate_rejects_zero_interval()
|
|
{
|
|
var cmd = new SubscribeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
IntervalMs = 0,
|
|
};
|
|
var ex = Should.Throw<CliFx.Exceptions.CommandException>(() => cmd.ValidateForTest());
|
|
ex.Message.ShouldContain("--interval-ms");
|
|
}
|
|
|
|
/// <summary>Verifies that SubscribeCommand validation rejects negative interval.</summary>
|
|
[Fact]
|
|
public void SubscribeCommand_validate_rejects_negative_interval()
|
|
{
|
|
var cmd = new SubscribeCommand
|
|
{
|
|
AmsNetId = "127.0.0.1.1.1",
|
|
SymbolPath = "MAIN.x",
|
|
IntervalMs = -100,
|
|
};
|
|
Should.Throw<CliFx.Exceptions.CommandException>(() => cmd.ValidateForTest());
|
|
}
|
|
|
|
// ---- Driver.TwinCAT.Cli-004 (PollOnly off BrowseCommand surface) ----
|
|
|
|
/// <summary>Verifies that BrowseCommand does not expose the poll-only flag.</summary>
|
|
[Fact]
|
|
public void BrowseCommand_does_not_expose_poll_only_flag()
|
|
{
|
|
// Driver.TwinCAT.Cli-004: the flag has no observable effect on browse — surfacing it
|
|
// misleads users. After the refactor, PollOnly lives on an intermediate base shared
|
|
// only by the commands that actually consume native ADS notifications.
|
|
var props = typeof(BrowseCommand)
|
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
props.ShouldNotContain(p => p.Name == "PollOnly");
|
|
}
|
|
|
|
/// <summary>Verifies that ProbeCommand still exposes the poll-only flag.</summary>
|
|
[Fact]
|
|
public void ProbeCommand_still_exposes_poll_only_flag()
|
|
{
|
|
// Probe / Read / Write / Subscribe all build TwinCATDriverOptions and so still take
|
|
// the --poll-only toggle.
|
|
var props = typeof(ProbeCommand)
|
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
props.ShouldContain(p => p.Name == "PollOnly");
|
|
}
|
|
|
|
// ---- Driver.TwinCAT.Cli-005 (probe --type short alias) ----
|
|
|
|
/// <summary>Verifies that ProbeCommand type option carries the short alias 't'.</summary>
|
|
[Fact]
|
|
public void ProbeCommand_type_option_carries_short_alias_t()
|
|
{
|
|
// Driver.TwinCAT.Cli-005: --type on read/write/subscribe takes the -t short alias;
|
|
// probe must match so muscle memory works the same way across all four verbs.
|
|
var dataTypeProp = typeof(ProbeCommand).GetProperty("DataType");
|
|
dataTypeProp.ShouldNotBeNull();
|
|
var attr = dataTypeProp!.GetCustomAttribute<CommandOptionAttribute>();
|
|
attr.ShouldNotBeNull();
|
|
attr!.ShortName.ShouldBe('t');
|
|
}
|
|
}
|