using FluentAssertions; using StellaOps.Router.Gateway.RateLimit; using Xunit; namespace StellaOps.Router.Gateway.Tests; [Collection(nameof(ValkeyTestcontainerCollection))] public sealed class ValkeyRateLimitStoreIntegrationTests { private readonly ValkeyTestcontainerFixture _valkey; public ValkeyRateLimitStoreIntegrationTests(ValkeyTestcontainerFixture valkey) { _valkey = valkey; } [IntegrationFact] public async Task IncrementAndCheckAsync_UsesSmallestWindowAsRepresentativeWhenAllowed() { var bucket = $"stella-router-rate-limit-it-{Guid.NewGuid():N}"; using var store = new ValkeyRateLimitStore(_valkey.ConnectionString, bucket); var rules = new[] { new RateLimitRule { PerSeconds = 3600, MaxRequests = 1000 }, new RateLimitRule { PerSeconds = 60, MaxRequests = 10 }, }; var result = await store.IncrementAndCheckAsync("svc", rules, CancellationToken.None); result.Allowed.Should().BeTrue(); result.WindowSeconds.Should().Be(60); result.Limit.Should().Be(10); result.CurrentCount.Should().Be(1); result.RetryAfterSeconds.Should().Be(0); } [IntegrationFact] public async Task IncrementAndCheckAsync_DeniesWhenLimitExceeded() { var bucket = $"stella-router-rate-limit-it-{Guid.NewGuid():N}"; using var store = new ValkeyRateLimitStore(_valkey.ConnectionString, bucket); var rules = new[] { new RateLimitRule { PerSeconds = 2, MaxRequests = 1 }, }; (await store.IncrementAndCheckAsync("svc", rules, CancellationToken.None)).Allowed.Should().BeTrue(); var denied = await store.IncrementAndCheckAsync("svc", rules, CancellationToken.None); denied.Allowed.Should().BeFalse(); denied.WindowSeconds.Should().Be(2); denied.Limit.Should().Be(1); denied.CurrentCount.Should().Be(2); denied.RetryAfterSeconds.Should().BeInRange(1, 2); } [IntegrationFact] public async Task IncrementAndCheckAsync_ReturnsMostRestrictiveRetryAfterAcrossRules() { var bucket = $"stella-router-rate-limit-it-{Guid.NewGuid():N}"; using var store = new ValkeyRateLimitStore(_valkey.ConnectionString, bucket); var rules = new[] { new RateLimitRule { PerSeconds = 60, MaxRequests = 1 }, new RateLimitRule { PerSeconds = 2, MaxRequests = 100 }, }; (await store.IncrementAndCheckAsync("svc", rules, CancellationToken.None)).Allowed.Should().BeTrue(); var denied = await store.IncrementAndCheckAsync("svc", rules, CancellationToken.None); denied.Allowed.Should().BeFalse(); denied.WindowSeconds.Should().Be(60); denied.Limit.Should().Be(1); denied.CurrentCount.Should().Be(2); denied.RetryAfterSeconds.Should().BeInRange(1, 60); } }