using System.Net; using System.Net.Http.Json; using System.Text.Json; namespace ClaudeMailbox.Tests; public sealed class MailboxEndToEndTests { [Fact] public async Task Health_Returns_Ok() { await using var host = await TestHost.StartAsync(); var res = await host.Client.GetAsync("/health"); res.EnsureSuccessStatusCode(); var body = await res.Content.ReadFromJsonAsync(); Assert.Equal("ok", body.GetProperty("status").GetString()); } [Fact] public async Task Send_Without_Header_Is_BadRequest() { await using var host = await TestHost.StartAsync(); var res = await host.Client.PostAsJsonAsync("/v1/send", new { to = "anyone", body = "hi" }); Assert.Equal(HttpStatusCode.BadRequest, res.StatusCode); } [Fact] public async Task Two_Mailboxes_Coordinate() { await using var host = await TestHost.StartAsync(); using var backend = host.NewClientFor("backend"); using var frontend = host.NewClientFor("frontend"); // backend sends to frontend var send = await backend.PostAsJsonAsync("/v1/send", new { to = "frontend", body = "API shape changed" }); send.EnsureSuccessStatusCode(); // frontend peeks — expects 1 var peek1 = await frontend.GetFromJsonAsync("/v1/peek?name=frontend"); Assert.Equal(1, peek1.GetProperty("pending").GetInt32()); // frontend consumes var check = await frontend.PostAsync("/v1/check-inbox?name=frontend", null); check.EnsureSuccessStatusCode(); var messages = await check.Content.ReadFromJsonAsync(); Assert.Equal(1, messages.GetArrayLength()); var msg = messages[0]; Assert.Equal("backend", msg.GetProperty("from").GetString()); Assert.Equal("API shape changed", msg.GetProperty("body").GetString()); // peek again — expects 0 var peek2 = await frontend.GetFromJsonAsync("/v1/peek?name=frontend"); Assert.Equal(0, peek2.GetProperty("pending").GetInt32()); } [Fact] public async Task Check_Inbox_Rejects_Mismatched_Identity() { await using var host = await TestHost.StartAsync(); using var backend = host.NewClientFor("backend"); using var frontend = host.NewClientFor("frontend"); await backend.PostAsJsonAsync("/v1/send", new { to = "frontend", body = "hello" }); // backend tries to consume frontend's inbox — must be rejected var bad = await backend.PostAsync("/v1/check-inbox?name=frontend", null); Assert.Equal(HttpStatusCode.Forbidden, bad.StatusCode); } [Fact] public async Task List_Returns_Known_Mailboxes() { await using var host = await TestHost.StartAsync(); using var a = host.NewClientFor("alpha"); using var b = host.NewClientFor("beta"); // Touch both mailboxes by having each peek its own inbox await a.GetAsync("/v1/peek?name=alpha"); await b.GetAsync("/v1/peek?name=beta"); // /v1/list is the only endpoint that works without X-Mailbox var list = await host.Client.GetFromJsonAsync("/v1/list"); var names = new List(); foreach (var elem in list.EnumerateArray()) names.Add(elem.GetProperty("name").GetString()!); Assert.Contains("alpha", names); Assert.Contains("beta", names); } }