using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests;
///
/// End-to-end smoke tests that exercise the real transport stack (libplctag and native, via ) against a running
/// ab_server. Skipped when the binary isn't on PATH ().
/// Parametrized over so one test file covers every family
/// (ControlLogix / CompactLogix / Micro800 / GuardLogix).
///
[Trait("Category", "Integration")]
[Trait("Requires", "AbServer")]
public sealed class AbCipReadSmokeTests
{
/// Verifies that the driver can read a seeded DInt value from an AB server.
/// The AB server profile to test against.
/// The transport stack the driver runs on.
[AbServerTheory]
[MemberData(nameof(TransportMatrix.Profiles), MemberType = typeof(TransportMatrix))]
public async Task Driver_reads_seeded_DInt_from_ab_server(AbServerProfile profile, AbCipTransport transport)
{
var fixture = new AbServerFixture(profile);
await fixture.InitializeAsync();
try
{
// Use fixture.Host (not hardcoded 127.0.0.1) so the docker-host migration
// (10.100.0.35) and AB_SERVER_ENDPOINT overrides reach this test too. The
// sibling Emulate tests already use the fixture's resolved endpoint; this
// smoke test was missed.
// Micro800 has no backplane: its cip-path is empty (the compose service runs
// ab_server --plc=Micro800 with no --path, which refuses a routed request). Every
// other family reaches slot 0 through `1,0`. Both transports are expected to pass
// for Micro800: the libplctag adapters map the empty route to a null `path` attribute
// (LibplctagPath.Normalize) because libplctag rejects "" with ErrorBadData.
var cipPath = profile.Family == AbCipPlcFamily.Micro800 ? "" : "1,0";
var deviceUri = $"ab://{fixture.Host}:{fixture.Port}/{cipPath}";
var drv = new AbCipDriver(new AbCipDriverOptions
{
Devices = [new AbCipDeviceOptions(deviceUri, profile.Family)],
RawTags = AbCipRawTags.From(new AbCipTagDefinition("Counter", deviceUri, "TestDINT", AbCipDataType.DInt)),
Timeout = TimeSpan.FromSeconds(5),
Transport = transport,
}, $"drv-smoke-{profile.Family}-{transport}");
await drv.InitializeAsync("{}", CancellationToken.None);
var snapshots = await drv.ReadAsync(["Counter"], CancellationToken.None);
snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good);
drv.GetHealth().State.ShouldBe(DriverState.Healthy);
await drv.ShutdownAsync(CancellationToken.None);
}
finally
{
await fixture.DisposeAsync();
}
}
}