using System.Net.Http.Headers; using System.Text.Json; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using WxAgent.Service; using Xunit; namespace WxAgent.Service.Tests; public sealed class SseTests { private sealed class Backend : IAgentBackend { public IReadOnlyList Capabilities => [new("listener-events", true, true, true, true, false, "read", false, 30, null, [])]; public Task StatusAsync(CancellationToken ct) => Task.FromResult(new { ok = true }); } [Fact] public async Task EventStreamUsesRealSseFramesAndRedactsMessageContent() { var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); var token = new string('A', 43); var options = new ServiceOptions { CredentialFile = Path.Combine(dir, "credentials.json"), DataDirectory = dir }; File.WriteAllText(options.CredentialFile, JsonSerializer.Serialize(new[] { new ServiceCredential("alice", ServiceOptions.HashToken(token), ["read"], []) })); await using var app = ServiceHost.Build(options, new Backend(), b => b.WebHost.UseTestServer()); try { await app.StartAsync(); using var client = app.GetTestClient(); client.BaseAddress = new Uri("http://localhost:5088"); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); using var response = await client.GetAsync("/api/v1/events", HttpCompletionOption.ResponseHeadersRead); Assert.Equal(200, (int)response.StatusCode); var hub = app.Services.GetRequiredService(); hub.Publish("alice", new AgentEvent("evt-1", "ui-current", "test", "message", "Text", DateTimeOffset.UtcNow)); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); using var reader = new StreamReader(await response.Content.ReadAsStreamAsync(timeout.Token)); Assert.Equal("id: evt-1", await reader.ReadLineAsync(timeout.Token)); Assert.Equal("event: message", await reader.ReadLineAsync(timeout.Token)); var data = await reader.ReadLineAsync(timeout.Token); Assert.StartsWith("data: ", data); Assert.DoesNotContain("content", data, StringComparison.OrdinalIgnoreCase); Assert.Equal(string.Empty, await reader.ReadLineAsync(timeout.Token)); } finally { await app.StopAsync(); Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); Directory.Delete(dir, true); } } }