using Xunit; using Svrnty.MCP.Gateway.Infrastructure.Routing; using Svrnty.MCP.Gateway.Core.Models; namespace Svrnty.MCP.Gateway.Infrastructure.Tests.Routing; /// /// Unit tests for ToolBasedStrategy following TDD approach. /// Tests tool-based routing with pattern matching. /// public class ToolBasedStrategyTests { [Fact] public async Task SelectServerAsync_WithExactMatch_ReturnsMatchedServer() { // Arrange var toolMapping = new Dictionary { { "search_documents", "server-1" }, { "get_document", "server-2" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = "search_documents" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.NotNull(selected); Assert.Equal("server-1", selected.Id); } [Fact] public async Task SelectServerAsync_WithWildcardPattern_MatchesCorrectly() { // Arrange var toolMapping = new Dictionary { { "search_*", "server-1" }, { "get_*", "server-2" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context1 = new RoutingContext { ToolName = "search_documents" }; var context2 = new RoutingContext { ToolName = "get_user" }; // 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 toolMapping = new Dictionary { { "search_*", "server-1" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = "unknown_tool" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should fall back to round-robin (first healthy server) Assert.NotNull(selected); Assert.True(selected.Id == "server-1" || selected.Id == "server-2"); } [Fact] public async Task SelectServerAsync_WhenMappedServerUnhealthy_SelectsNextHealthy() { // Arrange var toolMapping = new Dictionary { { "search_documents", "server-1" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = false }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = "search_documents" }; // 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_WithMultiplePatterns_UsesFirstMatch() { // Arrange var toolMapping = new Dictionary { { "search_*", "server-1" }, { "*_documents", "server-2" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = "search_documents" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should use first matching pattern Assert.NotNull(selected); Assert.Equal("server-1", selected.Id); } [Fact] public async Task SelectServerAsync_WithNullToolName_FallsBackToRoundRobin() { // Arrange var toolMapping = new Dictionary { { "search_*", "server-1" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = null }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.NotNull(selected); } [Fact] public async Task SelectServerAsync_WithEmptyMapping_UsesRoundRobin() { // Arrange var toolMapping = new Dictionary(); var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = true }, new ServerInfo { Id = "server-2", IsHealthy = true } }; var context = new RoutingContext { ToolName = "any_tool" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert - should use round-robin Assert.NotNull(selected); } [Fact] public async Task SelectServerAsync_WithNoHealthyServers_ReturnsNull() { // Arrange var toolMapping = new Dictionary { { "search_*", "server-1" } }; var strategy = new ToolBasedStrategy(toolMapping); var servers = new List { new ServerInfo { Id = "server-1", IsHealthy = false } }; var context = new RoutingContext { ToolName = "search_documents" }; // Act var selected = await strategy.SelectServerAsync(servers, context); // Assert Assert.Null(selected); } }