using Microsoft.Extensions.Logging.Abstractions; using Opc.Ua; using Opc.Ua.Client; using Opc.Ua.Server; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.OpcUa; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests; /// Local loopback evidence that late binding replaces the SDK default on the wire. public sealed class ServiceLevelBindingReplayTests { [Theory] [InlineData((byte)100)] [InlineData((byte)0)] public async Task Late_binding_replays_latest_request_to_standard_ServiceLevel_read(byte latest) { using var deadline = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); deadline.CancelAfter(TimeSpan.FromSeconds(15)); var ct = deadline.Token; var pki = Path.Combine(Path.GetTempPath(), $"otopcua-replay-{Guid.NewGuid():N}"); var clientPki = TestClientSecurity.AllocatePkiRoot(); var port = 0; try { var deferred = new DeferredServiceLevelPublisher(); deferred.Publish(240); deferred.Publish(latest); var started = await OpcUaHostStart.StartAsync(p => { port = p; return new OpcUaApplicationHost(new OpcUaApplicationHostOptions { ApplicationName = "OtOpcUa.Replay", ApplicationUri = $"urn:OtOpcUa.Replay:{Path.GetFileName(pki)}", OpcUaPort = p, PublicHostname = "localhost", PkiStoreRoot = pki, }, NullLogger.Instance); }, () => new StandardServer(), ct); await using var host = started.Host; deferred.SetInner(new SdkServiceLevelPublisher(started.Server.CurrentInstance, NullLogger.Instance)); var config = new ApplicationConfiguration { ApplicationName = "OtOpcUa.ReplayClient", ApplicationUri = $"urn:OtOpcUa.ReplayClient:{Guid.NewGuid():N}", ApplicationType = ApplicationType.Client, SecurityConfiguration = TestClientSecurity.Build(clientPki), ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 10_000 }, TransportQuotas = new TransportQuotas { OperationTimeout = 5_000 }, }; await config.ValidateAsync(ApplicationType.Client, ct); config.CertificateValidator.CertificateValidation += (_, e) => e.Accept = true; var endpoint = await CoreClientUtils.SelectEndpointAsync(config, $"opc.tcp://localhost:{port}/OtOpcUa", false, DefaultTelemetry.Create(_ => { }), ct); using var session = await new DefaultSessionFactory(DefaultTelemetry.Create(_ => { })).CreateAsync( config, new ConfiguredEndpoint(null, endpoint, EndpointConfiguration.Create(config)), updateBeforeConnect: false, checkDomain: false, sessionName: "ServiceLevelReplay", sessionTimeout: 10_000, identity: new UserIdentity(new AnonymousIdentityToken()), preferredLocales: null, ct: ct); var read = await session.ReadValueAsync(new Opc.Ua.NodeId(2267u), ct); read.StatusCode.ShouldBe(StatusCodes.Good); read.Value.ShouldBe(latest); } finally { if (Directory.Exists(pki)) Directory.Delete(pki, recursive: true); if (Directory.Exists(clientPki)) Directory.Delete(clientPki, recursive: true); } } }