using FluentAssertions; using Microsoft.AspNetCore.Http; using Moq; using StellaOps.Gateway.WebService.Middleware; using StellaOps.Router.Common.Abstractions; using StellaOps.Router.Common.Models; using Xunit; namespace StellaOps.Gateway.WebService.Tests; /// /// Unit tests for . /// public sealed class EndpointResolutionMiddlewareTests { private readonly Mock _routingStateMock; private readonly Mock _nextMock; private bool _nextCalled; public EndpointResolutionMiddlewareTests() { _routingStateMock = new Mock(); _nextMock = new Mock(); _nextMock.Setup(n => n(It.IsAny())) .Callback(() => _nextCalled = true) .Returns(Task.CompletedTask); } private EndpointResolutionMiddleware CreateMiddleware() { return new EndpointResolutionMiddleware(_nextMock.Object); } private static HttpContext CreateHttpContext(string method = "GET", string path = "/api/test") { var context = new DefaultHttpContext(); context.Request.Method = method; context.Request.Path = path; context.Response.Body = new MemoryStream(); return context; } private static EndpointDescriptor CreateEndpoint( string serviceName = "test-service", string method = "GET", string path = "/api/test") { return new EndpointDescriptor { ServiceName = serviceName, Version = "1.0.0", Method = method, Path = path }; } #region Matching Endpoint Tests [Fact] public async Task Invoke_WithMatchingEndpoint_SetsHttpContextItem() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(); var context = CreateHttpContext(); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/test")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); context.Items[RouterHttpContextKeys.EndpointDescriptor].Should().Be(endpoint); } [Fact] public async Task Invoke_WithMatchingEndpoint_CallsNext() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(); var context = CreateHttpContext(); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/test")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); } #endregion #region Unknown Path Tests [Fact] public async Task Invoke_WithUnknownPath_Returns404() { // Arrange var middleware = CreateMiddleware(); var context = CreateHttpContext(path: "/api/unknown"); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/unknown")) .Returns((EndpointDescriptor?)null); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeFalse(); context.Response.StatusCode.Should().Be(StatusCodes.Status404NotFound); } [Fact] public async Task Invoke_WithUnknownPath_WritesErrorResponse() { // Arrange var middleware = CreateMiddleware(); var context = CreateHttpContext(path: "/api/unknown"); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/unknown")) .Returns((EndpointDescriptor?)null); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert context.Response.Body.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(context.Response.Body); var responseBody = await reader.ReadToEndAsync(); responseBody.Should().Contain("not found"); responseBody.Should().Contain("/api/unknown"); } #endregion #region HTTP Method Tests [Fact] public async Task Invoke_WithPostMethod_ResolvesCorrectly() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(method: "POST"); var context = CreateHttpContext(method: "POST"); _routingStateMock.Setup(r => r.ResolveEndpoint("POST", "/api/test")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); context.Items[RouterHttpContextKeys.EndpointDescriptor].Should().Be(endpoint); } [Fact] public async Task Invoke_WithDeleteMethod_ResolvesCorrectly() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(method: "DELETE", path: "/api/users/123"); var context = CreateHttpContext(method: "DELETE", path: "/api/users/123"); _routingStateMock.Setup(r => r.ResolveEndpoint("DELETE", "/api/users/123")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); } [Fact] public async Task Invoke_WithWrongMethod_Returns404() { // Arrange var middleware = CreateMiddleware(); var context = CreateHttpContext(method: "DELETE", path: "/api/test"); _routingStateMock.Setup(r => r.ResolveEndpoint("DELETE", "/api/test")) .Returns((EndpointDescriptor?)null); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeFalse(); context.Response.StatusCode.Should().Be(StatusCodes.Status404NotFound); } #endregion #region Path Variations Tests [Fact] public async Task Invoke_WithParameterizedPath_ResolvesCorrectly() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(path: "/api/users/{id}"); var context = CreateHttpContext(path: "/api/users/123"); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/users/123")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); context.Items[RouterHttpContextKeys.EndpointDescriptor].Should().Be(endpoint); } [Fact] public async Task Invoke_WithRootPath_ResolvesCorrectly() { // Arrange var middleware = CreateMiddleware(); var endpoint = CreateEndpoint(path: "/"); var context = CreateHttpContext(path: "/"); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/")) .Returns(endpoint); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _nextCalled.Should().BeTrue(); } [Fact] public async Task Invoke_WithEmptyPath_PassesEmptyStringToRouting() { // Arrange var middleware = CreateMiddleware(); var context = CreateHttpContext(path: ""); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "")) .Returns((EndpointDescriptor?)null); // Act await middleware.Invoke(context, _routingStateMock.Object); // Assert _routingStateMock.Verify(r => r.ResolveEndpoint("GET", ""), Times.Once); } #endregion #region Multiple Calls Tests [Fact] public async Task Invoke_MultipleCalls_EachResolvesIndependently() { // Arrange var middleware = CreateMiddleware(); var endpoint1 = CreateEndpoint(path: "/api/users"); var endpoint2 = CreateEndpoint(path: "/api/items"); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/users")) .Returns(endpoint1); _routingStateMock.Setup(r => r.ResolveEndpoint("GET", "/api/items")) .Returns(endpoint2); var context1 = CreateHttpContext(path: "/api/users"); var context2 = CreateHttpContext(path: "/api/items"); // Act await middleware.Invoke(context1, _routingStateMock.Object); await middleware.Invoke(context2, _routingStateMock.Object); // Assert context1.Items[RouterHttpContextKeys.EndpointDescriptor].Should().Be(endpoint1); context2.Items[RouterHttpContextKeys.EndpointDescriptor].Should().Be(endpoint2); } #endregion }