using System.Runtime.CompilerServices;
using Shouldly;
using Xunit;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
///
/// #865: — what advise paths wait on so an initial value is
/// never pushed before the stream is requested. It must complete once the RPC is started (before any
/// event), and must never strand a waiter on a pump whose loop died before getting that far.
///
public sealed class EventPumpStreamRequestedTests
{
private sealed class ScriptedSubscriber(Func> stream) : IGalaxySubscriber
{
public Task> SubscribeBulkAsync(
IReadOnlyList fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
=> Task.FromResult>([]);
public Task UnsubscribeBulkAsync(IReadOnlyList itemHandles, CancellationToken cancellationToken) => Task.CompletedTask;
public IAsyncEnumerable StreamEventsAsync(CancellationToken cancellationToken) => stream(cancellationToken);
}
/// A stream that is requested and then never yields: StreamRequested completes; the RPC started first.
[Fact]
public async Task Completes_once_the_stream_is_requested_before_any_event()
{
var rpcStarted = 0;
async IAsyncEnumerable Never([EnumeratorCancellation] CancellationToken ct)
{
Interlocked.Exchange(ref rpcStarted, 1); // the synchronous first step — where a real call is created
await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false);
yield break;
}
await using var pump = new EventPump(new ScriptedSubscriber(Never), new SubscriptionRegistry());
pump.StreamRequested.IsCompleted.ShouldBeFalse("not before Start");
pump.Start();
await TestWait.ForAsync(pump.StreamRequested, "StreamRequested must complete once the stream call is started");
Volatile.Read(ref rpcStarted).ShouldBe(1, "StreamRequested must not precede the stream call");
}
/// A subscriber that throws before reaching the RPC still completes StreamRequested (no stranded waiter).
[Fact]
public async Task Completes_when_the_stream_fails_before_the_rpc()
{
var faulted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
await using var pump = new EventPump(
new ScriptedSubscriber(_ => throw new InvalidOperationException("not connected")),
new SubscriptionRegistry(),
onStreamFault: ex => faulted.TrySetResult(ex));
pump.Start();
await TestWait.ForAsync(pump.StreamRequested, "a dead pump must not strand a StreamRequested waiter");
(await TestWait.ForAsync(faulted.Task, "positive control: the failure is still reported as a stream fault"))
.ShouldBeOfType();
}
}