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);
}
}
}