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());
}