using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
/// 真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。
[Collection("backend")]
public sealed class ProbeEndToEndTests
{
private readonly BackendFixture _backend;
public ProbeEndToEndTests(BackendFixture backend)
{
_backend = backend;
}
private async Task AgentViewAsync(AgentHarness agent) => await _backend.GetJsonAsync(_backend.Admin, "/api/admin/agents/" + agent.Id);
private async Task ChunkCountAsync(AgentHarness agent, string kind) =>
(await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={agent.Id}"))["items"]!.AsArray().Count(c => c!["kind"]!.GetValue() == kind);
[Fact]
public async Task Probe_ConnectsThroughTheBootstrapList_RunsTheLineTasks_AndTheBackendStoresMergedChunks()
{
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
await agent.OverrideLineTasksAsync(rateHz: 30);
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(agent.Id)), because: "the probe should connect to the bootstrap agent");
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue());
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.Connection(agent.Id)!.RoleCount == 2), because: "udp_echo and tcp_echo");
// 每 10 秒切一块,回显类再等 2 秒宽限
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "udp_echo") >= 1 && await ChunkCountAsync(agent, "tcp_echo") >= 1, TimeSpan.FromSeconds(40));
Assert.True(agent.App.Counters.Fragments >= 2);
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
// 时钟偏移已经估计出来:进程内两端共用同一个单调时钟,所以偏移应当接近 0(真实线路上是两台机器各自的时钟原点之差)
Assert.InRange(probe.App.Connection(agent.Id)!.ClockOffsetUs, -20_000, 20_000);
}
[Fact]
public async Task Probe_FollowsTheAgentListThePanelKnows_AndSavesItForTheNextStart()
{
await using AgentHarness first = await AgentHarness.StartAsync(_backend);
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(first.Id)));
await using AgentHarness second = await AgentHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(second.Id)), TimeSpan.FromSeconds(30), "a new agent should be picked up through agents_changed: " + string.Join(";", probe.App.Directory.Agents.Select(a => $"{a.AgentId}@{a.Host}:{a.TcpPort}")) + " v" + probe.App.Directory.Version + " want " + second.Id);
Assert.Contains(probe.App.Directory.Agents, a => a.AgentId == second.Id);
Assert.True(File.Exists(Path.Combine(probe.Directory, "agents.json")));
// 停用一个 Agent:Probe 断开它
using HttpResponseMessage disabled = await _backend.Admin.PostAsync($"/api/admin/agents/{second.Id}/disable", BackendFixture.Json(new JsonObject()));
Assert.True(disabled.IsSuccessStatusCode, await disabled.Content.ReadAsStringAsync());
await BackendFixture.EventuallyAsync(() => Task.FromResult(!probe.App.IsConnected(second.Id)), TimeSpan.FromSeconds(30));
}
[Fact]
public async Task Hy2Ladder_WithTheRealProbe_Succeeds_AndEveryFragmentIsStored()
{
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
await agent.OverrideLineTasksAsync();
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue());
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{probe.ProbeId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "hy2_ladder", ["params"] = new JsonObject { ["size"] = 1000 } }));
Assert.True(accepted.StatusCode == System.Net.HttpStatusCode.Accepted, await accepted.Content.ReadAsStringAsync());
string testId = JsonNode.Parse(await accepted.Content.ReadAsStringAsync())!["id"]!.GetValue();
JsonNode test = null!;
await BackendFixture.EventuallyAsync(
async () =>
{
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
return test["status"]!.GetValue() is "succeeded" or "failed" or "aborted";
},
TimeSpan.FromSeconds(90));
Assert.True(test["status"]!.GetValue() == "succeeded", test.ToJsonString());
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "udp_train") >= 5, because: "50 and 100 Mbit/s phases are split into several fragments");
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
}
[Fact]
public async Task RealitySession_WithTheRealProbe_RunsTcpBulkAndStoresBytesAndKernelRows()
{
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
await agent.OverrideLineTasksAsync();
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue());
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{probe.ProbeId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "reality_session" }));
Assert.True(accepted.StatusCode == System.Net.HttpStatusCode.Accepted, await accepted.Content.ReadAsStringAsync());
string testId = JsonNode.Parse(await accepted.Content.ReadAsStringAsync())!["id"]!.GetValue();
JsonNode test = null!;
await BackendFixture.EventuallyAsync(
async () =>
{
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
return test["status"]!.GetValue() is "succeeded" or "failed" or "aborted";
},
TimeSpan.FromSeconds(150));
Assert.True(test["status"]!.GetValue() == "succeeded", test.ToJsonString());
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "tcp_bulk") >= 1, because: "the bulk phase stores its fragments");
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
}
}