Files
Assistant/backend.tests/DeviceHubHandshakeTests.cs
T

92 lines
3.8 KiB
C#

using System.Net.Http.Json;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using Xunit;
namespace backend.tests;
public class DeviceHubHandshakeTests(ApiFactory factory) : IClassFixture<ApiFactory>
{
private readonly ApiFactory _factory = factory;
private async Task<string> PairAndGetTokenAsync(string email)
{
var http = _factory.CreateClient();
await http.PostAsJsonAsync("/api/auth/register",
new { email, password = "Passw0rd!" });
await http.PostAsJsonAsync("/api/auth/login",
new { email, password = "Passw0rd!" });
var codeRes = await http.PostAsJsonAsync("/api/pair-code", new { name = "ws-test" });
var code = (await codeRes.Content.ReadFromJsonAsync<Dictionary<string, object>>())!["code"].ToString()!;
var pairRes = await _factory.CreateClient().PostAsJsonAsync("/api/pair",
new { code, hostname = "smoke", client_version = "0.1" });
var pairBody = await pairRes.Content.ReadFromJsonAsync<Dictionary<string, object>>();
return pairBody!["device_token"].ToString()!;
}
[Fact]
public async Task Connect_without_token_fails_upgrade()
{
var client = _factory.Server.CreateWebSocketClient();
var uri = new Uri(_factory.Server.BaseAddress, "device");
var act = async () => await client.ConnectAsync(uri, CancellationToken.None);
await act.Should().ThrowAsync<Exception>();
}
[Fact]
public async Task Hello_ack_includes_config()
{
var token = await PairAndGetTokenAsync("ws1@example.com");
var client = _factory.Server.CreateWebSocketClient();
client.ConfigureRequest = req => req.Headers["Authorization"] = $"Bearer {token}";
var uri = new Uri(_factory.Server.BaseAddress, "device");
var ws = await client.ConnectAsync(uri, CancellationToken.None);
await SendJson(ws, new { type = "hello", device_id = Guid.NewGuid(), client_version = "0.1" });
var ack = await ReceiveJson(ws);
ack.GetProperty("type").GetString().Should().Be("hello_ack");
ack.GetProperty("config").GetProperty("voice").GetString().Should().NotBeNullOrEmpty();
ack.GetProperty("config").GetProperty("model").GetString().Should().NotBeNullOrEmpty();
ack.GetProperty("config").GetProperty("enabled_tools").GetArrayLength().Should().BeGreaterThan(0);
}
[Fact]
public async Task Ping_replies_with_pong()
{
var token = await PairAndGetTokenAsync("ws2@example.com");
var client = _factory.Server.CreateWebSocketClient();
client.ConfigureRequest = req => req.Headers["Authorization"] = $"Bearer {token}";
var uri = new Uri(_factory.Server.BaseAddress, "device");
var ws = await client.ConnectAsync(uri, CancellationToken.None);
await SendJson(ws, new { type = "hello", device_id = Guid.NewGuid(), client_version = "0.1" });
await ReceiveJson(ws);
await SendJson(ws, new { type = "ping" });
var pong = await ReceiveJson(ws);
pong.GetProperty("type").GetString().Should().Be("pong");
}
private static async Task SendJson(WebSocket ws, object payload)
{
var bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload));
await ws.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
}
private static async Task<JsonElement> ReceiveJson(WebSocket ws)
{
var buf = new byte[8192];
using var stream = new MemoryStream();
while (true)
{
var r = await ws.ReceiveAsync(buf, CancellationToken.None);
stream.Write(buf, 0, r.Count);
if (r.EndOfMessage) break;
}
return JsonDocument.Parse(stream.ToArray()).RootElement.Clone();
}
}