Merge hw5-660 (#660)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 07:15:30 -04:00
5 changed files with 78 additions and 3 deletions
@@ -5,6 +5,7 @@
using System.Net;
using System.Net.Sockets;
using Shouldly;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
@@ -147,4 +148,56 @@ public sealed class AbLegacyDriverProbeTests
$"Expected 'timed out' or 'Connect failed' but got: {result.Message}");
result.Latency.ShouldBeNull();
}
// ---- #660: Test Connect normalises the probe address exactly like the driver's probe loop ----
private static async Task<(DriverProbeResult Result, string? SeenName)> ProbeWithFakeAsync(string family, string? probeAddress)
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
try
{
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
var factory = new FakeAbLegacyTagFactory();
var probe = new AbLegacyDriverProbe(factory);
var addr = probeAddress is null ? "" : $",\"probe\":{{\"probeAddress\":\"{probeAddress}\"}}";
var json = $"{{\"devices\":[{{\"hostAddress\":\"ab://127.0.0.1:{port}/1,0\",\"plcFamily\":\"{family}\"}}]{addr}}}";
var result = await probe.ProbeAsync(json, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken);
return (result, factory.Tags.Keys.SingleOrDefault());
}
finally { listener.Stop(); }
}
[Theory]
[InlineData("Plc5", "I:017", "I:15")]
[InlineData("Plc5", "O:010/07", "O:8/7")]
[InlineData("LogixPccc", "I:017", "I:17")]
[InlineData("Slc500", "I:017", "I:17")]
[InlineData("MicroLogix", "I:017", "I:17")]
[InlineData("Slc500", "N7:0", "N7:0")]
[InlineData("Slc500", null, "S:0")]
public async Task Probe_address_reaches_the_factory_family_normalised(string family, string? configured, string expected)
{
var (result, seen) = await ProbeWithFakeAsync(family, configured);
result.Ok.ShouldBeTrue(result.Message);
seen.ShouldBe(expected);
}
[Fact]
public async Task Normalised_default_probe_then_composes_with_the_libplctag_translation()
{
var (_, seen) = await ProbeWithFakeAsync("Plc5", "I:017");
LibplctagLegacyName.Translate(seen!).ShouldBe("I1:15");
var (_, status) = await ProbeWithFakeAsync("Slc500", null);
LibplctagLegacyName.Translate(status!).ShouldBe("S2:0");
}
[Fact]
public async Task Unparseable_probe_address_is_a_clean_failure_not_an_exception()
{
var (result, seen) = await ProbeWithFakeAsync("Slc500", "not-an-address");
result.Ok.ShouldBeFalse();
result.Message!.ShouldContain("not-an-address");
seen.ShouldBeNull();
}
}