using Xunit; using OpenHarbor.MCP.Gateway.Infrastructure.Health; using OpenHarbor.MCP.Gateway.Core.Models; namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Health; /// /// Unit tests for PassiveHealthTracker following TDD approach. /// Tests passive health tracking based on response times and errors. /// public class PassiveHealthTrackerTests { [Fact] public void RecordSuccess_UpdatesHealthStatus() { // Arrange var tracker = new PassiveHealthTracker(); var serverId = "server-1"; var responseTime = TimeSpan.FromMilliseconds(50); // Act tracker.RecordSuccess(serverId, responseTime); // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.True(health.IsHealthy); Assert.Equal(responseTime, health.ResponseTime); } [Fact] public void RecordFailure_UpdatesHealthStatus() { // Arrange var tracker = new PassiveHealthTracker { UnhealthyThreshold = 1 // Mark unhealthy after 1 failure for this test }; var serverId = "server-2"; var errorMessage = "Connection timeout"; // Act tracker.RecordFailure(serverId, errorMessage); // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.False(health.IsHealthy); Assert.Equal(errorMessage, health.ErrorMessage); } [Fact] public void RecordSuccess_MultipleRequests_CalculatesAverageResponseTime() { // Arrange var tracker = new PassiveHealthTracker(); var serverId = "server-3"; // Act - record 3 successful requests tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(100)); tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(200)); tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(300)); // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.True(health.IsHealthy); // Average should be around 200ms Assert.NotNull(health.ResponseTime); Assert.InRange(health.ResponseTime.Value.TotalMilliseconds, 150, 250); } [Fact] public void RecordFailure_MultipleFailures_MarksServerUnhealthy() { // Arrange var tracker = new PassiveHealthTracker { UnhealthyThreshold = 3 // Mark unhealthy after 3 failures }; var serverId = "server-4"; // Act - record 3 failures tracker.RecordFailure(serverId, "Error 1"); tracker.RecordFailure(serverId, "Error 2"); tracker.RecordFailure(serverId, "Error 3"); // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.False(health.IsHealthy); } [Fact] public void RecordSuccess_AfterFailures_RecoverToHealthy() { // Arrange var tracker = new PassiveHealthTracker { UnhealthyThreshold = 2, HealthyThreshold = 3 // Recover after 3 successes }; var serverId = "server-5"; // Record failures to make unhealthy tracker.RecordFailure(serverId, "Error 1"); tracker.RecordFailure(serverId, "Error 2"); // Act - record successes to recover tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(50)); tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(50)); tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(50)); // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.True(health.IsHealthy); } [Fact] public void GetServerHealth_WithUnknownServer_ReturnsNull() { // Arrange var tracker = new PassiveHealthTracker(); // Act var health = tracker.GetServerHealth("unknown-server"); // Assert Assert.Null(health); } [Fact] public void GetAllServerHealth_ReturnsAllTrackedServers() { // Arrange var tracker = new PassiveHealthTracker { UnhealthyThreshold = 1 // Mark unhealthy after 1 failure for this test }; tracker.RecordSuccess("server-1", TimeSpan.FromMilliseconds(50)); tracker.RecordSuccess("server-2", TimeSpan.FromMilliseconds(100)); tracker.RecordFailure("server-3", "Connection failed"); // Act var allHealth = tracker.GetAllServerHealth(); // Assert Assert.Equal(3, allHealth.Count()); Assert.Contains(allHealth, h => h.ServerId == "server-1" && h.IsHealthy); Assert.Contains(allHealth, h => h.ServerId == "server-2" && h.IsHealthy); Assert.Contains(allHealth, h => h.ServerId == "server-3" && !h.IsHealthy); } [Fact] public void RecordSuccess_WithSlowResponse_MarksAsUnhealthy() { // Arrange var tracker = new PassiveHealthTracker { SlowResponseThreshold = TimeSpan.FromMilliseconds(100) }; var serverId = "server-6"; // Act - record slow response tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(500)); // Assert - should still be marked as success, but noted as slow var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.True(health.IsHealthy); // Still healthy, just slow Assert.Equal(TimeSpan.FromMilliseconds(500), health.ResponseTime); } [Fact] public void Reset_ClearsAllHealthData() { // Arrange var tracker = new PassiveHealthTracker(); tracker.RecordSuccess("server-1", TimeSpan.FromMilliseconds(50)); tracker.RecordFailure("server-2", "Error"); // Act tracker.Reset(); // Assert var allHealth = tracker.GetAllServerHealth(); Assert.Empty(allHealth); } [Fact] public void RecordSuccess_UpdatesLastCheckTime() { // Arrange var tracker = new PassiveHealthTracker(); var serverId = "server-7"; var beforeRecord = DateTime.UtcNow; // Act tracker.RecordSuccess(serverId, TimeSpan.FromMilliseconds(50)); var afterRecord = DateTime.UtcNow; // Assert var health = tracker.GetServerHealth(serverId); Assert.NotNull(health); Assert.True(health.LastCheck >= beforeRecord); Assert.True(health.LastCheck <= afterRecord); } [Fact] public void Constructor_SetsDefaultThresholds() { // Act var tracker = new PassiveHealthTracker(); // Assert Assert.Equal(5, tracker.UnhealthyThreshold); Assert.Equal(3, tracker.HealthyThreshold); Assert.Equal(TimeSpan.FromSeconds(5), tracker.SlowResponseThreshold); } }