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. [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 { /// Returns this builder (no-op). /// The browse name of the folder. /// The display name of the folder. public IAddressSpaceBuilder Folder(string browseName, string displayName) => this; /// Returns a stub handle. /// The browse name of the variable. /// The display name of the variable. /// The attribute information for the variable. public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo) => new StubHandle(); /// No-op property addition. /// The browse name of the property. /// The data type of the property. /// The property value. 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 { /// Gets the full reference as "stub". public string FullReference => "stub"; /// Throws NotSupportedException. /// The alarm condition information (unused). public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => throw new NotSupportedException(); } } }