using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Scaffold-level tests for that don't require a live /// remote OPC UA server. PR 67+ adds IReadable/IWritable/ITagDiscovery/ISubscribable /// tests against a local in-process OPC UA server fixture. /// [Trait("Category", "Unit")] public sealed class OpcUaClientDriverScaffoldTests { [Fact] public void Default_options_target_standard_opcua_port_and_anonymous_auth() { var opts = new OpcUaClientDriverOptions(); opts.EndpointUrl.ShouldBe("opc.tcp://localhost:4840", "4840 is the IANA-assigned OPC UA port"); opts.SecurityMode.ShouldBe(OpcUaSecurityMode.None); opts.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.None); opts.AuthType.ShouldBe(OpcUaAuthType.Anonymous); opts.AutoAcceptCertificates.ShouldBeFalse("production default must reject untrusted server certs"); } [Fact] public void Default_timeouts_match_driver_specs_section_8() { var opts = new OpcUaClientDriverOptions(); opts.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(120)); opts.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(5)); opts.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(5)); } [Fact] public void Driver_reports_type_and_id_before_connect() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-test"); drv.DriverType.ShouldBe("OpcUaClient"); drv.DriverInstanceId.ShouldBe("opcua-test"); drv.GetHealth().State.ShouldBe(DriverState.Unknown); } [Fact] public async Task Initialize_against_unreachable_endpoint_transitions_to_Faulted_and_throws() { // RFC 5737 reserved-for-documentation IP; won't route anywhere. Pick opc.tcp:// so // endpoint selection hits the transport-layer connection rather than a DNS lookup. var opts = new OpcUaClientDriverOptions { // Port 1 on loopback is effectively guaranteed to be closed — the OS responds // with TCP RST immediately instead of hanging on connect, which keeps the // unreachable-host tests snappy. Don't use an RFC 5737 reserved IP; those get // routed to a black-hole + time out only after the SDK's internal retry/backoff // fully elapses (~60s even with Options.Timeout=500ms). EndpointUrl = "opc.tcp://127.0.0.1:1", Timeout = TimeSpan.FromMilliseconds(500), AutoAcceptCertificates = true, // dev-mode to bypass cert validation in the test }; using var drv = new OpcUaClientDriver(opts, "opcua-unreach"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); var health = drv.GetHealth(); health.State.ShouldBe(DriverState.Faulted); health.LastError.ShouldNotBeNull(); } [Fact] public async Task Reinitialize_against_unreachable_endpoint_re_throws() { var opts = new OpcUaClientDriverOptions { // Port 1 on loopback is effectively guaranteed to be closed — the OS responds // with TCP RST immediately instead of hanging on connect, which keeps the // unreachable-host tests snappy. Don't use an RFC 5737 reserved IP; those get // routed to a black-hole + time out only after the SDK's internal retry/backoff // fully elapses (~60s even with Options.Timeout=500ms). EndpointUrl = "opc.tcp://127.0.0.1:1", Timeout = TimeSpan.FromMilliseconds(500), AutoAcceptCertificates = true, }; using var drv = new OpcUaClientDriver(opts, "opcua-reinit"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); await Should.ThrowAsync(async () => await drv.ReinitializeAsync("{}", TestContext.Current.CancellationToken)); } }