svrnty-mcp-gateway/tests/Svrnty.MCP.Gateway.Infrastructure.Tests/Routing/GatewayRouterTests.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

323 lines
12 KiB
C#

using Xunit;
using Moq;
using OpenHarbor.MCP.Gateway.Infrastructure.Routing;
using OpenHarbor.MCP.Gateway.Infrastructure.Connection;
using OpenHarbor.MCP.Gateway.Core.Interfaces;
using OpenHarbor.MCP.Gateway.Core.Models;
namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Routing;
/// <summary>
/// Unit tests for GatewayRouter following TDD approach.
/// Tests routing, server registration, and health tracking.
/// </summary>
public class GatewayRouterTests
{
[Fact]
public async Task RegisterServerAsync_AddsServerToPool()
{
// Arrange
var mockStrategy = new Mock<IRoutingStrategy>();
var mockPool = new Mock<IServerConnectionPool>();
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
// Act
await router.RegisterServerAsync(serverConfig);
// Assert
var health = await router.GetServerHealthAsync();
Assert.Contains(health, s => s.ServerId == "server-1");
}
[Fact]
public async Task UnregisterServerAsync_RemovesServerFromPool()
{
// Arrange
var mockStrategy = new Mock<IRoutingStrategy>();
var mockPool = new Mock<IServerConnectionPool>();
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
await router.RegisterServerAsync(serverConfig);
// Act
var result = await router.UnregisterServerAsync("server-1");
// Assert
Assert.True(result);
var health = await router.GetServerHealthAsync();
Assert.DoesNotContain(health, s => s.ServerId == "server-1");
}
[Fact]
public async Task UnregisterServerAsync_WithNonExistentServer_ReturnsFalse()
{
// Arrange
var mockStrategy = new Mock<IRoutingStrategy>();
var mockPool = new Mock<IServerConnectionPool>();
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
// Act
var result = await router.UnregisterServerAsync("non-existent");
// Assert
Assert.False(result);
}
[Fact]
public async Task RouteAsync_WithValidRequest_RoutesToSelectedServer()
{
// Arrange
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", Name = "Test Server", IsHealthy = true });
mockConnection.Setup(c => c.SendRequestAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true });
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ServerInfo { Id = "server-1", Name = "Test Server", IsHealthy = true });
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
await router.RegisterServerAsync(serverConfig);
var request = new GatewayRequest { ToolName = "test_tool" };
// Act
var response = await router.RouteAsync(request);
// Assert
Assert.NotNull(response);
Assert.True(response.Success);
mockConnection.Verify(c => c.SendRequestAsync(request, It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task RouteAsync_WithNoHealthyServers_ReturnsErrorResponse()
{
// Arrange
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((ServerInfo?)null);
var mockPool = new Mock<ServerConnectionPool>();
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
var request = new GatewayRequest { ToolName = "test_tool" };
// Act
var response = await router.RouteAsync(request);
// Assert
Assert.NotNull(response);
Assert.False(response.Success);
Assert.Contains("No healthy servers", response.Error);
}
[Fact]
public async Task GetServerHealthAsync_ReturnsAllRegisteredServers()
{
// Arrange
var mockStrategy = new Mock<IRoutingStrategy>();
var mockPool = new Mock<IServerConnectionPool>();
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
var server1 = new ServerConfig { Id = "server-1", Name = "Server 1", TransportType = "Http", BaseUrl = "http://localhost:5000" };
var server2 = new ServerConfig { Id = "server-2", Name = "Server 2", TransportType = "Http", BaseUrl = "http://localhost:5001" };
await router.RegisterServerAsync(server1);
await router.RegisterServerAsync(server2);
// Act
var health = await router.GetServerHealthAsync();
// Assert
Assert.Equal(2, health.Count());
Assert.Contains(health, s => s.ServerId == "server-1");
Assert.Contains(health, s => s.ServerId == "server-2");
}
[Fact]
public async Task RouteAsync_UsesRoutingStrategy()
{
// Arrange
var serverConfig = new ServerConfig
{
Id = "selected-server",
Name = "Selected Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "selected-server", IsHealthy = true });
mockConnection.Setup(c => c.SendRequestAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true });
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.Is<ServerConfig>(sc => sc.Id == "selected-server"), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ServerInfo { Id = "selected-server", IsHealthy = true });
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
await router.RegisterServerAsync(serverConfig);
var request = new GatewayRequest { ToolName = "test_tool", ClientId = "test-client" };
// Act
await router.RouteAsync(request);
// Assert - verify strategy was called
mockStrategy.Verify(s => s.SelectServerAsync(
It.IsAny<IEnumerable<ServerInfo>>(),
It.Is<RoutingContext>(rc => rc.ToolName == "test_tool" && rc.ClientId == "test-client"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task RouteAsync_SetsServerIdInResponse()
{
// Arrange
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", IsHealthy = true });
mockConnection.Setup(c => c.SendRequestAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true });
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ServerInfo { Id = "server-1", IsHealthy = true });
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
await router.RegisterServerAsync(serverConfig);
var request = new GatewayRequest { ToolName = "test_tool" };
// Act
var response = await router.RouteAsync(request);
// Assert
Assert.Equal("server-1", response.ServerId);
}
[Fact]
public async Task RouteAsync_TracksPassiveHealth_OnSuccess()
{
// Arrange
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", IsHealthy = true, ResponseTime = TimeSpan.FromMilliseconds(50) });
mockConnection.Setup(c => c.SendRequestAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new GatewayResponse { Success = true });
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ServerInfo { Id = "server-1", IsHealthy = true });
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
await router.RegisterServerAsync(serverConfig);
var request = new GatewayRequest { ToolName = "test_tool" };
// Act
await router.RouteAsync(request);
// Assert - passive health should be tracked (implementation will verify this)
mockConnection.Verify(c => c.SendRequestAsync(request, It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task RouteAsync_TracksPassiveHealth_OnFailure()
{
// Arrange
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", IsHealthy = true });
mockConnection.Setup(c => c.SendRequestAsync(It.IsAny<GatewayRequest>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("Request failed"));
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var mockStrategy = new Mock<IRoutingStrategy>();
mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny<IEnumerable<ServerInfo>>(), It.IsAny<RoutingContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ServerInfo { Id = "server-1", IsHealthy = true });
var router = new GatewayRouter(mockStrategy.Object, mockPool.Object);
await router.RegisterServerAsync(serverConfig);
var request = new GatewayRequest { ToolName = "test_tool" };
// Act
var response = await router.RouteAsync(request);
// Assert
Assert.False(response.Success);
Assert.Contains("Request failed", response.Error);
}
}