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);
}
}