using FluentAssertions; using StellaOps.Router.Gateway.RateLimit; using Xunit; namespace StellaOps.Router.Gateway.Tests; public sealed class RateLimitRouteMatcherTests { [Fact] public void TryMatch_ExactBeatsPrefixAndRegex() { var microservice = new MicroserviceLimitsConfig { Routes = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["exact"] = new RouteLimitsConfig { Pattern = "/api/scans", MatchType = RouteMatchType.Exact, Rules = [new RateLimitRule { PerSeconds = 10, MaxRequests = 1 }] }, ["prefix"] = new RouteLimitsConfig { Pattern = "/api/scans/*", MatchType = RouteMatchType.Prefix, Rules = [new RateLimitRule { PerSeconds = 10, MaxRequests = 1 }] }, ["regex"] = new RouteLimitsConfig { Pattern = "^/api/scans/[a-f0-9-]+$", MatchType = RouteMatchType.Regex, Rules = [new RateLimitRule { PerSeconds = 10, MaxRequests = 1 }] }, } }; microservice.Validate("microservice"); var matcher = new RateLimitRouteMatcher(); var match = matcher.TryMatch(microservice, "/api/scans"); match.Should().NotBeNull(); match!.Value.Name.Should().Be("exact"); } [Fact] public void TryMatch_LongestPrefixWins() { var microservice = new MicroserviceLimitsConfig { Routes = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["short"] = new RouteLimitsConfig { Pattern = "/api/*", MatchType = RouteMatchType.Prefix, Rules = [new RateLimitRule { PerSeconds = 10, MaxRequests = 1 }] }, ["long"] = new RouteLimitsConfig { Pattern = "/api/scans/*", MatchType = RouteMatchType.Prefix, Rules = [new RateLimitRule { PerSeconds = 10, MaxRequests = 1 }] }, } }; microservice.Validate("microservice"); var matcher = new RateLimitRouteMatcher(); var match = matcher.TryMatch(microservice, "/api/scans/123"); match.Should().NotBeNull(); match!.Value.Name.Should().Be("long"); } }