0a693e0be9
Implements IDataConnection + IBrowsableDataConnection over the IMxGatewayClient seam: connect/disconnect with once-only Disconnected guard + background event loop, subscribe/unsubscribe with tag routing, read/write batch with per-tag error classification, WriteBatchAndWait, and Galaxy browse mapping. Covers plan Tasks 6-10. Full unit coverage via FakeMxGatewayClient (12 tests).
66 lines
2.8 KiB
C#
66 lines
2.8 KiB
C#
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters;
|
|
|
|
/// <summary>
|
|
/// In-memory fake <see cref="IMxGatewayClient"/> for adapter unit tests. Lets tests
|
|
/// drive the event loop (push updates / fault the stream) and stub read/write/browse.
|
|
/// </summary>
|
|
public sealed class FakeMxGatewayClient : IMxGatewayClient, IMxGatewayClientFactory
|
|
{
|
|
public MxGatewayConnectionOptions? ConnectedWith;
|
|
public readonly List<string> Subscribed = new();
|
|
public readonly List<string> Unsubscribed = new();
|
|
public readonly TaskCompletionSource EventLoopGate = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
public Action<MxValueUpdate>? OnUpdate;
|
|
public Func<IReadOnlyList<string>, IReadOnlyList<MxReadOutcome>>? ReadHandler;
|
|
public Func<IReadOnlyList<(string TagPath, object? Value)>, IReadOnlyList<MxWriteOutcome>>? WriteHandler;
|
|
public Func<string?, (IReadOnlyList<MxBrowseChild>, bool)>? BrowseHandler;
|
|
private int _nextHandle;
|
|
|
|
public IMxGatewayClient Create() => this;
|
|
|
|
public Task ConnectAsync(MxGatewayConnectionOptions o, CancellationToken ct = default)
|
|
{
|
|
ConnectedWith = o;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask;
|
|
|
|
public Task<string> SubscribeAsync(string tag, CancellationToken ct = default)
|
|
{
|
|
var id = (++_nextHandle).ToString();
|
|
Subscribed.Add(tag);
|
|
return Task.FromResult(id);
|
|
}
|
|
|
|
public Task UnsubscribeAsync(string id, CancellationToken ct = default)
|
|
{
|
|
Unsubscribed.Add(id);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task<IReadOnlyList<MxReadOutcome>> ReadAsync(IReadOnlyList<string> tags, CancellationToken ct = default)
|
|
=> Task.FromResult(ReadHandler!(tags));
|
|
|
|
public Task<IReadOnlyList<MxWriteOutcome>> WriteAsync(IReadOnlyList<(string TagPath, object? Value)> w, CancellationToken ct = default)
|
|
=> Task.FromResult(WriteHandler!(w));
|
|
|
|
public Task<(IReadOnlyList<MxBrowseChild> Children, bool Truncated)> BrowseChildrenAsync(string? p, CancellationToken ct = default)
|
|
=> Task.FromResult(BrowseHandler!(p));
|
|
|
|
public async Task RunEventLoopAsync(Action<MxValueUpdate> onUpdate, CancellationToken ct = default)
|
|
{
|
|
OnUpdate = onUpdate;
|
|
using var reg = ct.Register(() => EventLoopGate.TrySetResult());
|
|
await EventLoopGate.Task; // test completes this to end the loop…
|
|
ct.ThrowIfCancellationRequested(); // …or FaultEventLoop() faults it to simulate a stream break
|
|
}
|
|
|
|
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
|
|
|
/// <summary>Simulate a stream break so the adapter raises Disconnected.</summary>
|
|
public void FaultEventLoop() => EventLoopGate.TrySetException(new Exception("stream broke"));
|
|
}
|