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

311 lines
10 KiB
C#

using Xunit;
using Moq;
using OpenHarbor.MCP.Gateway.Infrastructure.Health;
using OpenHarbor.MCP.Gateway.Core.Interfaces;
using OpenHarbor.MCP.Gateway.Core.Models;
namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Health;
/// <summary>
/// Unit tests for ActiveHealthChecker following TDD approach.
/// Tests periodic health checks with configurable intervals.
/// </summary>
public class ActiveHealthCheckerTests
{
[Fact]
public async Task CheckHealthAsync_WithHealthyServer_ReturnsHealthyStatus()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo
{
Id = "server-1",
Name = "Test Server",
IsHealthy = true,
ResponseTime = TimeSpan.FromMilliseconds(50)
});
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
// Act
var result = await checker.CheckHealthAsync(serverConfig);
// Assert
Assert.NotNull(result);
Assert.Equal("server-1", result.ServerId);
Assert.Equal("Test Server", result.ServerName);
Assert.True(result.IsHealthy);
Assert.NotNull(result.ResponseTime);
}
[Fact]
public async Task CheckHealthAsync_WithUnhealthyServer_ReturnsUnhealthyStatus()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(false);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo
{
Id = "server-2",
Name = "Unhealthy Server",
IsHealthy = false
});
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-2",
Name = "Unhealthy Server",
TransportType = "Http",
BaseUrl = "http://localhost:5001"
};
// Act
var result = await checker.CheckHealthAsync(serverConfig);
// Assert
Assert.NotNull(result);
Assert.Equal("server-2", result.ServerId);
Assert.False(result.IsHealthy);
}
[Fact]
public async Task CheckHealthAsync_WithConnectionException_ReturnsUnhealthyStatus()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("Connection failed"));
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-3",
Name = "Failed Server",
TransportType = "Http",
BaseUrl = "http://localhost:5002"
};
// Act
var result = await checker.CheckHealthAsync(serverConfig);
// Assert
Assert.NotNull(result);
Assert.Equal("server-3", result.ServerId);
Assert.False(result.IsHealthy);
Assert.Contains("Connection failed", result.ErrorMessage);
}
[Fact]
public async Task StartMonitoringAsync_BeginsPeriodicChecks()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo
{
Id = "server-1",
Name = "Test Server",
IsHealthy = true
});
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var checker = new ActiveHealthChecker(mockPool.Object)
{
CheckInterval = TimeSpan.FromMilliseconds(100) // Fast interval for testing
};
var serverConfigs = new List<ServerConfig>
{
new ServerConfig { Id = "server-1", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }
};
// Act
await checker.StartMonitoringAsync(serverConfigs);
await Task.Delay(50); // Allow initial check to start
// Assert - monitoring should be active
var health = await checker.GetCurrentHealthAsync();
Assert.NotNull(health);
Assert.Contains(health, s => s.ServerId == "server-1");
}
[Fact]
public async Task StopMonitoringAsync_EndsPeriodicChecks()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfigs = new List<ServerConfig>
{
new ServerConfig { Id = "server-1", Name = "Test Server", TransportType = "Http", BaseUrl = "http://localhost:5000" }
};
await checker.StartMonitoringAsync(serverConfigs);
// Act
await checker.StopMonitoringAsync();
// Assert - should complete without error
Assert.True(true);
}
[Fact]
public async Task GetCurrentHealthAsync_ReturnsLatestHealthStatus()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection = new Mock<IServerConnection>();
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo
{
Id = "server-1",
Name = "Test Server",
IsHealthy = true
});
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
// Perform a check to populate status
await checker.CheckHealthAsync(serverConfig);
// Act
var health = await checker.GetCurrentHealthAsync();
// Assert
Assert.NotNull(health);
Assert.Single(health);
Assert.Equal("server-1", health.First().ServerId);
Assert.True(health.First().IsHealthy);
}
[Fact]
public async Task StartMonitoringAsync_WithMultipleServers_ChecksAllServers()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection1 = new Mock<IServerConnection>();
var mockConnection2 = new Mock<IServerConnection>();
mockConnection1.Setup(c => c.IsConnected).Returns(true);
mockConnection1.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", IsHealthy = true });
mockConnection2.Setup(c => c.IsConnected).Returns(true);
mockConnection2.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-2", IsHealthy = true });
mockPool.Setup(p => p.GetConnectionAsync(It.Is<ServerConfig>(sc => sc.Id == "server-1"), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection1.Object);
mockPool.Setup(p => p.GetConnectionAsync(It.Is<ServerConfig>(sc => sc.Id == "server-2"), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection2.Object);
var checker = new ActiveHealthChecker(mockPool.Object)
{
CheckInterval = TimeSpan.FromMilliseconds(100)
};
var serverConfigs = new List<ServerConfig>
{
new ServerConfig { Id = "server-1", Name = "Server 1", TransportType = "Http", BaseUrl = "http://localhost:5000" },
new ServerConfig { Id = "server-2", Name = "Server 2", TransportType = "Http", BaseUrl = "http://localhost:5001" }
};
// Act
await checker.StartMonitoringAsync(serverConfigs);
await Task.Delay(50);
// Assert
var health = await checker.GetCurrentHealthAsync();
Assert.Equal(2, health.Count());
Assert.Contains(health, s => s.ServerId == "server-1");
Assert.Contains(health, s => s.ServerId == "server-2");
await checker.StopMonitoringAsync();
}
[Fact]
public void Constructor_WithNullPool_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new ActiveHealthChecker(null!));
}
[Fact]
public async Task CheckHealthAsync_WithNullConfig_ThrowsArgumentNullException()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var checker = new ActiveHealthChecker(mockPool.Object);
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => checker.CheckHealthAsync(null!));
}
[Fact]
public async Task CheckHealthAsync_SetsLastCheckTime()
{
// Arrange
var mockPool = new Mock<IServerConnectionPool>();
var mockConnection = new Mock<IServerConnection>();
var beforeCheck = DateTime.UtcNow;
mockConnection.Setup(c => c.IsConnected).Returns(true);
mockConnection.Setup(c => c.ServerInfo).Returns(new ServerInfo { Id = "server-1", IsHealthy = true });
mockPool.Setup(p => p.GetConnectionAsync(It.IsAny<ServerConfig>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockConnection.Object);
var checker = new ActiveHealthChecker(mockPool.Object);
var serverConfig = new ServerConfig
{
Id = "server-1",
Name = "Test Server",
TransportType = "Http",
BaseUrl = "http://localhost:5000"
};
// Act
var result = await checker.CheckHealthAsync(serverConfig);
var afterCheck = DateTime.UtcNow;
// Assert
Assert.True(result.LastCheck >= beforeCheck);
Assert.True(result.LastCheck <= afterCheck);
}
}