using System.Text.Json.Nodes; using MoShiro.Protocol; using MoShiro.TestSupport; namespace MoShiro.Backend.Tests; public sealed class NodeRuntimeLogTests : IClassFixture { private readonly BackendFixture _backend; public NodeRuntimeLogTests(BackendFixture backend) { _backend = backend; } private static LogEntryDto Entry(long id, string level, string message, Dictionary? fields = null) => new() { Id = id, Type = (uint)id, AtUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(), Level = level, Message = message, Fields = fields ?? [] }; private async Task LogsAsync(AgentSimulator agent) => await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/nodes/{agent.NodeId.Value}/logs?limit=50"); [Fact] public async Task Logs_ArePulledFromTheNodeOnDemand_OnlyTheNewLinesEachTime_WithSecretsAndOversizedFieldsBounded() { await using AgentSimulator agent = await _backend.ConnectAgentAsync(); Dictionary fields = Enumerable.Range(0, 18).ToDictionary(i => "field_" + i, _ => "value"); fields.Add("api_token", "must-not-survive"); fields.Add("detail", new string('x', 500)); fields.Add("overflow", "discard"); agent.Logs.Add(Entry(1, "info", "link up")); agent.Logs.Add(Entry(2, "warn", "handshake failed", fields)); JsonNode first = await LogsAsync(agent); Assert.Equal(["handshake failed", "link up"], first["items"]!.AsArray().Select(i => i!["message"]!.GetValue())); // 新的在前 JsonNode warn = first["items"]![0]!; Assert.Equal(20, warn["fields"]!.AsObject().Count); Assert.Equal("[redacted]", warn["fields"]!["api_token"]!.GetValue()); Assert.Equal(128, warn["fields"]!["detail"]!.GetValue().Length); Assert.Null(warn["fields"]!["overflow"]); agent.Logs.Add(Entry(3, "error", "transport shutdown")); JsonNode second = await LogsAsync(agent); Assert.Equal(3, second["items"]!.AsArray().Count); Assert.Equal(2, agent.LogFetches.Last().AfterId); // 第二次只拉编号 2 之后的 } [Fact] public async Task Logs_OfAnOfflineNode_AreWhatTheBackendKept() { JsonNode logs = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/nodes/agt_nobody/logs"); Assert.Empty(logs["items"]!.AsArray()); } }