diff --git a/backend/tests/MoShiro.Backend.Tests/AgentHarness.cs b/backend/tests/MoShiro.Backend.Tests/AgentHarness.cs index d82c0af..078d944 100644 --- a/backend/tests/MoShiro.Backend.Tests/AgentHarness.cs +++ b/backend/tests/MoShiro.Backend.Tests/AgentHarness.cs @@ -119,6 +119,7 @@ public sealed class AgentHarness : IAsyncDisposable bool enabled = kind != "connect"; if (rateHz is { } hz && kind != "connect") { + parameters["adaptive"] = false; // 固定频率:用例按这个速率发包,限速也按它算 parameters["rate_hz"] = hz; } diff --git a/backend/tests/MoShiro.Backend.Tests/AgentMeshTests.cs b/backend/tests/MoShiro.Backend.Tests/AgentMeshTests.cs index b561aa9..87a9beb 100644 --- a/backend/tests/MoShiro.Backend.Tests/AgentMeshTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AgentMeshTests.cs @@ -10,18 +10,21 @@ public sealed class AgentMeshTests : IClassFixture public AgentMeshTests(BackendFixture backend) => _backend = backend; + /// 两个都监听的 Agent 只有一条线路:id 较小的拨出,上下行都在这一条连接上测。 [Fact] - public async Task ListeningAgents_MeasureBothDirections_AndReportThroughTheirBackendLinks() + public async Task ListeningAgents_ShareOneLine_DialedBySmallerId_AndReportThroughTheirBackendLinks() { - await using AgentHarness a = await AgentHarness.StartAsync(_backend); - await using AgentHarness b = await AgentHarness.StartAsync(_backend); - await a.OverrideLineTasksAsync(rateHz: 30); - await b.OverrideLineTasksAsync(rateHz: 30); + 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(a.App.Dialer.IsConnected(b.Id) && b.App.Dialer.IsConnected(a.Id)), - TimeSpan.FromSeconds(30), "both listening agents should establish outgoing measurement sessions"); + () => 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/{a.Id}/{b.Id}/retry", null); + 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()); @@ -29,18 +32,16 @@ public sealed class AgentMeshTests : IClassFixture await BackendFixture.EventuallyAsync(async () => { JsonNode matrix = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/links"); - return matrix["items"]!.AsArray().Count(i => - ((i!["dialer_id"]!.GetValue() == a.Id && i["listener_id"]!.GetValue() == b.Id) - || (i["dialer_id"]!.GetValue() == b.Id && i["listener_id"]!.GetValue() == a.Id)) - && i["connected"]!.GetValue()) == 2; - }, TimeSpan.FromSeconds(30), "each agent heartbeat should report its outgoing connection"); + 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 left = await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={a.Id}"); - JsonNode right = await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={b.Id}"); - return left["items"]!.AsArray().Any(c => c!["kind"]!.GetValue() == "udp_echo" && c["series"]!.GetValue() == "line:" + b.Id) - && right["items"]!.AsArray().Any(c => c!["kind"]!.GetValue() == "udp_echo" && c["series"]!.GetValue() == "line:" + a.Id); - }, TimeSpan.FromSeconds(40), "both directed lines should persist measured packet data"); + JsonNode chunks = await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={listener.Id}"); + return chunks["items"]!.AsArray().Any(c => c!["kind"]!.GetValue() == "udp_echo" && c["series"]!.GetValue() == "line:" + dialer.Id); + }, TimeSpan.FromSeconds(40), "the line should persist measured packet data"); } }