Files
swyx/tests/SwyxTray.RecordingChecks/TransportChecks.cs
T
2026-11-02 11:45:41 +01:00

155 lines
8.2 KiB
C#

using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using SwyxTray.Recording;
using SwyxTray.Web;
internal static class TransportChecks
{
// Local-only fixture for browser -> real SwyxWeb -> isolated SwyxTrans integration checks.
public static async Task Serve()
{
var folder = Path.Combine(Path.GetTempPath(), "swyx-browser-check-" + Guid.NewGuid());
Directory.CreateDirectory(folder);
var now = DateTime.UtcNow;
using var outbox = new RecordingOutbox(Path.Combine(folder, "queue"), () => now);
var id = outbox.Begin(1, 123, "123", "WebSocket-Testgespräch", folder, null);
OutboxChecks.WriteWav(Path.Combine(folder, "call.wav"), now, 32_000);
outbox.Observe(new SwyxTray.Swyx.SwyxSnapshot(true, true, "test", "test",
[new SwyxTray.Swyx.SwyxLineInfo(0, IpPbx.CLMgrLib.CLMgrLineStates.LSInactive, "123", "Test", true, 123)]));
now = now.AddSeconds(4); outbox.Process(); now = now.AddSeconds(3); outbox.Process();
using var listener = new TcpListener(IPAddress.Loopback, 17658);
listener.Start();
Console.WriteLine("Synthetic WAV fixture ready: " + folder);
while (true)
{
var tcp = await listener.AcceptTcpClientAsync();
_ = Task.Run(async () =>
{
using (tcp)
{
try
{
var stream = tcp.GetStream();
var request = await WebSocketHandshake.ReadAsync(stream, CancellationToken.None);
if (request == null) return;
await WebSocketHandshake.AcceptAsync(stream, request, CancellationToken.None);
using var session = new WebSocketSession(1, WebSocket.CreateFromStream(stream, true, null,
TimeSpan.FromSeconds(15)), new CommandExecutor(outbox), new PluginTabChannel(), request.Origin, false);
await session.RunAsync(CancellationToken.None);
}
catch (Exception ex) { Console.WriteLine(ex.GetType().Name); }
}
});
}
}
public static async Task Run(Action<bool, string> check)
{
var folder = Path.Combine(Path.GetTempPath(), "swyx-transport-" + Guid.NewGuid());
Directory.CreateDirectory(folder);
try
{
var now = DateTime.UtcNow;
using var outbox = new RecordingOutbox(Path.Combine(folder, "queue"), () => now);
var id = outbox.Begin(1, 123, "123", "Test", folder, null);
var path = Path.Combine(folder, "call.wav");
OutboxChecks.WriteWav(path, now, samples: 1_000_000);
outbox.Observe(new SwyxTray.Swyx.SwyxSnapshot(true, true, "test", "test",
[new SwyxTray.Swyx.SwyxLineInfo(0, IpPbx.CLMgrLib.CLMgrLineStates.LSInactive, "123", "Test", true, 123)]));
now = now.AddSeconds(4); outbox.Process(); now = now.AddSeconds(3); outbox.Process();
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
for (int round = 0; round < 3; round++)
{
using var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
using var tcp = new TcpClient();
var connection = tcp.ConnectAsync((IPEndPoint)listener.LocalEndpoint);
using var remote = await listener.AcceptTcpClientAsync(timeout.Token);
await connection;
using var socket = WebSocket.CreateFromStream(tcp.GetStream(), false, null, TimeSpan.FromSeconds(15));
using var server = new WebSocketSession(round, WebSocket.CreateFromStream(remote.GetStream(), true, null,
TimeSpan.FromSeconds(15)), new CommandExecutor(outbox), new PluginTabChannel(), "http://test", false);
var running = server.RunAsync(timeout.Token);
var notice = await Until(socket, e => e.TryGetProperty("recordingId", out _), timeout.Token);
check(notice.GetProperty("recordingId").GetGuid() == id, "recording replay uses original UUID");
if (round == 2)
{
check(notice.GetProperty("state").GetString() == "submitted", "receipt replay on reconnect");
}
else
{
await Send(socket, new { id = 7, cmd = "recording.download", recordingId = id }, timeout.Token);
await Send(socket, new { id = 8, cmd = "ping" }, timeout.Token);
using var received = new MemoryStream();
bool done = false, ping = false;
while (!done || !ping)
{
var (type, bytes) = await Receive(socket, timeout.Token);
if (type == WebSocketMessageType.Binary)
{
check(bytes.AsSpan(0, 4).SequenceEqual("SWYX"u8), "binary block magic");
check(BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(4)) == 7, "binary request id");
check(BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(8)) == received.Length, "binary offset");
received.Write(bytes, 12, bytes.Length - 12);
if (round == 0) break; // Interrupt, then request the same retained WAV on another session.
}
else
{
var result = JsonDocument.Parse(bytes).RootElement;
if (result.TryGetProperty("id", out var requestId))
{
check(result.GetProperty("ok").GetBoolean(), "transfer/control command succeeds");
if (requestId.GetInt32() == 7) done = true;
if (requestId.GetInt32() == 8) ping = true;
}
}
}
if (round == 1)
{
check(received.ToArray().SequenceEqual(File.ReadAllBytes(path)), "actual WebSocket preserves entire WAV");
check(ping, "control commands remain responsive during transfer");
await Send(socket, new { id = 9, cmd = "recording.ack", recordingId = id, jobId = new string('a', 32) }, timeout.Token);
var ack = await Until(socket, e => e.TryGetProperty("id", out var n) && n.GetInt32() == 9, timeout.Token);
check(ack.GetProperty("ok").GetBoolean(), "transfer receipt accepted");
}
}
socket.Abort(); server.Dispose();
try { await running; } catch (Exception ex) when (ex is WebSocketException or IOException or ObjectDisposedException) { }
}
check(File.Exists(path), "original WAV survives websocket acknowledgements");
}
finally { Directory.Delete(folder, true); }
}
private static Task Send(WebSocket socket, object value, CancellationToken token) =>
socket.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value)), WebSocketMessageType.Text, true, token);
private static async Task<JsonElement> Until(WebSocket socket, Func<JsonElement, bool> predicate, CancellationToken token)
{
while (true)
{
var (type, bytes) = await Receive(socket, token);
if (type != WebSocketMessageType.Text) continue;
var element = JsonDocument.Parse(bytes).RootElement;
if (predicate(element)) return element;
}
}
private static async Task<(WebSocketMessageType, byte[])> Receive(WebSocket socket, CancellationToken token)
{
using var message = new MemoryStream();
var buffer = new byte[32 * 1024];
WebSocketReceiveResult result;
do
{
result = await socket.ReceiveAsync(buffer, token);
message.Write(buffer, 0, result.Count);
} while (!result.EndOfMessage);
return (result.MessageType, message.ToArray());
}
}