Files
MoShiro/backend/tests/MoShiro.Backend.Tests/AuditTests.cs
T

69 lines
2.8 KiB
C#

using System.Net;
using System.Text.Json.Nodes;
namespace MoShiro.Backend.Tests;
/// <summary>审计日志:写操作、登录成败都会记一行;谁做的(用户名或 key:令牌名)、结果、细节。</summary>
[Collection("probe-e2e")]
public sealed class AuditTests
{
private readonly BackendFixture _backend;
public AuditTests(BackendFixture backend)
{
_backend = backend;
}
/// <summary>审计在响应发完之后才写,轮询一下。</summary>
private async Task<JsonNode?> FindAsync(string query, Func<JsonNode, bool> match)
{
for (int i = 0; i < 50; i++)
{
JsonNode page = JsonNode.Parse(await _backend.Admin.GetStringAsync("/api/admin/audit?" + query))!;
if (page["items"]!.AsArray().FirstOrDefault(x => x is not null && match(x)) is { } found)
{
return found;
}
await Task.Delay(100);
}
return null;
}
[Fact]
public async Task WriteActions_AreRecorded_WithWhoAndTheDetail()
{
string note = "audit-" + Guid.NewGuid().ToString("N")[..8];
using HttpResponseMessage created = await _backend.Admin.PostAsync("/api/admin/join-tokens", BackendFixture.Json(new JsonObject { ["role"] = "probe", ["note"] = note }));
Assert.Equal(HttpStatusCode.Created, created.StatusCode);
JsonNode? row = await FindAsync("limit=100", x => x["detail"]?.GetValue<string>().Contains(note, StringComparison.Ordinal) == true);
Assert.NotNull(row);
Assert.StartsWith("key:", row["actor"]!.GetValue<string>(), StringComparison.Ordinal);
Assert.Equal("key", row["method"]!.GetValue<string>());
Assert.True(row["success"]!.GetValue<bool>());
Assert.False(string.IsNullOrEmpty(row["action"]!.GetValue<string>()));
}
[Fact]
public async Task FailedSignIn_IsRecorded_WithTheAttemptedName_AndWithoutThePassword()
{
string name = "nobody-" + Guid.NewGuid().ToString("N")[..8];
using HttpResponseMessage denied = await _backend.Anonymous.PostAsync("/api/admin/auth/login", BackendFixture.Json(new JsonObject { ["username"] = name, ["password"] = "secret-pw-123" }));
Assert.Equal(HttpStatusCode.Unauthorized, denied.StatusCode);
JsonNode? row = await FindAsync("action=auth.login&limit=100", x => x["actor"]!.GetValue<string>() == name);
Assert.NotNull(row);
Assert.False(row["success"]!.GetValue<bool>());
Assert.DoesNotContain("secret-pw-123", row.ToJsonString(), StringComparison.Ordinal);
}
[Fact]
public async Task Audit_NeedsItsOwnCapability()
{
using HttpResponseMessage response = await _backend.Anonymous.GetAsync("/api/admin/audit");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
}