using Xunit; using Svrnty.MCP.Gateway.Infrastructure.Routing; using Svrnty.MCP.Gateway.Core.Models; namespace Svrnty.MCP.Gateway.Infrastructure.Tests.Routing; /// /// Unit tests for ClientBasedStrategy following TDD approach. /// Tests client-based routing. /// public class ClientBasedStrategyTests { [Fact] public async Task SelectServerAsync_WithExactClientMatch_ReturnsMatchedServer() { // Arrange var clientMapping = new Dictionary { { "web-client", "server-1" }, { "mobile-client", "server-2" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = "web-client" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.NotNull(selected); Assert.Equal("server-1", selected.Id); } [Fact] public async Task SelectServerAsync_WithDifferentClients_RoutesDifferently() { // Arrange var clientMapping = new Dictionary { { "client-a", "server-1" }, { "client-b", "server-2" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context1 = new RoutingContext { ClientId = "client-a" }; var context2 = new RoutingContext { ClientId = "client-b" }; // Act var selected1 = await strategy.SelectServerAsync(servers, context1); var selected2 = await strategy.SelectServerAsync(servers, context2); // Assert Assert.Equal("server-1", selected1!.Id); Assert.Equal("server-2", selected2!.Id); } [Fact] public async Task SelectServerAsync_WithNoMatch_FallsBackToRoundRobin() { // Arrange var clientMapping = new Dictionary { { "known-client", "server-1" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = "unknown-client" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should fall back to round-robin Assert.NotNull(selected); } [Fact] public async Task SelectServerAsync_WhenMappedServerUnhealthy_SelectsNextHealthy() { // Arrange var clientMapping = new Dictionary { { "web-client", "server-1" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = false }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = "web-client" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should select next healthy server Assert.NotNull(selected); Assert.Equal("server-2", selected.Id); } [Fact] public async Task SelectServerAsync_WithNullClientId_FallsBackToRoundRobin() { // Arrange var clientMapping = new Dictionary { { "web-client", "server-1" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = null }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.NotNull(selected); } [Fact] public async Task SelectServerAsync_WithEmptyMapping_UsesRoundRobin() { // Arrange var clientMapping = new Dictionary(); var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = "any-client" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should use round-robin Assert.NotNull(selected); } [Fact] public async Task SelectServerAsync_SameClientMultipleTimes_RoutesSameServer() { // Arrange var clientMapping = new Dictionary { { "sticky-client", "server-1" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ClientId = "sticky-client" }; // Act var selected1 = await strategy.SelectServerAsync(servers, context); var selected2 = await strategy.SelectServerAsync(servers, context); var selected3 = await strategy.SelectServerAsync(servers, context); // Assert - should always route to same server (sticky sessions) Assert.Equal("server-1", selected1!.Id); Assert.Equal("server-1", selected2!.Id); Assert.Equal("server-1", selected3!.Id); } [Fact] public async Task SelectServerAsync_WithNoHealthyServers_ReturnsNull() { // Arrange var clientMapping = new Dictionary { { "web-client", "server-1" } }; var strategy = new ClientBasedStrategy(clientMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = false } }; var context = new RoutingContext { ClientId = "web-client" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.Null(selected); } }