Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry. Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
91 lines
5.6 KiB
C#
91 lines
5.6 KiB
C#
using System.Text.Json.Nodes;
|
||
|
||
namespace MoShiro.Backend.Tests;
|
||
|
||
/// <summary>真实的后端 + 真实的 Agent + 真实的拨号侧 Agent(都在进程内,回环网络)。</summary>
|
||
// 慢的端到端测试(真实时间的阶梯、要等测量块出来):CI 里单独一批跑(Category=Slow),不拦着出镜像,见 ci/verify-backend.sh
|
||
[Trait("Category", "Slow")]
|
||
public sealed class DialerEndToEndTests : IClassFixture<BackendFixture>
|
||
{
|
||
private readonly BackendFixture _backend;
|
||
|
||
public DialerEndToEndTests(BackendFixture backend)
|
||
{
|
||
_backend = backend;
|
||
}
|
||
|
||
private async Task<JsonNode> AgentViewAsync(AgentHarness agent) => await _backend.GetJsonAsync(_backend.Admin, "/api/admin/agents/" + agent.Id);
|
||
|
||
private async Task<int> 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<string>() == kind);
|
||
|
||
[Fact]
|
||
public async Task Dialer_FollowsTheAgentListThePanelKnows_AndSavesItForTheNextStart()
|
||
{
|
||
await using AgentHarness first = await AgentHarness.StartAsync(_backend);
|
||
await using DialerHarness dialer = await DialerHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(dialer.App.Dialer.IsConnected(first.Id)));
|
||
|
||
await using AgentHarness second = await AgentHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(dialer.App.Dialer.IsConnected(second.Id)), TimeSpan.FromSeconds(30), "a new Agent should arrive through dial_targets state sync: " + string.Join(";", dialer.App.Dialer.Directory.Targets.Select(a => $"{a.NodeId}@{a.Host}:{a.TcpPort}")) + " v" + dialer.App.Dialer.Directory.Version + " want " + second.Id);
|
||
Assert.Contains(dialer.App.Dialer.Directory.Targets, a => a.NodeId == second.Id);
|
||
Assert.True(File.Exists(Path.Combine(dialer.Directory, "dial_targets.json")));
|
||
|
||
// 停用一个 Agent:Dialer 断开它
|
||
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(!dialer.App.Dialer.IsConnected(second.Id)), TimeSpan.FromSeconds(30));
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Hy2Ladder_WithTheRealDialer_Succeeds_AndEveryFragmentIsStored()
|
||
{
|
||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync();
|
||
await using DialerHarness dialer = await DialerHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["dialer_connected"]!.GetValue<bool>());
|
||
|
||
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{dialer.DialerId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "hy2_ladder", ["params"] = new JsonObject { ["size"] = 1000, ["duration_s"] = 5 } }));
|
||
Assert.True(accepted.StatusCode == System.Net.HttpStatusCode.Accepted, await accepted.Content.ReadAsStringAsync());
|
||
string testId = JsonNode.Parse(await accepted.Content.ReadAsStringAsync())!["id"]!.GetValue<string>();
|
||
|
||
JsonNode test = null!;
|
||
await BackendFixture.EventuallyAsync(
|
||
async () =>
|
||
{
|
||
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
|
||
return test["status"]!.GetValue<string>() is "succeeded" or "failed" or "aborted";
|
||
},
|
||
TimeSpan.FromSeconds(90));
|
||
Assert.True(test["status"]!.GetValue<string>() == "succeeded", test.ToJsonString());
|
||
|
||
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "udp_train") >= 5, because: "the 100 Mbit/s phase (5 s of 1000 B packets, over 50000 records) is split into two fragments");
|
||
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task RealitySession_WithTheRealDialer_RunsTcpBulkAndStoresBytesAndKernelRows()
|
||
{
|
||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync();
|
||
await using DialerHarness dialer = await DialerHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["dialer_connected"]!.GetValue<bool>());
|
||
|
||
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{dialer.DialerId}/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<string>();
|
||
|
||
JsonNode test = null!;
|
||
await BackendFixture.EventuallyAsync(
|
||
async () =>
|
||
{
|
||
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
|
||
return test["status"]!.GetValue<string>() is "succeeded" or "failed" or "aborted";
|
||
},
|
||
TimeSpan.FromSeconds(150));
|
||
Assert.True(test["status"]!.GetValue<string>() == "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);
|
||
}
|
||
}
|