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

223 lines
6.8 KiB
C#

using Xunit;
using OpenHarbor.MCP.Gateway.Infrastructure.Health;
using OpenHarbor.MCP.Gateway.Core.Models;
namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Health;
/// <summary>
/// Unit tests for PassiveHealthTracker following TDD approach.
/// Tests passive health tracking based on response times and errors.
/// </summary>
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);
}
}