using Microsoft.AspNetCore.Http; using StellaOps.Gateway.WebService.Routing; using StellaOps.Router.Gateway.Configuration; namespace StellaOps.Gateway.WebService.Tests.Routing; public sealed class StellaOpsRouteResolverTests { private static StellaOpsRoute MakeRoute( string path, StellaOpsRouteType type = StellaOpsRouteType.Microservice, bool isRegex = false, string? translatesTo = null) { return new StellaOpsRoute { Path = path, Type = type, IsRegex = isRegex, TranslatesTo = translatesTo ?? "http://backend:5000" }; } [Fact] public void Resolve_ExactPathMatch_ReturnsRoute() { var route = MakeRoute("/dashboard"); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/dashboard")); Assert.NotNull(result.Route); Assert.Equal("/dashboard", result.Route.Path); Assert.Null(result.RegexMatch); } [Fact] public void Resolve_PrefixMatch_ReturnsRoute() { var route = MakeRoute("/app"); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/app/index.html")); Assert.NotNull(result.Route); Assert.Equal("/app", result.Route.Path); Assert.Null(result.RegexMatch); } [Fact] public void Resolve_RegexRoute_Matches() { var route = MakeRoute(@"^/api/v[0-9]+/.*", isRegex: true); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/api/v2/data")); Assert.NotNull(result.Route); Assert.True(result.Route.IsRegex); Assert.Equal(@"^/api/v[0-9]+/.*", result.Route.Path); Assert.NotNull(result.RegexMatch); Assert.True(result.RegexMatch.Success); } [Fact] public void Resolve_RegexRoute_ReturnsCaptureGroups() { var route = MakeRoute( @"^/api/v1/([^/]+)(.*)", isRegex: true, translatesTo: "http://$1.stella-ops.local/api/v1/$1$2"); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/api/v1/scanner/health")); Assert.NotNull(result.Route); Assert.NotNull(result.RegexMatch); Assert.Equal("scanner", result.RegexMatch.Groups[1].Value); Assert.Equal("/health", result.RegexMatch.Groups[2].Value); } [Fact] public void Resolve_NoMatch_ReturnsNull() { var route = MakeRoute("/dashboard"); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/unknown")); Assert.Null(result.Route); Assert.Null(result.RegexMatch); } [Fact] public void Resolve_FirstMatchWins() { var firstRoute = MakeRoute("/api", translatesTo: "http://first:5000"); var secondRoute = MakeRoute("/api", translatesTo: "http://second:5000"); var resolver = new StellaOpsRouteResolver(new[] { firstRoute, secondRoute }); var result = resolver.Resolve(new PathString("/api/resource")); Assert.NotNull(result.Route); Assert.Equal("http://first:5000", result.Route.TranslatesTo); } [Fact] public void Resolve_NotFoundPageRoute_IsExcluded() { var notFoundRoute = MakeRoute("/not-found", type: StellaOpsRouteType.NotFoundPage); var resolver = new StellaOpsRouteResolver(new[] { notFoundRoute }); var result = resolver.Resolve(new PathString("/not-found")); Assert.Null(result.Route); } [Fact] public void Resolve_ServerErrorPageRoute_IsExcluded() { var errorRoute = MakeRoute("/error", type: StellaOpsRouteType.ServerErrorPage); var resolver = new StellaOpsRouteResolver(new[] { errorRoute }); var result = resolver.Resolve(new PathString("/error")); Assert.Null(result.Route); } [Fact] public void Resolve_CaseInsensitive_Matches() { var route = MakeRoute("/app"); var resolver = new StellaOpsRouteResolver(new[] { route }); var result = resolver.Resolve(new PathString("/APP")); Assert.NotNull(result.Route); Assert.Equal("/app", result.Route.Path); } [Fact] public void Resolve_EmptyRoutes_ReturnsNull() { var resolver = new StellaOpsRouteResolver(Array.Empty()); var result = resolver.Resolve(new PathString("/anything")); Assert.Null(result.Route); } }