using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; /// /// Scaffold tests for 's /// surface that don't require a live remote server. Live-browse coverage lands in a /// follow-up PR once the in-process OPC UA server fixture is scaffolded. /// [Trait("Category", "Unit")] public sealed class OpcUaClientDiscoveryTests { /// Verifies that DiscoverAsync throws InvalidOperationException when not initialized. /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-disco"); var builder = new NullAddressSpaceBuilder(); await Should.ThrowAsync(async () => await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken)); } /// Verifies that DiscoverAsync rejects null builder argument. [Fact] public void DiscoverAsync_rejects_null_builder() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-disco"); Should.ThrowAsync(async () => await drv.DiscoverAsync(null!, TestContext.Current.CancellationToken)); } /// Verifies that discovery configuration has sensible defaults. [Fact] public void Discovery_caps_are_sensible_defaults() { var opts = new OpcUaClientDriverOptions(); opts.MaxDiscoveredNodes.ShouldBe(10_000, "bounds memory on runaway servers without clipping normal models"); opts.MaxBrowseDepth.ShouldBe(10, "deep enough for realistic info models; shallow enough for cycle safety"); opts.BrowseRoot.ShouldBeNull("null = default to ObjectsFolder i=85"); } /// Test builder that provides no-op implementations for discovery tests. private sealed class NullAddressSpaceBuilder : IAddressSpaceBuilder { /// public IAddressSpaceBuilder Folder(string browseName, string displayName) => this; /// public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo) => new StubHandle(); /// public void AddProperty(string browseName, DriverDataType dataType, object? value) { } /// No-op alarm condition attachment. /// The source variable handle. /// The alarm name. /// The alarm attribute information. public void AttachAlarmCondition(IVariableHandle sourceVariable, string alarmName, DriverAttributeInfo alarmInfo) { } /// Stub variable handle for testing. private sealed class StubHandle : IVariableHandle { /// public string FullReference => "stub"; /// public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => throw new NotSupportedException(); } } }