Files
justme ad5efc62ff agent: unify listening and dialing with directed mutual measurements
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.
2026-10-21 11:55:36 +08:00

105 lines
4.9 KiB
C#

using Fleet.Core;
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;
/// <summary>public_host 和 ipv6_host 自动识别:Agent 上报公网地址,手填的优先,可以撤销手填。</summary>
public sealed class AddressTests : IClassFixture<BackendFixture>
{
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<JsonNode> AgentAsync(string id) => await _backend.GetJsonAsync(_backend.Admin, "/api/admin/agents/" + id);
private async Task<JsonNode> 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<string>() == V6);
Assert.Equal(V4, view["public_host"]!.GetValue<string>());
Assert.True(view["public_host_auto"]!.GetValue<bool>());
Assert.True(view["ipv6_host_auto"]!.GetValue<bool>());
}
[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<string>() == V6);
// 手填:标记为手动,之后 Agent 再上报别的地址也不覆盖
JsonNode manual = await PatchAsync(id, new JsonObject { ["public_host"] = "agent.example.net", ["ipv6_host"] = "" });
Assert.False(manual["public_host_auto"]!.GetValue<bool>());
Assert.False(manual["ipv6_host_auto"]!.GetValue<bool>());
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<string>());
Assert.Equal("", kept["ipv6_host"]!.GetValue<string>()); // 空 = 手动关闭 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<string>());
Assert.Equal("2606:4700:4700::1001", auto["ipv6_host"]!.GetValue<string>());
Assert.True(auto["public_host_auto"]!.GetValue<bool>());
}
[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<string>() == V4);
agent.Addresses = ["1.0.0.1", V4];
await agent.HelloAsync();
await Task.Delay(300);
Assert.Equal(V4, (await AgentAsync(id))["public_host"]!.GetValue<string>());
// 原来的地址不见了才换
agent.Addresses = ["1.0.0.1"];
await agent.HelloAsync();
await BackendFixture.EventuallyAsync(async () => (await AgentAsync(id))["public_host"]!.GetValue<string>() == "1.0.0.1");
}
[Fact]
public async Task DialTargets_CarryTheCertificatePublicKeyTheNodeEnrolledWith_ForPinningAndKnocking()
{
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
AgentService service = _backend.Services.GetRequiredService<AgentService>();
AgentRecord dialer = (await _backend.Services.GetRequiredService<IAgentStore>().GetAsync(new NodeId(_backend.DefaultDialerId), default))!;
DialTargets list = await service.DialTargetsAsync(dialer, default);
DialTarget endpoint = Assert.Single(list.Targets, t => t.NodeId == agent.NodeId);
Assert.Equal(Convert.ToBase64String(agent.TlsPublicKey), endpoint.PublicKey);
}
}