ci/woodpecker/push/main-amd64-build Pipeline was successful
- Group docs by category with an index (docs/README.md); fix every link, code comment and the protocol conformance test that read docs/protocol.md. - access.md and security.md rewritten to what is implemented: removed the unadopted member-proof chain, roles and recovery key that do not exist, the old implementation order and the budgets; gaps now carry a status. - enrollment, deployment, api: the install flow is the static, hash-checked script with environment variables; no default admin. - README and architecture: new topology and update-flow diagrams (operator passkey, CI signing, registry feed, auth and trust modules).
95 lines
4.0 KiB
C#
95 lines
4.0 KiB
C#
using System.Reflection;
|
|
using Fleet.Rpc;
|
|
using Fleet.Server;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using MoShiro.Probe;
|
|
using MoShiro.Protocol;
|
|
|
|
namespace MoShiro.Backend.Tests;
|
|
|
|
/// <summary>
|
|
/// 协议方向声明(<see cref="MoShiroMethodTable"/>)和实际代码的对账:每个方法常量都声明了,每一侧该处理的方法都注册了处理函数,
|
|
/// 二进制通道都有对应的收件箱,文档里写了每一个方法。新增方法绕不过声明,声明了没人处理也会被发现。
|
|
/// 注册处理函数时路由器本身还会核对方向、类型和角色,不符就在启动时失败。
|
|
/// </summary>
|
|
[Collection("probe-e2e")]
|
|
public sealed class ProtocolDeclarationTests
|
|
{
|
|
private static readonly MethodTable s_table = MoShiroMethodTable.Table;
|
|
|
|
private readonly BackendFixture _backend;
|
|
|
|
public ProtocolDeclarationTests(BackendFixture backend)
|
|
{
|
|
_backend = backend;
|
|
}
|
|
|
|
private static IEnumerable<string> ConstantsOf(Type type) =>
|
|
type.GetFields(BindingFlags.Public | BindingFlags.Static).Where(f => f.IsLiteral && f.FieldType == typeof(string)).Select(f => (string)f.GetRawConstantValue()!);
|
|
|
|
[Fact]
|
|
public void EveryMethodConstant_IsDeclared()
|
|
{
|
|
HashSet<string> declared = [.. s_table.Methods.Select(m => m.Name)];
|
|
string[] undeclared = [.. ConstantsOf(typeof(FleetMethods)).Concat(ConstantsOf(typeof(PeerMethods))).Concat(ConstantsOf(typeof(MoShiroMethods))).Where(n => !declared.Contains(n))];
|
|
Assert.Empty(undeclared);
|
|
}
|
|
|
|
[Fact]
|
|
public void EveryDeclaredMethod_IsDocumented()
|
|
{
|
|
string root = FindRoot();
|
|
string docs = File.ReadAllText(Path.Combine(root, "docs", "design", "protocol.md")) + File.ReadAllText(Path.Combine(root, "backend", "fleet", "README.md"));
|
|
string[] missing = [.. s_table.Methods.Select(m => m.Name).Distinct().Where(n => !docs.Contains($"`{n}`", StringComparison.Ordinal))];
|
|
Assert.Empty(missing);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backend_HandlesEveryMethodNodesMayInitiate_AndHasAnInboxForEveryDeclaredChannel()
|
|
{
|
|
FleetRouter router = _backend.Services.GetRequiredService<FleetRouter>();
|
|
Assert.Empty(router.Table.MissingHandlers(RpcSide.Server, null, router.Methods));
|
|
|
|
byte[] inboxes = [.. _backend.Services.GetServices<IInbox>().Select(i => i.Channel).Order()];
|
|
byte[] declared = [.. s_table.Channels.Where(c => c.Plane == RpcPlane.Node).Select(c => c.Channel).Order()];
|
|
Assert.Equal(declared, inboxes);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Agent_HandlesEveryMethodItShould_OnBothPlanes()
|
|
{
|
|
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
|
Assert.Empty(s_table.MissingHandlers(RpcSide.Node, NodeRoles.Agent, agent.App.Link.Router.Methods));
|
|
// 对等面:Agent 是监听方,要处理 Probe 发来的方法
|
|
Assert.Empty(s_table.MissingHandlers(RpcSide.Listener, null, agent.App.PeerRouter.Methods));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Probe_HandlesEveryMethodItShould_OnBothPlanes()
|
|
{
|
|
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
|
Assert.Empty(s_table.MissingHandlers(RpcSide.Node, NodeRoles.Probe, probe.App.Link.Router.Methods));
|
|
// 对等面:Probe 是拨号方,要处理 Agent 发来的方法
|
|
Assert.Empty(s_table.MissingHandlers(RpcSide.Dialer, null, AgentConnection.Router.Methods));
|
|
}
|
|
|
|
[Fact]
|
|
public void RolesOfNodeIds_FollowThePrefix()
|
|
{
|
|
Assert.Equal(NodeRoles.Agent, NodeRoles.Of(AgentIds.Prefix + "_01ABC"));
|
|
Assert.Equal(NodeRoles.Probe, NodeRoles.Of(ProbeId.Prefix + "_01ABC"));
|
|
Assert.Null(NodeRoles.Of("usr_01ABC"));
|
|
}
|
|
|
|
private static string FindRoot()
|
|
{
|
|
string? dir = AppContext.BaseDirectory;
|
|
while (dir is not null && !File.Exists(Path.Combine(dir, "docs", "design", "protocol.md")))
|
|
{
|
|
dir = Path.GetDirectoryName(dir);
|
|
}
|
|
|
|
return dir ?? throw new InvalidOperationException("repository root not found");
|
|
}
|
|
}
|