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; /// /// Unit tests for GatewayRouter following TDD approach. /// Tests routing, server registration, and health tracking. /// public class GatewayRouterTests { [Fact] public async Task RegisterServerAsync_AddsServerToPool() { // Arrange var mockStrategy = new Mock(); var mockPool = new Mock(); 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(); var mockPool = new Mock(); 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(); var mockPool = new Mock(); 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(); 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(), It.IsAny())) .ReturnsAsync(new GatewayResponse { Success = true }); var mockPool = new Mock(); mockPool.Setup(p => p.GetConnectionAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(mockConnection.Object); var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .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()), Times.Once); } [Fact] public async Task RouteAsync_WithNoHealthyServers_ReturnsErrorResponse() { // Arrange var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync((ServerInfo?)null); var mockPool = new Mock(); 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(); var mockPool = new Mock(); 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(); 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(), It.IsAny())) .ReturnsAsync(new GatewayResponse { Success = true }); var mockPool = new Mock(); mockPool.Setup(p => p.GetConnectionAsync(It.Is(sc => sc.Id == "selected-server"), It.IsAny())) .ReturnsAsync(mockConnection.Object); var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .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>(), It.Is(rc => rc.ToolName == "test_tool" && rc.ClientId == "test-client"), It.IsAny()), 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(); 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(), It.IsAny())) .ReturnsAsync(new GatewayResponse { Success = true }); var mockPool = new Mock(); mockPool.Setup(p => p.GetConnectionAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(mockConnection.Object); var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .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(); 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(), It.IsAny())) .ReturnsAsync(new GatewayResponse { Success = true }); var mockPool = new Mock(); mockPool.Setup(p => p.GetConnectionAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(mockConnection.Object); var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .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()), 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(); 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(), It.IsAny())) .ThrowsAsync(new Exception("Request failed")); var mockPool = new Mock(); mockPool.Setup(p => p.GetConnectionAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(mockConnection.Object); var mockStrategy = new Mock(); mockStrategy.Setup(s => s.SelectServerAsync(It.IsAny>(), It.IsAny(), It.IsAny())) .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); } }