refactor!: rename the project from Monitor to MoShiro

Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
This commit is contained in:
justme committed 2026-10-07 01:49:04 +08:00
1 parent 17b35223aa
commit 83e5120377
309 files changed
+1505 -1499

No files matched your search

@@ -1,6 +1,6 @@
using Monitor.Backend.Analysis;
using MoShiro.Backend.Analysis;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class AccumulatorTests
{
@@ -6,8 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" />
<ProjectReference Include="..\..\src\Monitor.Backend.Analysis\Monitor.Backend.Analysis.csproj" />
<ProjectReference Include="..\Monitor.TestSupport\Monitor.TestSupport.csproj" />
<ProjectReference Include="..\..\src\MoShiro.Backend.Analysis\MoShiro.Backend.Analysis.csproj" />
<ProjectReference Include="..\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -1,8 +1,8 @@
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class ModelAndTestEvaluatorTests
{
@@ -1,8 +1,8 @@
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class OpTests
{
@@ -1,9 +1,9 @@
using System.Text.Json.Nodes;
using Monitor.Measurement;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Measurement;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>真实的后端 + 真实的 Agent(进程内)+ 假 Probe:覆盖 Agent 与 Backend 的链路、Probe 与 Agent 的点对点会话和数据面。</summary>
[Collection("backend")]
@@ -3,11 +3,11 @@ using Fleet.Core;
using Fleet.Enrollment.Client;
using Fleet.Hosting;
using Fleet.Identity;
using Monitor.Agent;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Agent;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 进程内的真实 Agent:用 <see cref="EnrollmentClient"/> 注册(和 <c>fleet-enroll</c> 同一份代码)、写出身份目录、
@@ -43,7 +43,7 @@ public sealed class AgentHarness : IAsyncDisposable
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null)
{
string directory = Path.Combine(Path.GetTempPath(), "monitor-agent-" + Guid.NewGuid().ToString("N"));
string directory = Path.Combine(Path.GetTempPath(), "moshiro-agent-" + Guid.NewGuid().ToString("N"));
string token = await backend.CreateJoinTokenAsync();
using var http = new HttpClient();
Result<EnrollmentResult> enrolled = await EnrollmentClient.EnrollAsync(http, backend.BaseAddress, token, name ?? backend.UniqueName("agent"), directory, AgentApp.Version, null, TimeProvider.System, CancellationToken.None);
@@ -1,24 +1,24 @@
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 对一个正在运行的 NativeAOT 发布产物跑一遍真实的 Agent 流程(注册、WebSocket、状态同步、二进制收件箱、查询、SSE、按需测试)。
/// 只有设置了 <c>MONITOR_AOT_URL</c> 才会运行:
/// <c>dotnet publish backend/src/Monitor.Backend -r win-x64 -p:PublishAot=true</c>,启动产物,再设置该环境变量后运行本测试。
/// 只有设置了 <c>MOSHIRO_AOT_URL</c> 才会运行:
/// <c>dotnet publish backend/src/MoShiro.Backend -r win-x64 -p:PublishAot=true</c>,启动产物,再设置该环境变量后运行本测试。
/// 管理员凭据与测试夹具相同(用户 admin,口令见 <see cref="BackendFixture.AdminPassword"/>)。
/// </summary>
public sealed class AotSmokeTests
{
private static string? Url => Environment.GetEnvironmentVariable("MONITOR_AOT_URL");
private static string? Url => Environment.GetEnvironmentVariable("MOSHIRO_AOT_URL");
[Fact]
public async Task TheNativeBinary_ServesTheWholeAgentWorkflow()
{
Assert.SkipUnless(Url is not null, "set MONITOR_AOT_URL to the address of a running NativeAOT build to run this test");
Assert.SkipUnless(Url is not null, "set MOSHIRO_AOT_URL to the address of a running NativeAOT build to run this test");
var baseAddress = new Uri(Url!);
using var http = new HttpClient { BaseAddress = baseAddress };
@@ -2,7 +2,7 @@ using System.Net;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class ApiBasicsTests
@@ -217,8 +217,8 @@ public sealed class ApiBasicsTests
using HttpResponseMessage allowed = await _backend.Admin.GetAsync("/metrics");
string text = await allowed.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, allowed.StatusCode);
Assert.Contains("monitor_agents_online", text);
Assert.Contains("monitor_analysis_queue_length", text);
Assert.Contains("moshiro_agents_online", text);
Assert.Contains("moshiro_analysis_queue_length", text);
}
[Fact]
@@ -1,6 +1,6 @@
using System.Xml.Linq;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 依赖规则(docs/project-layout.md 第 3 节):由测试在构建期强制,等价于文档里的 <c>eng/check-deps</c>。
@@ -17,7 +17,8 @@ public sealed class ArchitectureTests
private static string FindRoot()
{
string? dir = AppContext.BaseDirectory;
while (dir is not null && !File.Exists(Path.Combine(dir, "global.json")))
// backend/ 目录的标志:BannedSymbols.txt 只在这里(global.json 和 NuGet 版本表已经挪到仓库根目录)
while (dir is not null && !File.Exists(Path.Combine(dir, "BannedSymbols.txt")))
{
dir = Path.GetDirectoryName(dir);
}
@@ -48,17 +49,17 @@ public sealed class ArchitectureTests
private static bool IsFleet(Project p) => p.Name.StartsWith("Fleet.", StringComparison.Ordinal) && !IsTest(p);
[Fact]
public void Fleet_NeverReferencesMonitor_InProjectsOrCode()
public void Fleet_NeverReferencesMoShiro_InProjectsOrCode()
{
foreach (Project project in LoadProjects().Where(IsFleet))
{
Assert.DoesNotContain(project.ProjectReferences, r => r.StartsWith("Monitor.", StringComparison.Ordinal));
Assert.DoesNotContain(project.ProjectReferences, r => r.StartsWith("MoShiro.", StringComparison.Ordinal));
foreach (string file in Directory.EnumerateFiles(project.Directory, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
{
string text = File.ReadAllText(file);
Assert.False(text.Contains("using Monitor.", StringComparison.Ordinal) || text.Contains("namespace Monitor.", StringComparison.Ordinal), $"{file} refers to Monitor.*");
Assert.False(text.Contains("monitor.", StringComparison.Ordinal) && text.Contains("\"monitor.", StringComparison.Ordinal), $"{file} hard-codes a Monitor method or channel name");
Assert.False(text.Contains("7443", StringComparison.Ordinal), $"{file} hard-codes a Monitor port");
Assert.False(text.Contains("using MoShiro.", StringComparison.Ordinal) || text.Contains("namespace MoShiro.", StringComparison.Ordinal), $"{file} refers to MoShiro.*");
Assert.False(text.Contains("moshiro.", StringComparison.Ordinal) && text.Contains("\"moshiro.", StringComparison.Ordinal), $"{file} hard-codes a MoShiro method or channel name");
Assert.False(text.Contains("7443", StringComparison.Ordinal), $"{file} hard-codes a MoShiro port");
}
}
}
@@ -66,7 +67,7 @@ public sealed class ArchitectureTests
[Fact]
public void NodeSideLibraries_HaveNoRuntimePackages_AndUseTheNodeBanList()
{
string[] nodeSide = ["Fleet.Core", "Fleet.Identity", "Fleet.Rpc", "Monitor.Protocol", "Monitor.Backend.Analysis"];
string[] nodeSide = ["Fleet.Core", "Fleet.Identity", "Fleet.Rpc", "MoShiro.Protocol", "MoShiro.Backend.Analysis"];
foreach (Project project in LoadProjects().Where(p => nodeSide.Contains(p.Name)))
{
Assert.All(project.Packages, package => Assert.Contains(package, s_allowedAnalyzers));
@@ -83,8 +84,8 @@ public sealed class ArchitectureTests
public void Protocol_DependsOnlyOnFleetCoreAndRpc_AndAnalysisOnlyOnProtocol()
{
List<Project> projects = LoadProjects();
Assert.Equal(["Fleet.Core", "Fleet.Rpc"], projects.Single(p => p.Name == "Monitor.Protocol").ProjectReferences.Order().ToArray());
Assert.Equal(["Monitor.Protocol"], projects.Single(p => p.Name == "Monitor.Backend.Analysis").ProjectReferences.ToArray());
Assert.Equal(["Fleet.Core", "Fleet.Rpc"], projects.Single(p => p.Name == "MoShiro.Protocol").ProjectReferences.Order().ToArray());
Assert.Equal(["MoShiro.Protocol"], projects.Single(p => p.Name == "MoShiro.Backend.Analysis").ProjectReferences.ToArray());
}
[Fact]
@@ -94,19 +95,19 @@ public sealed class ArchitectureTests
Project Get(string name) => projects.Single(p => p.Name == name);
// Agent 是纯采集进程:不引用后端的任何层,也不引用 Fleet 的服务端库
Project agent = Get("Monitor.Agent");
Assert.DoesNotContain(agent.ProjectReferences, r => r.StartsWith("Monitor.Backend", StringComparison.Ordinal) || r.StartsWith("Fleet.Server", StringComparison.Ordinal) || r.StartsWith("Fleet.Enrollment", StringComparison.Ordinal));
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "Monitor.Measurement"], agent.ProjectReferences.Order().ToArray());
Project agent = Get("MoShiro.Agent");
Assert.DoesNotContain(agent.ProjectReferences, r => r.StartsWith("MoShiro.Backend", StringComparison.Ordinal) || r.StartsWith("Fleet.Server", StringComparison.Ordinal) || r.StartsWith("Fleet.Enrollment", StringComparison.Ordinal));
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "MoShiro.Measurement"], agent.ProjectReferences.Order().ToArray());
// Probe 同理,并且不认识 Backend 和 Agent
Project probe = Get("Monitor.Probe");
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "Monitor.Measurement"], probe.ProjectReferences.Order().ToArray());
Project probe = Get("MoShiro.Probe");
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "MoShiro.Measurement"], probe.ProjectReferences.Order().ToArray());
// 两个节点的共享库只依赖协议
Assert.Equal(["Monitor.Protocol"], Get("Monitor.Measurement").ProjectReferences.ToArray());
Assert.Equal(["MoShiro.Protocol"], Get("MoShiro.Measurement").ProjectReferences.ToArray());
// 节点一侧的库没有运行时依赖包,并使用节点禁用列表(不能用 HttpClient、Process、写文件;唯一的写文件豁免在 ConfigStore)
foreach (string name in new[] { "Fleet.Link", "Fleet.Hosting", "Monitor.Measurement", "Monitor.Agent", "Monitor.Probe" })
foreach (string name in new[] { "Fleet.Link", "Fleet.Hosting", "MoShiro.Measurement", "MoShiro.Agent", "MoShiro.Probe" })
{
Project project = Get(name);
Assert.All(project.Packages, package => Assert.Contains(package, s_allowedAnalyzers));
@@ -126,7 +127,7 @@ public sealed class ArchitectureTests
{
// 采集层(协议、积木、Agent、Probe、Fleet)只认识机制(传输、节奏、方向、时间戳)和原始列;
// REALITY、Hysteria2、Minecraft 这些业务概念以及由原语编排成的测试方案只存在于 Backend。
string[] collectionRoots = ["shared/Monitor.Protocol", "shared/Monitor.Measurement", "agent/src", "probe/src", "fleet/src"];
string[] collectionRoots = ["shared/MoShiro.Protocol", "shared/MoShiro.Measurement", "agent/src", "probe/src", "fleet/src"];
string[] forbidden = ["reality", "hysteria", "hy2", "minecraft", "brutal", "PlanSpec", "PhaseSpec"];
foreach (string root in collectionRoots)
{
@@ -142,7 +143,7 @@ public sealed class ArchitectureTests
}
// 派生量(丢包率、RTT、抖动)由 Backend 的分析层算:采集层的积木不引用分析层
Assert.DoesNotContain(LoadProjects().Single(p => p.Name == "Monitor.Measurement").ProjectReferences, r => r.Contains("Analysis", StringComparison.Ordinal));
Assert.DoesNotContain(LoadProjects().Single(p => p.Name == "MoShiro.Measurement").ProjectReferences, r => r.Contains("Analysis", StringComparison.Ordinal));
}
[Fact]
@@ -152,22 +153,22 @@ public sealed class ArchitectureTests
Project Get(string name) => projects.Single(p => p.Name == name);
// Api 不能引用 Storage(也不引用 Gateway):只调用 Application
Assert.DoesNotContain(Get("Monitor.Backend.Api").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.Equal(["Monitor.Backend.Application"], Get("Monitor.Backend.Api").ProjectReferences.ToArray());
Assert.DoesNotContain(Get("MoShiro.Backend.Api").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.Equal(["MoShiro.Backend.Application"], Get("MoShiro.Backend.Api").ProjectReferences.ToArray());
// Application 不依赖任何下游层
Assert.DoesNotContain(Get("Monitor.Backend.Application").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Application").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
// Gateway 只通过 Application 与 Fleet.Server 工作
Assert.DoesNotContain(Get("Monitor.Backend.Gateway").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Backend.Api", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Gateway").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Backend.Api", StringComparison.Ordinal));
// Storage 实现 Application 的端口,不认识 Api 和 Gateway
Assert.DoesNotContain(Get("Monitor.Backend.Storage.Sqlite").ProjectReferences, r => r.Contains("Backend.Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Storage.Sqlite").ProjectReferences, r => r.Contains("Backend.Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
// 只有宿主(组合根)认识所有层
Assert.Equivalent(
new[] { "Monitor.Backend.Api", "Monitor.Backend.Gateway", "Monitor.Backend.Storage.Sqlite" },
Get("Monitor.Backend").ProjectReferences.ToArray());
new[] { "MoShiro.Backend.Api", "MoShiro.Backend.Gateway", "MoShiro.Backend.Storage.Sqlite" },
Get("MoShiro.Backend").ProjectReferences.ToArray());
}
[Fact]
@@ -175,7 +176,7 @@ public sealed class ArchitectureTests
{
string[] allowed =
[
"Microsoft.CodeAnalysis.BannedApiAnalyzers", "Microsoft.Data.Sqlite", "Microsoft.AspNetCore.OpenApi",
"Microsoft.CodeAnalysis.BannedApiAnalyzers", "Microsoft.Data.Sqlite", "SQLitePCLRaw.lib.e_sqlite3", "Microsoft.AspNetCore.OpenApi",
"Microsoft.Extensions.ApiDescription.Server", "xunit.v3", "Microsoft.Extensions.TimeProvider.Testing",
];
foreach (Project project in LoadProjects())
@@ -187,7 +188,7 @@ public sealed class ArchitectureTests
[Fact]
public void ApplicationAndApi_NeverTouchSqliteOrTheFileSystemDirectly()
{
foreach (string layer in new[] { "Monitor.Backend.Application", "Monitor.Backend.Api", "Monitor.Backend.Gateway", "Monitor.Backend.Analysis" })
foreach (string layer in new[] { "MoShiro.Backend.Application", "MoShiro.Backend.Api", "MoShiro.Backend.Gateway", "MoShiro.Backend.Analysis" })
{
string dir = Path.Combine(s_root, "src", layer);
foreach (string file in Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
@@ -204,7 +205,7 @@ public sealed class ArchitectureTests
public void NoProductionCode_BuildsSqlByConcatenatingValues()
{
// 手写的参数化 SQL:只有表名可以来自常量;值一律通过参数
string dir = Path.Combine(s_root, "src", "Monitor.Backend.Storage.Sqlite");
string dir = Path.Combine(s_root, "src", "MoShiro.Backend.Storage.Sqlite");
foreach (string file in Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
{
string text = File.ReadAllText(file);
@@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Monitor.Backend.Application;
using Monitor.TestSupport;
using MoShiro.Backend.Application;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 在真实的 Kestrel 端口上启动整个后端(真实的 SQLite 与段文件,只是用临时目录和较快的轮询),
@@ -58,7 +58,7 @@ public class BackendFixture : IAsyncLifetime
public async ValueTask InitializeAsync()
{
DataDirectory = Path.Combine(Path.GetTempPath(), "monitor-tests-" + Guid.NewGuid().ToString("N"));
DataDirectory = Path.Combine(Path.GetTempPath(), "moshiro-tests-" + Guid.NewGuid().ToString("N"));
await StartAsync();
_adminToken = await CreateAdminTokenAsync();
CreateClients();
@@ -100,7 +100,7 @@ public class BackendFixture : IAsyncLifetime
public async Task<AgentSimulator> ReconnectAsync(AgentSimulator previous)
{
AgentSimulator agent = AgentSimulator.Restore(previous.ExportedKey, previous.NodeId);
agent.ProbeForChunks = new Monitor.Protocol.ProbeId(DefaultProbeId);
agent.ProbeForChunks = new MoShiro.Protocol.ProbeId(DefaultProbeId);
_agents.Add(agent);
await agent.ConnectAsync(BaseAddress);
return agent;
@@ -129,7 +129,7 @@ public class BackendFixture : IAsyncLifetime
{
string token = await CreateJoinTokenAsync(autoApprove);
AgentSimulator agent = await AgentSimulator.EnrollAsync(Anonymous, token, name ?? UniqueName("agent"), kinds);
agent.ProbeForChunks = new Monitor.Protocol.ProbeId(DefaultProbeId);
agent.ProbeForChunks = new MoShiro.Protocol.ProbeId(DefaultProbeId);
_agents.Add(agent);
await agent.ConnectAsync(BaseAddress, hello, kinds);
return agent;
@@ -1,9 +1,9 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class EnrollmentAndTaskSetTests
@@ -150,7 +150,7 @@ public sealed class EnrollmentAndTaskSetTests
RawChunk chunk = ChunkFactory.UdpEchoClean(agent.NodeId, TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds() - 30_000);
chunk.Header.AgentId = agent.NodeId;
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk)));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk)));
await Task.Delay(300);
JsonNode chunks = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/raw/chunks?agent_id=" + agent.NodeId.Value);
Assert.Empty(chunks["items"]!.AsArray());
@@ -1,9 +1,9 @@
using System.Text.Json.Nodes;
using Monitor.Agent;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Agent;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed class HostSamplerTests
{
@@ -2,10 +2,10 @@ using System.Net;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class IngestAndQueryTests
@@ -120,11 +120,11 @@ public sealed class IngestAndQueryTests
var ack = new TaskCompletionSource<bool>();
byte[] corrupt = RawChunkCodec.Encode(ChunkFactory.UdpEchoClean(agent.NodeId, start));
corrupt[^1] ^= 0xFF;
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, garbage.Value, corrupt));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, garbage.Value, corrupt));
// 冒名的块:agent_id 与认证的节点不一致
RawChunk spoofed = ChunkFactory.UdpEchoClean(new NodeId("agt_someone_else"), start);
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, spoofed.Header.ChunkId.Value, RawChunkCodec.Encode(spoofed)));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, spoofed.Header.ChunkId.Value, RawChunkCodec.Encode(spoofed)));
await BackendFixture.EventuallyAsync(async () =>
{
@@ -1,8 +1,8 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.TestSupport;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("probe-e2e")]
public sealed class LinkTests
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class LiveTests
@@ -1,7 +1,7 @@
using System.Net;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>登录限流按来源地址计,会影响同一地址上的其他登录,所以用独立的后端实例。</summary>
public sealed class LoginRateLimitTests : IClassFixture<StrictLoginFixture>
@@ -8,11 +8,11 @@
<ItemGroup>
<PackageReference Include="xunit.v3" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Include="..\..\src\Monitor.Backend\Monitor.Backend.csproj" />
<ProjectReference Include="..\Monitor.TestSupport\Monitor.TestSupport.csproj" />
<ProjectReference Include="..\..\agent\src\Monitor.Agent\Monitor.Agent.csproj" />
<ProjectReference Include="..\..\src\MoShiro.Backend\MoShiro.Backend.csproj" />
<ProjectReference Include="..\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
<ProjectReference Include="..\..\agent\src\MoShiro.Agent\MoShiro.Agent.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Enrollment.Client\Fleet.Enrollment.Client.csproj" />
<ProjectReference Include="..\..\probe\src\Monitor.Probe\Monitor.Probe.csproj" />
<ProjectReference Include="..\..\probe\src\MoShiro.Probe\MoShiro.Probe.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -2,10 +2,10 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Time.Testing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed partial class NodeInfoTests
{
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class OnDemandTestsTests
@@ -233,7 +233,7 @@ public sealed class OnDemandTestsTests
Assert.Equal("line:" + _backend.DefaultProbeId, item["series"]!.GetValue<string>());
using HttpResponseMessage download = await _backend.Admin.GetAsync("/api/admin/raw/chunks/" + chunk.Header.ChunkId);
Assert.Equal("application/vnd.monitor.rawchunk", download.Content.Headers.ContentType!.MediaType);
Assert.Equal("application/vnd.moshiro.rawchunk", download.Content.Headers.ContentType!.MediaType);
RawChunk decoded = RawChunkCodec.Decode(await download.Content.ReadAsByteArrayAsync()).Value;
Assert.Equal(chunk.Header.ChunkId, decoded.Header.ChunkId);
Assert.Equal(100, decoded.Find("msg")!.Count);
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 对照 docs/api-guidelines.md 检查运行时生成的 OpenAPI 文档:operationId、路径风格、错误响应格式、字段命名。
@@ -5,12 +5,12 @@ using Fleet.Core;
using Fleet.Rpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class OperationsAndSecurityTests
@@ -169,7 +169,7 @@ public sealed class OperationsAndSecurityTests
await agent.SendBinaryAsync(OutboxFrame.Build(99, Guid.NewGuid(), [1])); // 没有注册的通道
// 节点不能调用任何“执行”类的方法:Fleet 根本没有这样的路由
foreach (string method in new[] { "fleet.exec", "fleet.shell", "monitor.exec", "monitor.write_file", "fleet.update" })
foreach (string method in new[] { "fleet.exec", "fleet.shell", "moshiro.exec", "moshiro.write_file", "fleet.update" })
{
RpcMessage reply = await agent.RequestRawAsync(method);
Assert.Equal("fleet_unsupported", reply.Error!.Data!.Code);
@@ -1,8 +1,8 @@
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>重启后端:数据、身份和令牌都必须还在;热层从原始块回填;段文件尾部没有索引的数据被截断。</summary>
public sealed class PersistenceTests : IAsyncLifetime
@@ -1,8 +1,8 @@
using System.Text.Json;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed class PlanTests
{
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。</summary>
[Collection("probe-e2e")]
@@ -1,9 +1,9 @@
using System.Text;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Probe;
using MoShiro.Probe;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>进程内的真实 Probe:用后端生成的身份文件(和面板下载的 probe.json 同一份)启动 <see cref="ProbeApp"/>。</summary>
public sealed class ProbeHarness : IAsyncDisposable
@@ -31,7 +31,7 @@ public sealed class ProbeHarness : IAsyncDisposable
JsonNode created = await backend.CreateProbeAsync();
string json = created.ToJsonString();
string directory = Path.Combine(Path.GetTempPath(), "monitor-probe-" + Guid.NewGuid().ToString("N"));
string directory = Path.Combine(Path.GetTempPath(), "moshiro-probe-" + Guid.NewGuid().ToString("N"));
System.IO.Directory.CreateDirectory(directory);
string path = Path.Combine(directory, "probe.json");
await File.WriteAllTextAsync(path, json, Encoding.UTF8);
@@ -1,10 +1,10 @@
using Fleet.Core;
using Microsoft.Extensions.Time.Testing;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>时间段、步长与数据源的规划:与 docs/query.md 里的表格逐行对应。</summary>
public sealed class QueryPlanningTests
@@ -127,18 +127,18 @@ public sealed class QueryPlanningTests
{
Result<QueryRange> result = Resolver.Resolve(new RangeRequest(range, null, null, null, null), s_now);
Assert.False(result.IsSuccess);
Assert.Contains(result.Error.Code, new[] { MonitorErrors.InvalidRange, MonitorErrors.RangeTooLarge });
Assert.Contains(result.Error.Code, new[] { MoShiroErrors.InvalidRange, MoShiroErrors.RangeTooLarge });
}
[Fact]
public void RangeResolver_EnforcesThePointLimit_AndSuggestsAStep()
{
Result<QueryRange> result = Resolver.Resolve(new RangeRequest("30d", null, null, "1m", null), s_now);
Assert.Equal(MonitorErrors.RangeTooLarge, result.Error.Code);
Assert.Equal(MoShiroErrors.RangeTooLarge, result.Error.Code);
Assert.Contains("step=", result.Error.Message);
Assert.Equal(MonitorErrors.InvalidStep, Resolver.Resolve(new RangeRequest("1h", null, null, "7s", null), s_now).Error.Code);
Assert.Equal(MonitorErrors.InvalidRange, Resolver.Resolve(new RangeRequest(null, DateTimeOffset.FromUnixTimeMilliseconds(s_now), DateTimeOffset.FromUnixTimeMilliseconds(s_now), null, null), s_now).Error.Code);
Assert.Equal(MonitorErrors.ValidationFailed, Resolver.Resolve(new RangeRequest("1h", DateTimeOffset.FromUnixTimeMilliseconds(s_now - Hour), null, null, null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.InvalidStep, Resolver.Resolve(new RangeRequest("1h", null, null, "7s", null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.InvalidRange, Resolver.Resolve(new RangeRequest(null, DateTimeOffset.FromUnixTimeMilliseconds(s_now), DateTimeOffset.FromUnixTimeMilliseconds(s_now), null, null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.ValidationFailed, Resolver.Resolve(new RangeRequest("1h", DateTimeOffset.FromUnixTimeMilliseconds(s_now - Hour), null, null, null), s_now).Error.Code);
}
[Fact]
@@ -147,7 +147,7 @@ public sealed class QueryPlanningTests
Assert.True(Resolver.Resolve(new RangeRequest("1h", null, null, null, "UTC"), s_now).IsSuccess);
Assert.True(Resolver.Resolve(new RangeRequest("1h", null, null, null, null), s_now).IsSuccess);
Result<QueryRange> shanghai = Resolver.Resolve(new RangeRequest("7d", null, null, "1d", "Asia/Shanghai"), s_now);
Assert.Equal(MonitorErrors.ValidationFailed, shanghai.Error.Code);
Assert.Equal(MoShiroErrors.ValidationFailed, shanghai.Error.Code);
Assert.IsType<ValidationError>(shanghai.Error);
}
@@ -209,10 +209,10 @@ public sealed class QueryPlanningTests
Assert.Equal(selection.ExportIds.Count, selection.ExportIds.Distinct().Count());
Assert.True(selection.WantsScores);
Assert.Equal(MonitorErrors.UnknownMetric, MetricSelection.Resolve("nope", null, 100).Error.Code);
Assert.Equal(MonitorErrors.UnknownMetric, MetricSelection.Resolve("score.nope", null, 100).Error.Code);
Assert.Equal(MonitorErrors.UnknownPreset, MetricSelection.Resolve(null, "nope", 100).Error.Code);
Assert.Equal(MonitorErrors.TooManyMetrics, MetricSelection.Resolve(null, "udp", 3).Error.Code);
Assert.Equal(MoShiroErrors.UnknownMetric, MetricSelection.Resolve("nope", null, 100).Error.Code);
Assert.Equal(MoShiroErrors.UnknownMetric, MetricSelection.Resolve("score.nope", null, 100).Error.Code);
Assert.Equal(MoShiroErrors.UnknownPreset, MetricSelection.Resolve(null, "nope", 100).Error.Code);
Assert.Equal(MoShiroErrors.TooManyMetrics, MetricSelection.Resolve(null, "udp", 3).Error.Code);
}
[Fact]
@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed record SseEvent(string Name, JsonNode? Data);
@@ -2,20 +2,20 @@ using System.Text;
using Fleet.Core;
using Fleet.Server;
using Microsoft.Data.Sqlite;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Backend.Storage.Sqlite;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Backend.Storage.Sqlite;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>存储层:段文件与索引、指标窗口、队列、节点与令牌。直接使用真实的 SQLite 和临时目录。</summary>
public sealed class StorageTests : IDisposable
{
private static readonly NodeId s_agent = new("agt_storage");
private readonly string _dir = Path.Combine(Path.GetTempPath(), "monitor-storage-" + Guid.NewGuid().ToString("N"));
private readonly string _dir = Path.Combine(Path.GetTempPath(), "moshiro-storage-" + Guid.NewGuid().ToString("N"));
private readonly SqliteDatabase _db;
public StorageTests()
@@ -269,7 +269,7 @@ public sealed class StorageTests : IDisposable
Assert.True((await ((IFleetStore)store).CreateNodeAsync(Node("agt_4", "hk", NodeStatus.Pending), default)).IsSuccess);
Result<AgentRecord> approve = await store.SetStatusAsync(new NodeId("agt_3"), NodeStatus.Active, default);
Assert.Equal(MonitorErrors.NameConflict, approve.Error.Code);
Assert.Equal(MoShiroErrors.NameConflict, approve.Error.Code);
// 接管:保留旧 ID 与历史,换成新公钥,删除待审批记录
Result<AgentRecord> takeover = await store.TakeoverAsync(new NodeId("agt_3"), default);
@@ -277,7 +277,7 @@ public sealed class StorageTests : IDisposable
Assert.Equal(new byte[] { 1, 2, 3 }, takeover.Value.PublicKey);
Assert.Null(await store.GetAsync(new NodeId("agt_3"), default));
Assert.Equal(NodeStatus.Pending, (await store.GetAsync(new NodeId("agt_4"), default))!.Status);
Assert.Equal(MonitorErrors.AgentNotFound, (await store.TakeoverAsync(new NodeId("agt_1"), default)).Error.Code); // 只有 pending 能接管别人
Assert.Equal(MoShiroErrors.AgentNotFound, (await store.TakeoverAsync(new NodeId("agt_1"), default)).Error.Code); // 只有 pending 能接管别人
}
[Fact]
@@ -288,12 +288,12 @@ public sealed class StorageTests : IDisposable
AgentRecord current = (await store.GetAsync(new NodeId("agt_1"), default))!;
Result<AgentRecord> stale = await store.PatchAsync(current.Id, new AgentPatch { Labels = ["x"] }, current.RowVersion + 5, default);
Assert.Equal(MonitorErrors.PreconditionFailed, stale.Error.Code);
Assert.Equal(MoShiroErrors.PreconditionFailed, stale.Error.Code);
Result<AgentRecord> ok = await store.PatchAsync(current.Id, new AgentPatch { Labels = ["x"] }, current.RowVersion, default);
Assert.Equal(current.RowVersion + 1, ok.Value.RowVersion);
Assert.Equal(["x"], ok.Value.Labels);
Assert.Equal("hk", ok.Value.Name); // 没写的字段不动
Assert.Equal(MonitorErrors.AgentNotFound, (await store.PatchAsync(new NodeId("agt_x"), new AgentPatch(), null, default)).Error.Code);
Assert.Equal(MoShiroErrors.AgentNotFound, (await store.PatchAsync(new NodeId("agt_x"), new AgentPatch(), null, default)).Error.Code);
}
[Fact]
@@ -368,11 +368,11 @@ public sealed class StorageTests : IDisposable
[Fact]
public void Schema_RefusesADatabaseFromANewerBuild()
{
string dir = Path.Combine(Path.GetTempPath(), "monitor-schema-" + Guid.NewGuid().ToString("N"));
string dir = Path.Combine(Path.GetTempPath(), "moshiro-schema-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
try
{
using (var connection = new SqliteConnection($"Data Source={Path.Combine(dir, "monitor.db")}"))
using (var connection = new SqliteConnection($"Data Source={Path.Combine(dir, "moshiro.db")}"))
{
connection.Open();
connection.Exec("PRAGMA user_version = 99");
@@ -1,15 +1,20 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("probe-e2e")]
public sealed class UpdateTests
{
private const string Digest = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
// 每个用例取自己的 seq(单调递增):共用的后端只接受比已登记的更大的 seq,用例的先后顺序不能影响结果
private static long s_seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000;
private static long NextSeq() => Interlocked.Add(ref s_seq, 100);
private readonly BackendFixture _backend;
public UpdateTests(BackendFixture backend)
@@ -19,7 +24,7 @@ public sealed class UpdateTests
// 面板不验签(节点宿主机上的更新器验),所以测试里签名随便写一个格式对的
private static string Manifest(string component, long seq, string digest = Digest) =>
$$"""{"schema":1,"component":"{{component}}","channel":"dev","image":"registry.example/monitor-dev/monitor-{{component}}","digest":"{{digest}}","tag":"dev-{{seq}}","version":"0.1.1","commit":"abc","built_at":"2026-10-06T00:00:00Z","seq":{{seq}}}""";
$$"""{"schema":1,"component":"{{component}}","channel":"dev","image":"registry.example/moshiro-dev/moshiro-{{component}}","digest":"{{digest}}","tag":"dev-{{seq}}","version":"0.1.1","commit":"abc","built_at":"2026-10-06T00:00:00Z","seq":{{seq}}}""";
private async Task<HttpResponseMessage> PublishAsync(string manifest, string signature = "MEUCIQDc") =>
await _backend.Admin.PostAsync("/api/admin/releases", BackendFixture.Json(new JsonObject { ["manifest"] = manifest, ["signature"] = signature }));
@@ -27,7 +32,7 @@ public sealed class UpdateTests
[Fact]
public async Task Release_IsForwardedByteForByte_ToOnlineNodesOfThatComponent_AndToNodesThatConnectLater()
{
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000;
long seq = NextSeq();
await using AgentSimulator online = await _backend.ConnectAgentAsync();
string manifest = Manifest("agent", seq);
@@ -53,7 +58,7 @@ public sealed class UpdateTests
[Fact]
public async Task Release_Registration_RefusesBadManifests_StaleSeq_AndNeedsAnAdmin()
{
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000 + 5;
long seq = NextSeq();
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync("{not json")).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync(Manifest("agent", seq, digest: "sha256:short"))).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync(Manifest("backend", seq))).StatusCode);
@@ -84,7 +89,7 @@ public sealed class UpdateTests
return client;
}
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000 + 50;
long seq = NextSeq();
using HttpClient release = Client(await NewTokenAsync("release"));
using HttpClient read = Client(await NewTokenAsync("read"));
var body = BackendFixture.Json(new JsonObject { ["manifest"] = Manifest("probe", seq), ["signature"] = "MEUC" });
@@ -7,9 +7,9 @@ using System.Text.Json.Nodes;
using Fleet.Core;
using Fleet.Identity;
using Fleet.Rpc;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>
/// 一个会说 Fleet 协议的假 Agent:用加入令牌注册 → 连接 → 签名认证 → hello → 接收期望状态 → 上传原始块并等待 ACK。
@@ -48,7 +48,7 @@ public sealed class AgentSimulator : IAsyncDisposable
public ConcurrentQueue<TestRunParams> TestRuns { get; } = new();
/// <summary>面板发来的发布清单(<c>monitor.update.offer</c>)和更新请求(<c>monitor.update.apply</c>)。</summary>
/// <summary>面板发来的发布清单(<c>moshiro.update.offer</c>)和更新请求(<c>moshiro.update.apply</c>)。</summary>
public ConcurrentQueue<UpdateOfferParams> UpdateOffers { get; } = new();
private int _updateApplies;
@@ -63,7 +63,7 @@ public sealed class AgentSimulator : IAsyncDisposable
public string? CloseReason { get; private set; }
/// <summary>收到 monitor.test.run 时的应答:默认接受。</summary>
/// <summary>收到 moshiro.test.run 时的应答:默认接受。</summary>
public Func<TestRunParams, (bool Accepted, string? ErrorCode)> TestRunHandler { get; set; } = _ => (true, null);
public byte[] PublicKey => _identity.PublicKeySpki;
@@ -144,11 +144,11 @@ public sealed class AgentSimulator : IAsyncDisposable
ProbeUdpPorts = [7443],
LocalLimits = new LocalLimits { MaxTotalMbps = 200, MaxTotalPps = 20_000, MaxConcurrentTests = 1 },
},
MonitorJsonContext.Default.AgentHelloMeta);
MoShiroJsonContext.Default.AgentHelloMeta);
var hello = new HelloParams
{
NodeVersion = "0.0.1-sim",
Capabilities = [MonitorCapabilities.Base, MonitorCapabilities.KindsPrefix + string.Join(',', kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"])],
Capabilities = [MoShiroCapabilities.Base, MoShiroCapabilities.KindsPrefix + string.Join(',', kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"])],
NowUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(),
Meta = meta,
};
@@ -170,7 +170,7 @@ public sealed class AgentSimulator : IAsyncDisposable
Guid id = chunk.Header.ChunkId.Value;
var ack = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_acks[id] = ack;
await SendBinaryAsync(OutboxFrame.Build(MonitorChannels.RawChunk, id, bytes));
await SendBinaryAsync(OutboxFrame.Build(MoShiroChannels.RawChunk, id, bytes));
return await ack.Task.WaitAsync(timeout ?? TimeSpan.FromSeconds(10));
}
@@ -182,24 +182,24 @@ public sealed class AgentSimulator : IAsyncDisposable
public Task SendEventAsync(string kind, string? detail = null) =>
SendAsync(RpcMessage.Notification(
MonitorMethods.Event,
MoShiroMethods.Event,
new AgentEventParams { Kind = kind, AtUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(), Detail = detail },
MonitorJsonContext.Default.AgentEventParams));
MoShiroJsonContext.Default.AgentEventParams));
public Task SendHostAsync(HostSnapshot snapshot) =>
SendAsync(RpcMessage.Notification(MonitorMethods.Host, snapshot, MonitorJsonContext.Default.HostSnapshot));
SendAsync(RpcMessage.Notification(MoShiroMethods.Host, snapshot, MoShiroJsonContext.Default.HostSnapshot));
public Task SendTestStatusAsync(TestId test, string status, int? phase = null, string? errorCode = null) =>
SendAsync(RpcMessage.Notification(
MonitorMethods.TestStatus,
MoShiroMethods.TestStatus,
new TestStatusParams { TestId = test, Status = status, Phase = phase, ErrorCode = errorCode },
MonitorJsonContext.Default.TestStatusParams));
MoShiroJsonContext.Default.TestStatusParams));
/// <summary>像节点的更新器报告的那样,随心跳上报更新状态。</summary>
public Task SendUpdateStatusAsync(UpdateStatus status) =>
SendAsync(RpcMessage.Notification(
FleetMethods.Heartbeat,
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new AgentHeartbeatExtra { Update = status }, MonitorJsonContext.Default.AgentHeartbeatExtra) },
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new AgentHeartbeatExtra { Update = status }, MoShiroJsonContext.Default.AgentHeartbeatExtra) },
FleetRpcContext.Default.HeartbeatParams));
public async Task<RpcMessage> RequestRawAsync(string method, JsonElement? parameters = null) => await RequestAsync(method, parameters);
@@ -383,7 +383,7 @@ public sealed class AgentSimulator : IAsyncDisposable
StateSyncParams sync = message.Params!.Value.Deserialize(FleetRpcContext.Default.StateSyncParams)!;
if (sync.Type == TaskSet.StateType)
{
TaskSets.Enqueue(sync.Doc.Deserialize(MonitorJsonContext.Default.TaskSet)!);
TaskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.TaskSet)!);
}
var result = new StateSyncResult { AppliedVersion = sync.Version, Rejected = [.. RejectNextState] };
@@ -403,19 +403,19 @@ public sealed class AgentSimulator : IAsyncDisposable
case FleetMethods.Revoked:
Revoked = true;
break;
case MonitorMethods.UpdateOffer:
UpdateOffers.Enqueue(message.Params!.Value.Deserialize(MonitorJsonContext.Default.UpdateOfferParams)!);
case MoShiroMethods.UpdateOffer:
UpdateOffers.Enqueue(message.Params!.Value.Deserialize(MoShiroJsonContext.Default.UpdateOfferParams)!);
break;
case MonitorMethods.UpdateApply:
case MoShiroMethods.UpdateApply:
Interlocked.Increment(ref _updateApplies);
break;
case MonitorMethods.TestRun:
TestRunParams run = message.Params!.Value.Deserialize(MonitorJsonContext.Default.TestRunParams)!;
case MoShiroMethods.TestRun:
TestRunParams run = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.TestRunParams)!;
TestRuns.Enqueue(run);
(bool accepted, string? errorCode) = TestRunHandler(run);
if (accepted)
{
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MonitorJsonContext.Default.TestRunResult));
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MoShiroJsonContext.Default.TestRunResult));
}
else
{
@@ -423,10 +423,10 @@ public sealed class AgentSimulator : IAsyncDisposable
}
break;
case MonitorMethods.TestCancel:
TestCancelParams cancel = message.Params!.Value.Deserialize(MonitorJsonContext.Default.TestCancelParams)!;
case MoShiroMethods.TestCancel:
TestCancelParams cancel = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.TestCancelParams)!;
TestCancels.Enqueue(cancel.TestId);
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MonitorJsonContext.Default.TestRunResult));
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MoShiroJsonContext.Default.TestRunResult));
break;
}
}
@@ -3,7 +3,7 @@ using System.Text.Json;
using Fleet.Core;
using Fleet.Rpc;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
public sealed record AuthAttempt(bool Ok, string? Status, string? ErrorCode, string? CloseReason);
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>每一个回显包的设定:上行或下行丢失,以及两个方向的单向时延(微秒)。</summary>
public readonly record struct EchoPacket(bool LostUp, bool LostDown, long OwdUpUs, long OwdDownUs, long ProcessUs = 50);
@@ -3,7 +3,7 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\shared\Monitor.Protocol\Monitor.Protocol.csproj" />
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Identity\Fleet.Identity.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
</ItemGroup>
@@ -8,9 +8,9 @@ using Fleet.Core;
using Fleet.Identity;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>
/// 假 Probe:通过真实的点对点握手(TLS 固定证书 + 签名 + ECDH)连接真实的 Agent,接收 <c>line_task_set</c>,
@@ -104,7 +104,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
for (int i = 0; i < rounds; i++)
{
long t0 = NowUs();
Result<ClockResult> reply = await _rpc!.RequestAsync(MonitorMethods.Clock, new ClockParams { ProbeMonoUs = t0 }, MonitorJsonContext.Default.ClockParams, MonitorJsonContext.Default.ClockResult, CancellationToken.None);
Result<ClockResult> reply = await _rpc!.RequestAsync(MoShiroMethods.Clock, new ClockParams { ProbeMonoUs = t0 }, MoShiroJsonContext.Default.ClockParams, MoShiroJsonContext.Default.ClockResult, CancellationToken.None);
long t1 = NowUs();
long roundTrip = t1 - t0;
if (reply.IsSuccess && roundTrip < bestRoundTrip)
@@ -214,7 +214,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
Guid id = fragment.Header.ChunkId.Value;
var ack = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_acks[id] = ack;
await _rpc!.SendBinaryAsync(OutboxFrame.Build(MonitorChannels.ColumnBlock, id, RawChunkCodec.Encode(fragment)), ct);
await _rpc!.SendBinaryAsync(OutboxFrame.Build(MoShiroChannels.ColumnBlock, id, RawChunkCodec.Encode(fragment)), ct);
if (waitForAck)
{
await ack.Task.WaitAsync(TimeSpan.FromSeconds(10), ct);
@@ -322,24 +322,24 @@ public sealed class ProbeSimulator : IAsyncDisposable
{
if (sync.Type == LineTaskSet.StateType)
{
probe._taskSets.Enqueue(sync.Doc.Deserialize(MonitorJsonContext.Default.LineTaskSet)!);
probe._taskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet)!);
}
return ValueTask.FromResult(Result<StateSyncResult>.Ok(new StateSyncResult { AppliedVersion = sync.Version }));
})
.On(
MonitorMethods.TestPhase,
MonitorJsonContext.Default.TestPhaseStartParams,
MonitorJsonContext.Default.TestPhaseStartResult,
MoShiroMethods.TestPhase,
MoShiroJsonContext.Default.TestPhaseStartParams,
MoShiroJsonContext.Default.TestPhaseStartResult,
async (probe, start, ct) => Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = await probe.StartPhaseAsync(start, ct) }))
.On(
MonitorMethods.TestPhaseEnd,
MonitorJsonContext.Default.TestPhaseEndParams,
MonitorJsonContext.Default.TestPhaseEndResult,
MoShiroMethods.TestPhaseEnd,
MoShiroJsonContext.Default.TestPhaseEndParams,
MoShiroJsonContext.Default.TestPhaseEndResult,
async (probe, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await probe.EndPhaseAsync(end, ct) }))
.OnNotify(
MonitorMethods.TestAbort,
MonitorJsonContext.Default.TestAbortParams,
MoShiroMethods.TestAbort,
MoShiroJsonContext.Default.TestAbortParams,
(probe, abort, _) =>
{
probe.Aborts.Enqueue(abort.TestId);