svrnty-mcp-gateway/tests/Svrnty.MCP.Gateway.AspNetCore.Tests/Middleware/GatewayMiddlewareTests.cs
Svrnty a4a1dd2e38 docs: comprehensive AI coding assistant research and MCP-first implementation plan
Research conducted on modern AI coding assistants (Cursor, GitHub Copilot, Cline,
Aider, Windsurf, Replit Agent) to understand architecture patterns, context management,
code editing workflows, and tool use protocols.

Key Decision: Pivoted from building full CLI (40-50h) to validation-driven MCP-first
approach (10-15h). Build 5 core CODEX MCP tools that work with ANY coding assistant,
validate adoption over 2-4 weeks, then decide on full CLI if demand proven.

Files:
- research/ai-systems/modern-coding-assistants-architecture.md (comprehensive research)
- research/ai-systems/codex-coding-assistant-implementation-plan.md (original CLI plan, preserved)
- research/ai-systems/codex-mcp-tools-implementation-plan.md (approved MCP-first plan)
- ideas/registry.json (updated with approved MCP tools proposal)

Architech Validation: APPROVED with pivot to MCP-first approach
Human Decision: Approved (pragmatic validation-driven development)

Next: Begin Phase 1 implementation (10-15 hours, 5 core MCP tools)

🤖 Generated with CODEX Research System

Co-Authored-By: The Archivist <archivist@codex.svrnty.io>
Co-Authored-By: The Architech <architech@codex.svrnty.io>
Co-Authored-By: Mathias Beaulieu-Duncan <mat@svrnty.io>
2025-10-22 21:00:34 -04:00

192 lines
6.9 KiB
C#

using Xunit;
using Moq;
using Microsoft.AspNetCore.Http;
using OpenHarbor.MCP.Gateway.AspNetCore.Middleware;
using OpenHarbor.MCP.Gateway.Core.Interfaces;
using OpenHarbor.MCP.Gateway.Core.Models;
using System.Text;
using System.Text.Json;
namespace OpenHarbor.MCP.Gateway.AspNetCore.Tests.Middleware;
/// <summary>
/// Unit tests for GatewayMiddleware following TDD approach.
/// Tests HTTP request interception and gateway routing.
/// </summary>
public class GatewayMiddlewareTests
{
[Fact]
public async Task InvokeAsync_WithGatewayRequest_RoutesToGateway()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
mockRouter.Setup(r => r.RouteAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true, Result = new Dictionary<string, object> { ["data"] = "test" } });
var middleware = new GatewayMiddleware(
next: (HttpContext _) => Task.CompletedTask,
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "POST";
context.Request.Path = "/mcp/invoke";
context.Request.ContentType = "application/json";
var requestBody = JsonSerializer.Serialize(new { toolName = "test_tool", arguments = new { } });
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(requestBody));
context.Response.Body = new MemoryStream();
// Act
await middleware.InvokeAsync(context);
// Assert
mockRouter.Verify(r => r.RouteAsync(It.Is<GatewayRequest>(req => req.ToolName == "test_tool"), It.IsAny<CancellationToken>()), Times.Once);
Assert.Equal(200, context.Response.StatusCode);
}
[Fact]
public async Task InvokeAsync_WithNonGatewayPath_CallsNext()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
var nextCalled = false;
var middleware = new GatewayMiddleware(
next: (HttpContext _) => { nextCalled = true; return Task.CompletedTask; },
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "GET";
context.Request.Path = "/api/other";
// Act
await middleware.InvokeAsync(context);
// Assert
Assert.True(nextCalled);
mockRouter.Verify(r => r.RouteAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Fact]
public async Task InvokeAsync_WithGatewayError_Returns500()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
mockRouter.Setup(r => r.RouteAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = false, Error = "Routing failed", ErrorCode = "ROUTE_ERROR" });
var middleware = new GatewayMiddleware(
next: (HttpContext _) => Task.CompletedTask,
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "POST";
context.Request.Path = "/mcp/invoke";
context.Request.ContentType = "application/json";
var requestBody = JsonSerializer.Serialize(new { toolName = "test_tool" });
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(requestBody));
context.Response.Body = new MemoryStream();
// Act
await middleware.InvokeAsync(context);
// Assert
Assert.Equal(500, context.Response.StatusCode);
}
[Fact]
public async Task InvokeAsync_WithInvalidJson_Returns400()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
var middleware = new GatewayMiddleware(
next: (HttpContext _) => Task.CompletedTask,
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "POST";
context.Request.Path = "/mcp/invoke";
context.Request.ContentType = "application/json";
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("invalid json"));
context.Response.Body = new MemoryStream();
// Act
await middleware.InvokeAsync(context);
// Assert
Assert.Equal(400, context.Response.StatusCode);
}
[Fact]
public async Task InvokeAsync_ExtractsClientIdFromHeader()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
mockRouter.Setup(r => r.RouteAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true });
var middleware = new GatewayMiddleware(
next: (HttpContext _) => Task.CompletedTask,
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "POST";
context.Request.Path = "/mcp/invoke";
context.Request.ContentType = "application/json";
context.Request.Headers["X-Client-Id"] = "test-client";
var requestBody = JsonSerializer.Serialize(new { toolName = "test_tool" });
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(requestBody));
context.Response.Body = new MemoryStream();
// Act
await middleware.InvokeAsync(context);
// Assert
mockRouter.Verify(r => r.RouteAsync(
It.Is<GatewayRequest>(req => req.ClientId == "test-client"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task InvokeAsync_ReturnsJsonResponse()
{
// Arrange
var mockRouter = new Mock<IGatewayRouter>();
var expectedResult = new Dictionary<string, object> { ["status"] = "success", ["value"] = 42 };
mockRouter.Setup(r => r.RouteAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true, Result = expectedResult });
var middleware = new GatewayMiddleware(
next: (HttpContext _) => Task.CompletedTask,
router: mockRouter.Object
);
var context = new DefaultHttpContext();
context.Request.Method = "POST";
context.Request.Path = "/mcp/invoke";
context.Request.ContentType = "application/json";
var requestBody = JsonSerializer.Serialize(new { toolName = "test_tool" });
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(requestBody));
context.Response.Body = new MemoryStream();
// Act
await middleware.InvokeAsync(context);
// Assert
Assert.Equal("application/json", context.Response.ContentType);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await new StreamReader(context.Response.Body).ReadToEndAsync();
Assert.Contains("success", responseBody);
Assert.Contains("42", responseBody);
}
}