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; /// /// Unit tests for ActiveHealthChecker following TDD approach. /// Tests periodic health checks with configurable intervals. /// public class ActiveHealthCheckerTests { [Fact] public async Task CheckHealthAsync_WithHealthyServer_ReturnsHealthyStatus() { // Arrange var mockPool = new Mock(); var mockConnection = new Mock(); 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(), It.IsAny())) .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(); var mockConnection = new Mock(); 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(), It.IsAny())) .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(); mockPool.Setup(p => p.GetConnectionAsync(It.IsAny(), It.IsAny())) .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(); var mockConnection = new Mock(); 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(), It.IsAny())) .ReturnsAsync(mockConnection.Object); var checker = new ActiveHealthChecker(mockPool.Object) { CheckInterval = TimeSpan.FromMilliseconds(100) // Fast interval for testing }; var serverConfigs = new List { 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(); var checker = new ActiveHealthChecker(mockPool.Object); var serverConfigs = new List { 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(); var mockConnection = new Mock(); 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(), It.IsAny())) .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(); var mockConnection1 = new Mock(); var mockConnection2 = new Mock(); 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(sc => sc.Id == "server-1"), It.IsAny())) .ReturnsAsync(mockConnection1.Object); mockPool.Setup(p => p.GetConnectionAsync(It.Is(sc => sc.Id == "server-2"), It.IsAny())) .ReturnsAsync(mockConnection2.Object); var checker = new ActiveHealthChecker(mockPool.Object) { CheckInterval = TimeSpan.FromMilliseconds(100) }; var serverConfigs = new List { 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(() => new ActiveHealthChecker(null!)); } [Fact] public async Task CheckHealthAsync_WithNullConfig_ThrowsArgumentNullException() { // Arrange var mockPool = new Mock(); var checker = new ActiveHealthChecker(mockPool.Object); // Act & Assert await Assert.ThrowsAsync(() => checker.CheckHealthAsync(null!)); } [Fact] public async Task CheckHealthAsync_SetsLastCheckTime() { // Arrange var mockPool = new Mock(); var mockConnection = new Mock(); 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(), It.IsAny())) .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); } }