|
1 | | -// RateLimitedApplicationFactory — in-memory TestServer wired with both HMAC authentication |
2 | | -// and client rate limiting for end-to-end rate limit scenario tests. |
3 | | -// |
4 | | -// Middleware order (important): |
5 | | -// app.UseRouting() → endpoint matched; GetEndpoint() resolves for partition key |
6 | | -// app.UseRateLimiter() → rate limit enforced using client ID from Authorization header |
7 | | -// app.UseAuthentication() → HMAC signature verified (only reached when not rate-limited) |
8 | | -// app.UseAuthorization() → RequireAuthorization() policy checked |
9 | | -// |
10 | | -// Rate limits are configured uniformly for all clients via RequestLimitOptions: |
11 | | -// TokenLimit = RequestsPerPeriod × BurstFactor |
12 | | -// |
13 | | -// Exposed endpoints (both require HMAC auth + rate limiting): |
14 | | -// GET /api/rl/items — primary endpoint for most tests |
15 | | -// GET /api/rl/users — secondary endpoint for per-endpoint independence tests |
16 | | -// |
17 | | -// Usage: instantiate directly in each test method with the desired limits, then dispose. |
18 | | -// Each instance creates a fresh in-memory server with independent token bucket state. |
19 | | - |
20 | | -using Microsoft.AspNetCore.TestHost; |
21 | | - |
22 | 1 | namespace HashGate.Integration.Tests.Fixtures; |
23 | 2 |
|
24 | 3 | public sealed class RateLimitedApplicationFactory : IDisposable |
25 | 4 | { |
26 | | - // --------------------------------------------------------------------------- |
27 | | - // Shared client credentials — used by every test scenario. |
28 | | - // Each test creates its own factory instance so token bucket state never |
29 | | - // bleeds across tests (each fresh server starts with empty buckets). |
30 | | - // --------------------------------------------------------------------------- |
31 | | - |
32 | 5 | public const string ClientId = "rl-test"; |
33 | 6 | public const string ClientSecret = "rl-test-secret"; |
34 | 7 |
|
35 | | - // Retry-After: Math.Max(1, (int)Math.Min(5, Period.TotalSeconds)) |
36 | | - // = Math.Max(1, (int)Math.Min(5, 60.0)) = 5 (for the default 1-minute period) |
37 | | - public const string ExpectedRetryAfter = "5"; |
38 | | - |
39 | | - // Response body written by RequestLimitExtensions.OnRejectedAsync |
40 | | - public const string ExpectedRejectionBody = "Rate limit exceeded. Retry shortly."; |
41 | | - |
42 | | - // --------------------------------------------------------------------------- |
43 | | - // Host setup |
44 | | - // --------------------------------------------------------------------------- |
| 8 | + public const string ExpectedRetryAfter = "60"; |
| 9 | + public const string ExpectedRejectionBody = "Rate limit exceeded. Retry after 60s."; |
45 | 10 |
|
46 | 11 | private readonly IHost _host; |
47 | 12 |
|
48 | | - /// <param name="requestsPerPeriod"> |
49 | | - /// Tokens replenished each period; also sets the base rate for the bucket ceiling. |
50 | | - /// <c>TokenLimit = requestsPerPeriod × burstFactor</c>. |
51 | | - /// </param> |
52 | | - /// <param name="burstFactor"> |
53 | | - /// Multiplier for the bucket ceiling. Use 1 to disable bursting. |
54 | | - /// </param> |
55 | 13 | public RateLimitedApplicationFactory(int requestsPerPeriod = 100, int burstFactor = 1) |
56 | 14 | { |
57 | 15 | _host = new HostBuilder() |
|
0 commit comments