Files
MoShiro/backend/tests/Monitor.Backend.Tests/LoginRateLimitTests.cs
T
2026-10-04 21:46:39 +08:00

50 lines
2.1 KiB
C#

using System.Net;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
/// <summary>登录限流按来源地址计,会影响同一地址上的其他登录,所以用独立的后端实例。</summary>
public sealed class LoginRateLimitTests : IClassFixture<StrictLoginFixture>
{
private readonly StrictLoginFixture _backend;
public LoginRateLimitTests(StrictLoginFixture backend)
{
_backend = backend;
}
[Fact]
public async Task LoginAttempts_AreRateLimited_WithRetryAfter_AndASuccessfulLoginIsAlsoCounted()
{
using HttpClient client = _backend.NewClient();
HttpResponseMessage? limited = null;
int attempts = 0;
for (; attempts < 15 && limited is null; attempts++)
{
HttpResponseMessage response = await client.PostAsync("/api/admin/auth/login", BackendFixture.Json(new JsonObject { ["username"] = "admin", ["password"] = "wrong" }));
if (response.StatusCode == HttpStatusCode.TooManyRequests)
{
limited = response;
}
else
{
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
response.Dispose();
}
}
Assert.NotNull(limited);
using (limited)
{
// 前一次登录(创建管理员令牌)也占了一次配额:第 10 次之内的都是 401
Assert.InRange(attempts, 9, 10);
Assert.Equal("too_many_requests", JsonNode.Parse(await limited.Content.ReadAsStringAsync())!["code"]!.GetValue<string>());
Assert.True(limited.Headers.RetryAfter!.Delta!.Value.TotalSeconds is >= 1 and <= 60);
}
// 被限流的来源连正确的密码也不行:限流发生在校验密码之前,不能被用来猜密码
using HttpResponseMessage correct = await client.PostAsync("/api/admin/auth/login", BackendFixture.Json(new JsonObject { ["username"] = BackendFixture.AdminUser, ["password"] = BackendFixture.AdminPassword }));
Assert.Equal(HttpStatusCode.TooManyRequests, correct.StatusCode);
}
}