using Xunit; using Moq; using Svrnty.MCP.Gateway.Infrastructure.Connection; using Svrnty.MCP.Gateway.Core.Models; namespace Svrnty.MCP.Gateway.Infrastructure.Tests.Connection; /// /// Unit tests for ServerConnectionPool following TDD approach. /// Tests connection pooling, eviction, and limits. /// public class ServerConnectionPoolTests { [Fact] public async Task GetConnectionAsync_CreatesNewConnection() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool(); // Act var connection = await pool.GetConnectionAsync(serverConfig); // Assert Assert.NotNull(connection); Assert.Equal(serverConfig.Id, connection.ServerInfo.Id); } [Fact] public async Task GetConnectionAsync_ReusesSameConnection() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool(); // Act var connection1 = await pool.GetConnectionAsync(serverConfig); var connection2 = await pool.GetConnectionAsync(serverConfig); // Assert Assert.Same(connection1, connection2); // Should be the exact same instance } [Fact] public async Task ReleaseConnectionAsync_MarksConnectionAsAvailable() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool(); var connection = await pool.GetConnectionAsync(serverConfig); // Act await pool.ReleaseConnectionAsync(connection); // Assert - should be able to get it again var connection2 = await pool.GetConnectionAsync(serverConfig); Assert.Same(connection, connection2); } [Fact] public async Task GetConnectionAsync_WithMaxConnections_WaitsForAvailable() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool { MaxConnectionsPerServer = 1 }; var connection1 = await pool.GetConnectionAsync(serverConfig); // Act - try to get second connection (should wait or create new based on pool strategy) var getTask = pool.GetConnectionAsync(serverConfig); // Release first connection await pool.ReleaseConnectionAsync(connection1); var connection2 = await getTask; // Assert Assert.NotNull(connection2); } [Fact] public async Task EvictIdleConnectionsAsync_RemovesIdleConnections() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool { IdleTimeout = TimeSpan.FromMilliseconds(50) }; var connection = await pool.GetConnectionAsync(serverConfig); await pool.ReleaseConnectionAsync(connection); // Wait for idle timeout await Task.Delay(100); // Act await pool.EvictIdleConnectionsAsync(); // Assert - getting connection again should create a new one var connection2 = await pool.GetConnectionAsync(serverConfig); Assert.NotNull(connection2); // Note: Without access to internal state, we can't directly verify it's a new instance // This is tested indirectly through behavior } [Fact] public void GetPoolStats_ReturnsCorrectStats() { // Arrange var pool = new ServerConnectionPool(); // Act var stats = pool.GetPoolStats(); // Assert Assert.NotNull(stats); Assert.Equal(0, stats.TotalConnections); Assert.Equal(0, stats.ActiveConnections); Assert.Equal(0, stats.IdleConnections); } [Fact] public async Task GetPoolStats_ReflectsActiveConnections() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool(); // Act await pool.GetConnectionAsync(serverConfig); var stats = pool.GetPoolStats(); // Assert Assert.Equal(1, stats.TotalConnections); Assert.Equal(1, stats.ActiveConnections); } [Fact] public async Task GetPoolStats_ReflectsIdleConnections() { // Arrange var serverConfig = new ServerConfig { Id = "test", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }; var pool = new ServerConnectionPool(); var connection = await pool.GetConnectionAsync(serverConfig); await pool.ReleaseConnectionAsync(connection); // Act var stats = pool.GetPoolStats(); // Assert Assert.Equal(1, stats.TotalConnections); Assert.Equal(0, stats.ActiveConnections); Assert.Equal(1, stats.IdleConnections); } [Fact] public async Task Dispose_DisposesAllConnections() { // Arrange var serverConfig1 = new ServerConfig { Id = "test1", Name = "Test Server 1", TransportType = "Http", BaseUrl = "http://localhost:5001" }; var serverConfig2 = new ServerConfig { Id = "test2", Name = "Test Server 2", TransportType = "Http", BaseUrl = "http://localhost:5002" }; var pool = new ServerConnectionPool(); await pool.GetConnectionAsync(serverConfig1); await pool.GetConnectionAsync(serverConfig2); // Act pool.Dispose(); // Assert - getting stats after dispose should show zero connections var stats = pool.GetPoolStats(); Assert.Equal(0, stats.TotalConnections); } [Fact] public async Task GetConnectionAsync_DifferentServers_CreatesSeparateConnections() { // Arrange var serverConfig1 = new ServerConfig { Id = "test1", Name = "Test Server 1", TransportType = "Http", BaseUrl = "http://localhost:5001" }; var serverConfig2 = new ServerConfig { Id = "test2", Name = "Test Server 2", TransportType = "Http", BaseUrl = "http://localhost:5002" }; var pool = new ServerConnectionPool(); // Act var connection1 = await pool.GetConnectionAsync(serverConfig1); var connection2 = await pool.GetConnectionAsync(serverConfig2); // Assert Assert.NotSame(connection1, connection2); Assert.Equal("test1", connection1.ServerInfo.Id); Assert.Equal("test2", connection2.ServerInfo.Id); } }