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
}