using System.Net; using Microsoft.Extensions.DependencyInjection; using MoShiro.Backend.Application; using MoShiro.Protocol; using System.Text.Json.Nodes; using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// public_host 和 ipv6_host 自动识别:Agent 上报公网地址,手填的优先,可以撤销手填。 [Collection("probe-e2e")] public sealed class AddressTests { private const string V4 = "8.8.4.4"; private const string V6 = "2606:4700:4700::1111"; private readonly BackendFixture _backend; public AddressTests(BackendFixture backend) { _backend = backend; } private async Task AgentAsync(string id) => await _backend.GetJsonAsync(_backend.Admin, "/api/admin/agents/" + id); private async Task PatchAsync(string id, JsonObject body) { using HttpResponseMessage response = await _backend.Admin.PatchAsync("/api/admin/agents/" + id, BackendFixture.Json(body)); string text = await response.Content.ReadAsStringAsync(); Assert.True(response.IsSuccessStatusCode, text); return JsonNode.Parse(text)!; } [Fact] public async Task Hello_FillsPublicHostAndIpv6Host_PrivateAndTemporaryAddressesAreIgnored() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(hello: false); agent.Addresses = ["10.0.0.5", "192.168.1.9", "fd00::1", "fe80::1", V6, V4]; await agent.HelloAsync(); JsonNode view = null!; await BackendFixture.EventuallyAsync(async () => (view = await AgentAsync(agent.NodeId.Value))["ipv6_host"]!.GetValue() == V6); Assert.Equal(V4, view["public_host"]!.GetValue()); Assert.True(view["public_host_auto"]!.GetValue()); Assert.True(view["ipv6_host_auto"]!.GetValue()); } [Fact] public async Task ManualValues_WinOverAutoDetection_AndCanBeRevokedBackToAuto() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(hello: false); string id = agent.NodeId.Value; agent.Addresses = [V4, V6]; await agent.HelloAsync(); await BackendFixture.EventuallyAsync(async () => (await AgentAsync(id))["ipv6_host"]!.GetValue() == V6); // 手填:标记为手动,之后 Agent 再上报别的地址也不覆盖 JsonNode manual = await PatchAsync(id, new JsonObject { ["public_host"] = "agent.example.net", ["ipv6_host"] = "" }); Assert.False(manual["public_host_auto"]!.GetValue()); Assert.False(manual["ipv6_host_auto"]!.GetValue()); agent.Addresses = ["1.1.1.1", "2606:4700:4700::1001"]; await agent.HelloAsync(); await Task.Delay(300); JsonNode kept = await AgentAsync(id); Assert.Equal("agent.example.net", kept["public_host"]!.GetValue()); Assert.Equal("", kept["ipv6_host"]!.GetValue()); // 空 = 手动关闭 IPv6 // 撤销手填:马上按最近一次上报的地址重新识别 JsonNode auto = await PatchAsync(id, new JsonObject { ["public_host_auto"] = true, ["ipv6_host_auto"] = true }); Assert.Equal("1.1.1.1", auto["public_host"]!.GetValue()); Assert.Equal("2606:4700:4700::1001", auto["ipv6_host"]!.GetValue()); Assert.True(auto["public_host_auto"]!.GetValue()); } [Fact] public async Task AutoDetectedAddress_StaysWhileItIsStillReported_SoReorderingDoesNotFlap() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(hello: false); string id = agent.NodeId.Value; agent.Addresses = [V4, "9.9.9.9"]; await agent.HelloAsync(); await BackendFixture.EventuallyAsync(async () => (await AgentAsync(id))["public_host"]!.GetValue() == V4); agent.Addresses = ["1.0.0.1", V4]; await agent.HelloAsync(); await Task.Delay(300); Assert.Equal(V4, (await AgentAsync(id))["public_host"]!.GetValue()); // 原来的地址不见了才换 agent.Addresses = ["1.0.0.1"]; await agent.HelloAsync(); await BackendFixture.EventuallyAsync(async () => (await AgentAsync(id))["public_host"]!.GetValue() == "1.0.0.1"); } [Fact] public async Task ProbeAgentList_CarriesTheCertificatePublicKeyTheAgentReported_ForPinningAndKnocking() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(); AgentService service = _backend.Services.GetRequiredService(); ProbeAgentList list = await service.ProbeAgentsAsync(_backend.DefaultProbeId, default); AgentEndpoint endpoint = Assert.Single(list.Agents, a => a.AgentId == agent.NodeId); Assert.Equal(Convert.ToBase64String(agent.TlsPublicKey), endpoint.PublicKey); } }