Files
lmxopcua/tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/Browsing/DiscoveryDriverBrowserFakes.cs
T
Joseph Doherty fa339a5565 feat(browse): Wave-0 Batch D — IUniversalDriverBrowser + DiscoveryDriverBrowser core
Task 8: IUniversalDriverBrowser + DiscoveryDriverBrowser open path — construct(+PatchForBrowse)
        -> connect -> one-shot capture -> bounded (10s) shutdown; CanBrowse gate (catch-throwing
        TryCreate, defensive teardown of the throwaway instance); deep-merge browse patch.
        Commons gains Microsoft.Extensions.Logging.Abstractions (ILogger doesn't flow transitively)
        + InternalsVisibleTo(Commons.Tests). OTOPCUA0001 suppressed on the deliberate one-shot
        browse-capture DiscoverAsync (not a runtime dispatch path). 12 unit tests green.
2026-07-15 17:27:04 -04:00

119 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Commons.Tests.Browsing;
/// <summary>Shared test doubles for the DiscoveryDriverBrowser suites (Tasks 810).</summary>
internal sealed class FakeDiscoveryDriver : IDriver, ITagDiscovery
{
public int InitializeCalls;
public int DiscoverCalls;
public int ShutdownCalls;
/// <summary>Ordered lifecycle trace ("init"/"discover"/"shutdown").</summary>
public List<string> Trace { get; } = [];
/// <summary>Completes once ShutdownAsync has finished (even the fire-and-forget CanBrowse path).</summary>
public TaskCompletionSource ShutdownDone { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public bool SupportsOnlineDiscovery { get; init; } = true;
public DiscoveryRediscoverPolicy RediscoverPolicy { get; init; } = DiscoveryRediscoverPolicy.Once;
/// <summary>Discovery behavior: (builder, 1-based call index) → task. Default streams one leaf.</summary>
public Func<IAddressSpaceBuilder, int, Task>? OnDiscover { get; init; }
public Func<CancellationToken, Task>? OnInitialize { get; init; }
public Func<CancellationToken, Task>? 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;
}
/// <summary>An IDriver that is NOT ITagDiscovery — exercises the not-discovery-capable gate.</summary>
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;
}
/// <summary>Configurable IDriverFactory: delegates construction to a supplied func and records what it made.</summary>
internal sealed class FakeDriverFactory(Func<string, string, string, IDriver?> create) : IDriverFactory
{
public int CreateCalls;
public List<IDriver> 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<string> SupportedTypes { get; } = Array.Empty<string>();
/// <summary>Factory that always returns a fresh default FakeDiscoveryDriver.</summary>
public static FakeDriverFactory Of(Func<FakeDiscoveryDriver> make) =>
new((_, _, _) => make());
}