using System.Net; using System.Text.Json.Nodes; using MoShiro.Backend.Application; using MoShiro.Protocol; using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; /// 定时重启:默认开、北京时间 04:30;节点连上时补发,管理端改了设置马上群发,关掉就发空。 [Collection("probe-e2e")] public sealed class ScheduleTests { private readonly BackendFixture _backend; public ScheduleTests(BackendFixture backend) { _backend = backend; } private async Task PatchAsync(JsonObject settings) { using HttpResponseMessage response = await _backend.Admin.PatchAsync("/api/admin/settings", BackendFixture.Json(settings, "application/merge-patch+json")); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } private static Task LastIs(AgentSimulator agent, string? restartAt) => Task.FromResult(agent.Schedules.ToArray() is { Length: > 0 } all && all[^1].RestartAt == restartAt); [Fact] public void Format_WritesTheOffsetTheNodeParses() { Assert.Equal("04:30+08:00", ScheduleService.Format(4, 30, 480)); Assert.Equal("00:00+00:00", ScheduleService.Format(0, 0, 0)); Assert.Equal("23:05-05:30", ScheduleService.Format(23, 5, -330)); } [Fact] public async Task ANewNode_GetsTheDefaultSchedule_AndLaterChangesAreBroadcast() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(); await BackendFixture.EventuallyAsync(() => LastIs(agent, "04:30+08:00"), because: "defaults: on, 04:30 Beijing time, sent right after hello"); try { await PatchAsync(new JsonObject { ["fleet.restart_hour"] = 3, ["fleet.restart_minute"] = 5, ["fleet.restart_utc_offset_minutes"] = 0 }); await BackendFixture.EventuallyAsync(() => LastIs(agent, "03:05+00:00"), because: "a changed time is pushed to online nodes"); await PatchAsync(new JsonObject { ["fleet.restart_enabled"] = false }); await BackendFixture.EventuallyAsync(() => LastIs(agent, null), because: "switching it off sends an empty schedule"); // 关着的时候新连上的节点也拿到「关」 await using AgentSimulator late = await _backend.ConnectAgentAsync(); await BackendFixture.EventuallyAsync(() => LastIs(late, null), because: "a node connecting while it is off is told so"); } finally { await PatchAsync(new JsonObject { ["fleet.restart_enabled"] = true, ["fleet.restart_hour"] = 4, ["fleet.restart_minute"] = 30, ["fleet.restart_utc_offset_minutes"] = 480 }); } } [Fact] public async Task BadValues_AreRefused() { using HttpResponseMessage response = await _backend.Admin.PatchAsync("/api/admin/settings", BackendFixture.Json(new JsonObject { ["fleet.restart_hour"] = 24, ["fleet.restart_minute"] = 60, ["fleet.restart_utc_offset_minutes"] = 900 }, "application/merge-patch+json")); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] public async Task DefaultLineTemplates_IncludeTheConnectTimings() { using HttpResponseMessage response = await _backend.Admin.GetAsync("/api/admin/tasks"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); JsonArray items = JsonNode.Parse(await response.Content.ReadAsStringAsync())!["items"]!.AsArray(); string[] kinds = [.. items.Select(i => i!["kind"]!.GetValue())]; Assert.Contains("connect", kinds); Assert.Contains("udp_echo", kinds); Assert.Contains("tcp_echo", kinds); } }