using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Commons.Tests.Browsing; /// Shared test doubles for the DiscoveryDriverBrowser suites (Tasks 8–10). internal sealed class FakeDiscoveryDriver : IDriver, ITagDiscovery { public int InitializeCalls; public int DiscoverCalls; public int ShutdownCalls; /// Ordered lifecycle trace ("init"/"discover"/"shutdown"). public List Trace { get; } = []; /// Completes once ShutdownAsync has finished (even the fire-and-forget CanBrowse path). public TaskCompletionSource ShutdownDone { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); public bool SupportsOnlineDiscovery { get; init; } = true; public DiscoveryRediscoverPolicy RediscoverPolicy { get; init; } = DiscoveryRediscoverPolicy.Once; /// Discovery behavior: (builder, 1-based call index) → task. Default streams one leaf. public Func? OnDiscover { get; init; } public Func? OnInitialize { get; init; } public Func? OnShutdown { get; init; } public string DriverInstanceId { get; } public string DriverType { get; } public string? LastConfigJson { get; private set; } public FakeDiscoveryDriver(string driverType = "Fake", string id = "fake-1") { DriverType = driverType; DriverInstanceId = id; } public async Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken) { LastConfigJson = driverConfigJson; Interlocked.Increment(ref InitializeCalls); lock (Trace) Trace.Add("init"); if (OnInitialize is not null) await OnInitialize(cancellationToken).ConfigureAwait(false); } public async Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken) { var idx = Interlocked.Increment(ref DiscoverCalls); lock (Trace) Trace.Add("discover"); if (OnDiscover is not null) { await OnDiscover(builder, idx).ConfigureAwait(false); return; } // Default: stream one leaf so the captured tree is non-empty. builder.Variable("Tag1", "Tag1", new DriverAttributeInfo("Dev.Tag1", DriverDataType.Float64, false, null, SecurityClassification.Operate, false)); } public async Task ShutdownAsync(CancellationToken cancellationToken) { Interlocked.Increment(ref ShutdownCalls); lock (Trace) Trace.Add("shutdown"); try { if (OnShutdown is not null) await OnShutdown(cancellationToken).ConfigureAwait(false); } finally { ShutdownDone.TrySetResult(); } } public Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask; public DriverHealth GetHealth() => new(DriverState.Healthy, null, null); public long GetMemoryFootprint() => 0; public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask; } /// An IDriver that is NOT ITagDiscovery — exercises the not-discovery-capable gate. internal sealed class FakeNonDiscoveryDriver(string driverType = "Flat", string id = "flat-1") : IDriver { public int ShutdownCalls; public TaskCompletionSource ShutdownDone { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); public string DriverInstanceId { get; } = id; public string DriverType { get; } = driverType; public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask; public Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask; public Task ShutdownAsync(CancellationToken cancellationToken) { Interlocked.Increment(ref ShutdownCalls); ShutdownDone.TrySetResult(); return Task.CompletedTask; } public DriverHealth GetHealth() => new(DriverState.Healthy, null, null); public long GetMemoryFootprint() => 0; public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask; } /// Configurable IDriverFactory: delegates construction to a supplied func and records what it made. internal sealed class FakeDriverFactory(Func create) : IDriverFactory { public int CreateCalls; public List Created { get; } = []; public IDriver? TryCreate(string driverType, string driverInstanceId, string driverConfigJson) { Interlocked.Increment(ref CreateCalls); var d = create(driverType, driverInstanceId, driverConfigJson); if (d is not null) lock (Created) Created.Add(d); return d; } public IReadOnlyCollection SupportedTypes { get; } = Array.Empty(); /// Factory that always returns a fresh default FakeDiscoveryDriver. public static FakeDriverFactory Of(Func make) => new((_, _, _) => make()); }