using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Gitea #516 — OpcUaClientDriver.InitializeAsync used to serve the options its CONSTRUCTOR /// captured. Its own comment claimed "resolving on every InitializeAsync … picks up rotations", which /// was true of SECRET rotation only: the endpoint, the security policy and the tag set were all frozen /// at construction, so an operator's edit was discarded while the deployment still sealed green. /// [Trait("Category", "Unit")] public sealed class OpcUaClientReinitConfigAdoptionTests { /// /// ValidateNamespaceKind runs at the top of init and rejects TargetNamespaceKind=Equipment /// with an empty UNS mapping table. Reinitializing INTO that shape must therefore throw — a driver /// still serving the constructor's valid options would validate those and succeed, which is exactly /// how the discarded edit stayed invisible. /// [Fact] public async Task Reinitialize_adopts_a_changed_config() { var driver = OpcUaClientDriverFactoryExtensions.CreateInstance( "opc1", """{"endpointUrl":"opc.tcp://10.0.0.1:4840","targetNamespaceKind":"Equipment","unsMappingTable":{"a":"b"}}"""); await Should.ThrowAsync( () => driver.ReinitializeAsync( """{"endpointUrl":"opc.tcp://10.0.0.1:4840","targetNamespaceKind":"Equipment","unsMappingTable":{}}""", CancellationToken.None), "OpcUaClientDriver kept its constructor-supplied options after a reinitialize with a changed config (#516)"); } /// An empty/placeholder document must still keep the constructor options. [Fact] public async Task Reinitialize_with_an_empty_document_keeps_the_constructor_options() { var driver = OpcUaClientDriverFactoryExtensions.CreateInstance( "opc1", """{"endpointUrl":"opc.tcp://10.0.0.1:4840","targetNamespaceKind":"Equipment","unsMappingTable":{"a":"b"}}"""); // "{}" keeps the (valid) constructor options, so ValidateNamespaceKind passes and the driver gets as // far as the connect — which fails against an unreachable endpoint. Anything BUT the validation // throw proves the constructor options survived. var ex = await Record.ExceptionAsync(() => driver.ReinitializeAsync("{}", CancellationToken.None)); (ex is InvalidOperationException ioe && ioe.Message.Contains("UnsMappingTable", StringComparison.OrdinalIgnoreCase)) .ShouldBeFalse("an empty document must keep the constructor-supplied options, not re-validate an empty config"); } }