feat(runtime): track node lifecycle and QUIC link state

This commit is contained in:
justme committed 2026-10-10 14:16:57 +08:00
1 parent 238046d959
commit 97ab3cc740
37 files changed
+864 -35

No files matched your search

@@ -36,6 +36,20 @@ public sealed class AgentEndToEndTests : IClassFixture<BackendFixture>
await using ProbeSimulator probe = await agent.ConnectProbeAsync();
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue<bool>());
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["runtime_status"]!.GetValue<string>() == "running");
}
[Fact]
public async Task Agent_OrderlyShutdownIsReportedAsStoppingRatherThanOffline()
{
AgentHarness agent = await AgentHarness.StartAsync(_backend);
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["runtime_status"]!.GetValue<string>() == "running");
await agent.DisposeAsync();
await BackendFixture.EventuallyAsync(async () => !(await AgentViewAsync(agent))["online"]!.GetValue<bool>());
JsonNode final = await AgentViewAsync(agent);
Assert.Equal("stopping", final["runtime_status"]!.GetValue<string>());
}
[Fact]
@@ -77,4 +77,19 @@ public sealed class LinkTests : IClassFixture<BackendFixture>
using HttpResponseMessage noAgent = await SetLinkAsync(probe, "agt_missing", false);
Assert.Equal(HttpStatusCode.NotFound, noAgent.StatusCode);
}
[Fact]
public async Task Links_RetryEnablesPairAndReportsWhenProbeIsOffline()
{
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
string probe = await _backend.CreateProbeIdAsync();
using HttpResponseMessage response = await _backend.Admin.PostAsync($"/api/admin/links/{probe}/{agent.NodeId.Value}/retry", BackendFixture.Json(new JsonObject()));
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
JsonNode body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;
Assert.False(body["queued"]!.GetValue<bool>());
Assert.True(body["link"]!["linked"]!.GetValue<bool>());
Assert.Equal("probe_unavailable", body["link"]!["connection_state"]!.GetValue<string>());
Assert.Contains("Probe 状态", body["link"]!["connection_reason"]!.GetValue<string>());
}
}
@@ -48,6 +48,7 @@ public sealed class ProbeEndToEndTests : IClassFixture<BackendFixture>
// 直连面板:在线,hello 里的系统、架构和版本都被记下来了
await BackendFixture.EventuallyAsync(async () => (await _backend.GetJsonAsync(_backend.Admin, path))["online"]!.GetValue<bool>(), because: "the probe links to the panel");
await BackendFixture.EventuallyAsync(async () => (await _backend.GetJsonAsync(_backend.Admin, path))["runtime_status"]!.GetValue<string>() == "probe_paused", because: "a Probe without any enabled Agent endpoints reports paused probing");
await BackendFixture.EventuallyAsync(
async () => (await _backend.GetJsonAsync(_backend.Anonymous, "/api/public/nodes"))["items"]!.AsArray().Any(n => n!["id"]!.GetValue<string>() == probe.ProbeId && n["os"] is not null && n["arch"] is not null),
because: "the platform comes from the probe's own hello");
@@ -58,6 +59,7 @@ public sealed class ProbeEndToEndTests : IClassFixture<BackendFixture>
await BackendFixture.EventuallyAsync(async () =>
!(await _backend.GetJsonAsync(_backend.Admin, path))["online"]!.GetValue<bool>() && !probe.App.Link.IsConnected,
because: "a revoked probe is disconnected and cannot come back");
Assert.Equal("maintenance", (await _backend.GetJsonAsync(_backend.Admin, path))["runtime_status"]!.GetValue<string>());
}
[Fact]