using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Scaffold tests for ISubscribable + IHostConnectivityProbe that don't /// need a live remote server. Live-session tests (subscribe/unsubscribe round-trip, /// keep-alive transitions) land in a follow-up PR once the in-process OPC UA server /// fixture is scaffolded. /// [Trait("Category", "Unit")] public sealed class OpcUaClientSubscribeAndProbeTests { [Fact] public async Task SubscribeAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-sub-uninit"); await Should.ThrowAsync(async () => await drv.SubscribeAsync(["ns=2;s=Demo"], TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken)); } [Fact] public async Task UnsubscribeAsync_with_unknown_handle_is_noop() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-sub-unknown"); // UnsubscribeAsync returns cleanly for handles it doesn't recognise — protects against // the caller's race with server-side cleanup after a session drop. await drv.UnsubscribeAsync(new FakeHandle(), TestContext.Current.CancellationToken); } [Fact] public void GetHostStatuses_returns_endpoint_url_row_pre_init() { using var drv = new OpcUaClientDriver( new OpcUaClientDriverOptions { EndpointUrl = "opc.tcp://plc.example:4840" }, "opcua-hosts"); var rows = drv.GetHostStatuses(); rows.Count.ShouldBe(1); rows[0].HostName.ShouldBe("opc.tcp://plc.example:4840", "host identity mirrors the endpoint URL so the Admin /hosts dashboard can link back to the remote server"); rows[0].State.ShouldBe(HostState.Unknown); } private sealed class FakeHandle : ISubscriptionHandle { public string DiagnosticId => "fake"; } }