svrnty-mcp-gateway/tests/Svrnty.MCP.Gateway.Infrastructure.Tests/Health/CircuitBreakerTests.cs
Svrnty 19ef79362e refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries
- Renamed all directories: OpenHarbor.MCP.* → Svrnty.MCP.*
- Updated all namespaces in 179 C# files
- Renamed 20 .csproj files and 3 .sln files
- Updated 193 documentation references
- Updated 33 references in main CODEX codebase
- Updated Codex.sln with new paths
- Build verified: 0 errors

Preparing for extraction to standalone repositories.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 21:04:17 -04:00

304 lines
7.5 KiB
C#

using Xunit;
using Svrnty.MCP.Gateway.Infrastructure.Health;
using Svrnty.MCP.Gateway.Core.Models;
namespace Svrnty.MCP.Gateway.Infrastructure.Tests.Health;
/// <summary>
/// Unit tests for CircuitBreaker following TDD approach.
/// Tests circuit breaker states: Closed, Open, HalfOpen.
/// </summary>
public class CircuitBreakerTests
{
[Fact]
public async Task ExecuteAsync_InClosedState_AllowsExecution()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 3
};
var executed = false;
// Act
await breaker.ExecuteAsync(async () =>
{
executed = true;
await Task.CompletedTask;
});
// Assert
Assert.True(executed);
Assert.Equal(CircuitBreakerState.Closed, breaker.State);
}
[Fact]
public async Task ExecuteAsync_AfterMultipleFailures_OpensCircuit()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 3,
OpenTimeout = TimeSpan.FromSeconds(1)
};
// Act - trigger 3 failures
for (int i = 0; i < 3; i++)
{
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Simulated failure");
});
}
catch
{
// Expected
}
}
// Assert
Assert.Equal(CircuitBreakerState.Open, breaker.State);
}
[Fact]
public async Task ExecuteAsync_WhenOpen_ThrowsCircuitBreakerOpenException()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 1,
OpenTimeout = TimeSpan.FromSeconds(10)
};
// Trip the breaker
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
});
}
catch
{
// Expected
}
// Act & Assert - breaker should be open
await Assert.ThrowsAsync<CircuitBreakerOpenException>(() =>
breaker.ExecuteAsync(async () => await Task.CompletedTask));
}
[Fact]
public async Task ExecuteAsync_AfterTimeout_TransitionsToHalfOpen()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 1,
OpenTimeout = TimeSpan.FromMilliseconds(100),
SuccessThreshold = 1 // Close after 1 success
};
// Trip the breaker
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
});
}
catch
{
// Expected
}
Assert.Equal(CircuitBreakerState.Open, breaker.State);
// Act - wait for timeout
await Task.Delay(150);
// Try another execution (should transition to half-open)
var executed = false;
await breaker.ExecuteAsync(async () =>
{
executed = true;
await Task.CompletedTask;
});
// Assert
Assert.True(executed);
Assert.Equal(CircuitBreakerState.Closed, breaker.State); // Successful execution closes the circuit
}
[Fact]
public async Task ExecuteAsync_InHalfOpen_SuccessfulCallClosesCircuit()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 1,
OpenTimeout = TimeSpan.FromMilliseconds(100),
SuccessThreshold = 1
};
// Trip the breaker
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
});
}
catch
{
// Expected
}
await Task.Delay(150); // Wait for open timeout
// Act - successful call in half-open
await breaker.ExecuteAsync(async () => await Task.CompletedTask);
// Assert
Assert.Equal(CircuitBreakerState.Closed, breaker.State);
}
[Fact]
public async Task ExecuteAsync_InHalfOpen_FailureReopensCircuit()
{
// Arrange
var breaker = new CircuitBreaker
{
FailureThreshold = 1,
OpenTimeout = TimeSpan.FromMilliseconds(100)
};
// Trip the breaker
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
});
}
catch
{
// Expected
}
await Task.Delay(150); // Wait for open timeout (now in half-open)
// Act - fail in half-open
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Another failure");
});
}
catch
{
// Expected
}
// Assert
Assert.Equal(CircuitBreakerState.Open, breaker.State);
}
[Fact]
public async Task ExecuteAsync_WithReturnValue_ReturnsResult()
{
// Arrange
var breaker = new CircuitBreaker();
var expectedValue = 42;
// Act
var result = await breaker.ExecuteAsync(async () =>
{
await Task.CompletedTask;
return expectedValue;
});
// Assert
Assert.Equal(expectedValue, result);
}
[Fact]
public void Reset_ClosesCircuitAndClearsFailures()
{
// Arrange
var breaker = new CircuitBreaker { FailureThreshold = 1 };
try
{
breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
}).GetAwaiter().GetResult();
}
catch
{
// Expected
}
Assert.Equal(CircuitBreakerState.Open, breaker.State);
// Act
breaker.Reset();
// Assert
Assert.Equal(CircuitBreakerState.Closed, breaker.State);
}
[Fact]
public void Constructor_SetsDefaultValues()
{
// Act
var breaker = new CircuitBreaker();
// Assert
Assert.Equal(CircuitBreakerState.Closed, breaker.State);
Assert.Equal(5, breaker.FailureThreshold);
Assert.Equal(3, breaker.SuccessThreshold);
Assert.Equal(TimeSpan.FromSeconds(60), breaker.OpenTimeout);
}
[Fact]
public async Task ExecuteAsync_TracksConcurrentFailures()
{
// Arrange
var breaker = new CircuitBreaker { FailureThreshold = 5 };
var failureCount = 0;
// Act - cause 4 failures (not enough to trip)
for (int i = 0; i < 4; i++)
{
try
{
await breaker.ExecuteAsync<object>(async () =>
{
await Task.CompletedTask;
throw new Exception("Failure");
});
}
catch
{
failureCount++;
}
}
// Assert - should still be closed
Assert.Equal(CircuitBreakerState.Closed, breaker.State);
Assert.Equal(4, failureCount);
}
}