68 lines
2.8 KiB
C#
68 lines
2.8 KiB
C#
using System.Net;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace MoShiro.Backend.Tests;
|
|
|
|
/// <summary>审计日志:写操作、登录成败都会记一行;谁做的(用户名或 key:令牌名)、结果、细节。</summary>
|
|
public sealed class AuditTests : IClassFixture<BackendFixture>
|
|
{
|
|
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);
|
|
}
|
|
}
|