using Microsoft.Extensions.Logging.Abstractions; using Opc.Ua.Server; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests; /// /// Verifies the F13a cert auto-creation slice of : the SDK /// self-signs a certificate into {PkiStoreRoot}/own/certs/ on first boot against a /// fresh PKI tree, and the file persists for reuse on the next boot. /// public sealed class OpcUaApplicationHostTests : IDisposable { private static CancellationToken Ct => TestContext.Current.CancellationToken; private readonly string _pkiRoot = Path.Combine( Path.GetTempPath(), $"otopcua-pki-{Guid.NewGuid():N}"); /// Verifies StartAsync creates a self-signed certificate in the PKI own store. [Fact] public async Task StartAsync_creates_application_certificate_in_pki_own() { var applicationUri = $"urn:OtOpcUa.Test:{Guid.NewGuid():N}"; var started = await OpcUaHostStart.StartAsync( port => new OpcUaApplicationHost( new OpcUaApplicationHostOptions { ApplicationName = "OtOpcUa.Test", ApplicationUri = applicationUri, OpcUaPort = port, PublicHostname = "localhost", PkiStoreRoot = _pkiRoot, }, NullLogger.Instance), () => new StandardServer(), Ct); await using var host = started.Host; var ownCerts = Path.Combine(_pkiRoot, "own", "certs"); Directory.Exists(ownCerts).ShouldBeTrue($"expected SDK to create {ownCerts}"); Directory.EnumerateFiles(ownCerts).ShouldNotBeEmpty("expected a self-signed cert file in the own store"); } /// Verifies StartAsync reuses an existing certificate on the second boot. [Fact] public async Task StartAsync_reuses_existing_certificate_on_second_boot() { var first = await OpcUaHostStart.StartAsync( port => new OpcUaApplicationHost( new OpcUaApplicationHostOptions { ApplicationName = "OtOpcUa.Reuse", ApplicationUri = "urn:OtOpcUa.Reuse", OpcUaPort = port, PublicHostname = "localhost", PkiStoreRoot = _pkiRoot, }, NullLogger.Instance), () => new StandardServer(), Ct); await first.Host.DisposeAsync(); var ownCerts = Path.Combine(_pkiRoot, "own", "certs"); var firstFiles = Directory.GetFiles(ownCerts).OrderBy(f => f).ToArray(); firstFiles.ShouldNotBeEmpty(); var second = await OpcUaHostStart.StartAsync( port => new OpcUaApplicationHost( new OpcUaApplicationHostOptions { ApplicationName = "OtOpcUa.Reuse", ApplicationUri = "urn:OtOpcUa.Reuse", OpcUaPort = port, PublicHostname = "localhost", PkiStoreRoot = _pkiRoot, }, NullLogger.Instance), () => new StandardServer(), Ct); await second.Host.DisposeAsync(); var secondFiles = Directory.GetFiles(ownCerts).OrderBy(f => f).ToArray(); secondFiles.ShouldBe(firstFiles, "expected the second boot to reuse the cert from the first, not create a new one"); } /// Cleans up the temporary PKI directory. public void Dispose() { if (Directory.Exists(_pkiRoot)) { try { Directory.Delete(_pkiRoot, recursive: true); } catch { /* best-effort cleanup */ } } } }