b86c6bb47f
Resolve all CommentChecker findings across the gateway server, worker, tests, and .NET client (314 -> 0 real issues): add missing <returns>/<summary>/<param> on public and test members, convert Stream/interface overrides to <inheritdoc/>, and remove internal task/issue tracking IDs (SEC-*, IPC-*, WRK-*, GWC-*, TST-*, Client.Dotnet-*) from shipped code documentation while preserving the design rationale prose. Shipped comments should not carry internal bookkeeping, and complete XML docs keep the analyzer/TreatWarningsAsErrors gate and generated API docs clean. The 6 remaining flags are heuristic false positives (MD5, UTC-4, capacity-1, near-1601) left intact so real documentation is not corrupted. Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
67 lines
2.5 KiB
C#
67 lines
2.5 KiB
C#
using System.Net;
|
|
using System.Threading.RateLimiting;
|
|
using Microsoft.AspNetCore.Http;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
|
|
|
/// <summary>
|
|
/// Tests the <c>POST /auth/login</c> fixed-window per-IP rate limiter. Exercised through the same
|
|
/// partition factory the production policy registers, so the test pins the real permit limit and
|
|
/// per-IP partitioning without standing up an HTTP server.
|
|
/// </summary>
|
|
public sealed class DashboardLoginRateLimitTests
|
|
{
|
|
/// <summary>A burst beyond the permit limit from one IP has its excess attempts rejected (HTTP 429 in the pipeline).</summary>
|
|
[Fact]
|
|
public void LoginRateLimiter_BurstBeyondPermitLimit_RejectsExcessFromSameIp()
|
|
{
|
|
SecurityOptions security = new()
|
|
{
|
|
LoginRateLimitPermitLimit = 3,
|
|
LoginRateLimitWindowSeconds = 60,
|
|
};
|
|
using PartitionedRateLimiter<HttpContext> limiter =
|
|
DashboardEndpointRouteBuilderExtensions.CreateLoginRateLimiter(security);
|
|
HttpContext context = ContextWithIp("10.0.0.7");
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
using RateLimitLease lease = limiter.AttemptAcquire(context);
|
|
Assert.True(lease.IsAcquired);
|
|
}
|
|
|
|
using RateLimitLease rejected = limiter.AttemptAcquire(context);
|
|
Assert.False(rejected.IsAcquired);
|
|
}
|
|
|
|
/// <summary>Distinct client IPs are partitioned independently — one IP's burst does not starve another.</summary>
|
|
[Fact]
|
|
public void LoginRateLimiter_DistinctIps_PartitionedIndependently()
|
|
{
|
|
SecurityOptions security = new()
|
|
{
|
|
LoginRateLimitPermitLimit = 1,
|
|
LoginRateLimitWindowSeconds = 60,
|
|
};
|
|
using PartitionedRateLimiter<HttpContext> limiter =
|
|
DashboardEndpointRouteBuilderExtensions.CreateLoginRateLimiter(security);
|
|
|
|
using RateLimitLease first = limiter.AttemptAcquire(ContextWithIp("10.0.0.1"));
|
|
using RateLimitLease exhaustedForFirst = limiter.AttemptAcquire(ContextWithIp("10.0.0.1"));
|
|
using RateLimitLease second = limiter.AttemptAcquire(ContextWithIp("10.0.0.2"));
|
|
|
|
Assert.True(first.IsAcquired);
|
|
Assert.False(exhaustedForFirst.IsAcquired);
|
|
Assert.True(second.IsAcquired);
|
|
}
|
|
|
|
private static HttpContext ContextWithIp(string ip)
|
|
{
|
|
DefaultHttpContext context = new();
|
|
context.Connection.RemoteIpAddress = IPAddress.Parse(ip);
|
|
return context;
|
|
}
|
|
}
|