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

220 lines
6.6 KiB
C#

using Xunit;
using OpenHarbor.MCP.Gateway.Infrastructure.Routing;
using OpenHarbor.MCP.Gateway.Core.Models;
namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Routing;
/// <summary>
/// Unit tests for ClientBasedStrategy following TDD approach.
/// Tests client-based routing.
/// </summary>
public class ClientBasedStrategyTests
{
[Fact]
public async Task SelectServerAsync_WithExactClientMatch_ReturnsMatchedServer()
{
// Arrange
var clientMapping = new Dictionary<string, string>
{
{ "web-client", "server-1" },
{ "mobile-client", "server-2" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "client-a", "server-1" },
{ "client-b", "server-2" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "known-client", "server-1" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "web-client", "server-1" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "web-client", "server-1" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>();
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "sticky-client", "server-1" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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<string, string>
{
{ "web-client", "server-1" }
};
var strategy = new ClientBasedStrategy(clientMapping);
var servers = new List<ServerInfo>
{
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);
}
}