From b4995cb3bd9743be98ceebcbba8a62a4abeb791a Mon Sep 17 00:00:00 2001 From: santidev21 Date: Tue, 6 Oct 2026 11:08:35 -0500 Subject: [PATCH] test(integration): give rate-limit tests unique client IPs RateLimitIntegrationTests picked `203.0.113.{1..253}` at random while AuthorizationIntegrationTests pinned `203.0.113.250`. When the random draw hit 250 the two shared a limiter partition, so the rate-limit test started with part of its budget spent and failed intermittently (observed: 6/10 requests already 429). Replace both with a sequence-based helper that hands out a unique IP per call, so partitions can never collide. --- .../AuthorizationIntegrationTests.cs | 2 +- .../Infrastructure/TestClientIps.cs | 18 ++++++++++++++++++ .../RateLimitIntegrationTests.cs | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 Bikontrol/Bikontrol.Tests.Integration/Infrastructure/TestClientIps.cs diff --git a/Bikontrol/Bikontrol.Tests.Integration/AuthorizationIntegrationTests.cs b/Bikontrol/Bikontrol.Tests.Integration/AuthorizationIntegrationTests.cs index feb0f65..75ff3bf 100644 --- a/Bikontrol/Bikontrol.Tests.Integration/AuthorizationIntegrationTests.cs +++ b/Bikontrol/Bikontrol.Tests.Integration/AuthorizationIntegrationTests.cs @@ -70,7 +70,7 @@ public async Task Login_AfterMaxFailedAttempts_ShouldLockAccountAndReturn429() // Its own client IP so this test does not spend the per-IP auth rate // limit of the rest of the suite (which shares the "unknown" partition). var client = _factory.CreateClient(); - client.DefaultRequestHeaders.Add(ClientIpStartupFilter.HeaderName, "203.0.113.250"); + client.DefaultRequestHeaders.Add(ClientIpStartupFilter.HeaderName, TestClientIps.Next()); var email = $"locked-{Guid.NewGuid():N}@bikontrol.test"; var register = await client.PostAsJsonAsync("/api/auth/register", new diff --git a/Bikontrol/Bikontrol.Tests.Integration/Infrastructure/TestClientIps.cs b/Bikontrol/Bikontrol.Tests.Integration/Infrastructure/TestClientIps.cs new file mode 100644 index 0000000..ee973c7 --- /dev/null +++ b/Bikontrol/Bikontrol.Tests.Integration/Infrastructure/TestClientIps.cs @@ -0,0 +1,18 @@ +namespace Bikontrol.Tests.Integration.Infrastructure; + +/// +/// Hands out a unique synthetic client IP per call (in the benchmarking range +/// 198.18.0.0/15) so each test gets its own per-IP rate-limit partition. Using a +/// fixed IP (or a small random range) risks two tests sharing a partition and +/// one of them starting with part of its budget already spent. +/// +public static class TestClientIps +{ + private static int _sequence; + + public static string Next() + { + var n = Interlocked.Increment(ref _sequence); + return $"198.18.{(n / 256) % 256}.{n % 256}"; + } +} diff --git a/Bikontrol/Bikontrol.Tests.Integration/RateLimitIntegrationTests.cs b/Bikontrol/Bikontrol.Tests.Integration/RateLimitIntegrationTests.cs index 0369815..13bb5a8 100644 --- a/Bikontrol/Bikontrol.Tests.Integration/RateLimitIntegrationTests.cs +++ b/Bikontrol/Bikontrol.Tests.Integration/RateLimitIntegrationTests.cs @@ -22,8 +22,8 @@ public sealed class RateLimitIntegrationTests public async Task AuthLogin_WhenExceedingPerIpLimit_ShouldReturn429() { var client = _factory.CreateClient(); - // TEST-NET-3 address unique to this test run -> its own limiter partition. - var clientIp = $"203.0.113.{Random.Shared.Next(1, 254)}"; + // A unique client IP per run -> its own limiter partition. + var clientIp = TestClientIps.Next(); var statuses = new List(); for (var attempt = 0; attempt < PermitLimit + 1; attempt++)