diff --git a/backend.tests/FakeDeviceChannel.cs b/backend.tests/FakeDeviceChannel.cs new file mode 100644 index 0000000..e972ea4 --- /dev/null +++ b/backend.tests/FakeDeviceChannel.cs @@ -0,0 +1,18 @@ +using System.Collections.Concurrent; +using System.Text.Json; +using backend.Tools; + +namespace backend.tests; + +// Slim version for Tasks 9-15. Task 16 reopens this file to add IDeviceClient + envelope sending. +public class FakeDeviceChannel : IDeviceChannel +{ + public ConcurrentQueue PiToolReplies { get; } = new(); + + public Task CallPiToolAsync(string name, JsonElement args, CancellationToken ct) + { + if (PiToolReplies.TryDequeue(out var reply)) + return Task.FromResult(reply); + return Task.FromResult(JsonDocument.Parse("""{"ok":true}""").RootElement); + } +} diff --git a/backend.tests/ScriptedRealtimeUpstream.cs b/backend.tests/ScriptedRealtimeUpstream.cs new file mode 100644 index 0000000..5a332f4 --- /dev/null +++ b/backend.tests/ScriptedRealtimeUpstream.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Nodes; +using System.Threading.Channels; +using backend.Realtime; + +namespace backend.tests; + +public class ScriptedRealtimeUpstream : IRealtimeUpstream +{ + private readonly Channel _toRelay = + Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true }); + + private readonly Channel _fromRelay = + Channel.CreateUnbounded(new UnboundedChannelOptions { SingleWriter = true }); + + public IList Sent { get; } = new List(); + + public Task SendJsonAsync(JsonObject envelope, CancellationToken ct) + { + Sent.Add(envelope); + return _fromRelay.Writer.WriteAsync(envelope, ct).AsTask(); + } + + public async Task ReceiveJsonAsync(CancellationToken ct) + { + try + { + return await _toRelay.Reader.ReadAsync(ct); + } + catch (ChannelClosedException) + { + return null; + } + } + + public void Push(JsonObject evt) => _toRelay.Writer.TryWrite(evt); + public void CloseUpstream() => _toRelay.Writer.TryComplete(); + + public async Task WaitForSentAsync(string type, CancellationToken ct) + { + await foreach (var item in _fromRelay.Reader.ReadAllAsync(ct)) + { + if ((string?)item["type"] == type) return item; + } + throw new TimeoutException($"never saw outbound {type}"); + } + + public ValueTask DisposeAsync() + { + _toRelay.Writer.TryComplete(); + _fromRelay.Writer.TryComplete(); + return ValueTask.CompletedTask; + } +}