using System.Net; using System.Text.Json.Nodes; namespace MoShiro.Backend.Tests; [Trait("Category", "Slow")] public sealed class AgentMeshTests : IClassFixture { private readonly BackendFixture _backend; public AgentMeshTests(BackendFixture backend) => _backend = backend; /// 两个都监听的 Agent 只有一条线路:id 较小的拨出,上下行都在这一条连接上测。 [Fact] public async Task ListeningAgents_ShareOneLine_DialedBySmallerId_AndReportThroughTheirBackendLinks() { await using AgentHarness first = await AgentHarness.StartAsync(_backend); await using AgentHarness second = await AgentHarness.StartAsync(_backend); (AgentHarness dialer, AgentHarness listener) = string.CompareOrdinal(first.Id, second.Id) < 0 ? (first, second) : (second, first); await dialer.OverrideLineTasksAsync(rateHz: 30); await listener.OverrideLineTasksAsync(rateHz: 30); await BackendFixture.EventuallyAsync( () => Task.FromResult(dialer.App.Dialer.IsConnected(listener.Id)), TimeSpan.FromSeconds(30), "the agent with the smaller id should establish the outgoing measurement session"); Assert.False(listener.App.Dialer.IsConnected(dialer.Id)); // 反方向不再拨 using HttpResponseMessage retry = await _backend.Admin.PostAsync($"/api/admin/links/{dialer.Id}/{listener.Id}/retry", null); Assert.Equal(HttpStatusCode.Accepted, retry.StatusCode); JsonNode result = JsonNode.Parse(await retry.Content.ReadAsStringAsync())!; Assert.True(result["queued"]!.GetValue()); await BackendFixture.EventuallyAsync(async () => { JsonNode matrix = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/links"); JsonNode[] pair = [.. matrix["items"]!.AsArray().Select(i => i!).Where(i => (i["dialer_id"]!.GetValue() == dialer.Id && i["listener_id"]!.GetValue() == listener.Id) || (i["dialer_id"]!.GetValue() == listener.Id && i["listener_id"]!.GetValue() == dialer.Id))]; return pair.Length == 1 && pair[0]["connected"]!.GetValue(); }, TimeSpan.FromSeconds(30), "the dialer's heartbeat should report its outgoing connection, and the pair should have a single line"); await BackendFixture.EventuallyAsync(async () => { JsonNode chunks = await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={listener.Id}"); return chunks["items"]!.AsArray().Any(c => c!["kind"]!.GetValue() == "quic_ping" && c["series"]!.GetValue() == "line:" + dialer.Id); }, TimeSpan.FromSeconds(40), "the line should persist measured packet data"); } }