Skip to main content
Glama
test_circuit_breaker.py6.8 kB
"""Tests for circuit breaker functionality.""" import time import pytest from igloo_mcp.circuit_breaker import ( CircuitBreaker, CircuitBreakerConfig, CircuitBreakerError, CircuitState, circuit_breaker, ) class CircuitBreakerTestException(Exception): """Test exception for circuit breaker tests.""" def test_circuit_breaker_config(): """Test circuit breaker configuration.""" config = CircuitBreakerConfig() assert config.failure_threshold == 5 assert config.recovery_timeout == 60.0 assert config.expected_exception is Exception custom_config = CircuitBreakerConfig( failure_threshold=3, recovery_timeout=30.0, expected_exception=CircuitBreakerTestException, ) assert custom_config.failure_threshold == 3 assert custom_config.recovery_timeout == 30.0 assert custom_config.expected_exception == CircuitBreakerTestException def test_circuit_breaker_closed_state(): """Test circuit breaker in closed state.""" config = CircuitBreakerConfig( failure_threshold=2, expected_exception=CircuitBreakerTestException ) breaker = CircuitBreaker(config) # Should start in closed state assert breaker.state == CircuitState.CLOSED assert breaker.failure_count == 0 # Successful calls should work def success_func(): return "success" result = breaker.call(success_func) assert result == "success" assert breaker.state == CircuitState.CLOSED assert breaker.failure_count == 0 def test_circuit_breaker_failure_counting(): """Test that circuit breaker counts failures correctly.""" config = CircuitBreakerConfig( failure_threshold=2, expected_exception=CircuitBreakerTestException ) breaker = CircuitBreaker(config) def failing_func(): raise CircuitBreakerTestException("Test failure") # First failure with pytest.raises(CircuitBreakerTestException): breaker.call(failing_func) assert breaker.failure_count == 1 assert breaker.state == CircuitState.CLOSED # Second failure should open the circuit with pytest.raises(CircuitBreakerTestException): breaker.call(failing_func) assert breaker.failure_count == 2 assert breaker.state == CircuitState.OPEN def test_circuit_breaker_open_state(): """Test circuit breaker in open state.""" config = CircuitBreakerConfig( failure_threshold=1, expected_exception=CircuitBreakerTestException ) breaker = CircuitBreaker(config) def failing_func(): raise CircuitBreakerTestException("Test failure") # Trigger failure to open circuit with pytest.raises(CircuitBreakerTestException): breaker.call(failing_func) assert breaker.state == CircuitState.OPEN # Subsequent calls should raise CircuitBreakerError def any_func(): return "should not execute" with pytest.raises(CircuitBreakerError): breaker.call(any_func) def test_circuit_breaker_recovery(): """Test circuit breaker recovery after timeout.""" config = CircuitBreakerConfig( failure_threshold=1, recovery_timeout=0.1, # 100ms expected_exception=CircuitBreakerTestException, ) breaker = CircuitBreaker(config) def failing_func(): raise CircuitBreakerTestException("Test failure") def success_func(): return "success" # Open the circuit with pytest.raises(CircuitBreakerTestException): breaker.call(failing_func) assert breaker.state == CircuitState.OPEN # Wait for recovery timeout time.sleep(0.2) # Next call should transition to half-open and succeed result = breaker.call(success_func) assert result == "success" assert breaker.state == CircuitState.CLOSED assert breaker.failure_count == 0 def test_circuit_breaker_decorator(): """Test circuit breaker decorator functionality.""" call_count = 0 @circuit_breaker( failure_threshold=2, expected_exception=CircuitBreakerTestException ) def decorated_func(should_fail=False): nonlocal call_count call_count += 1 if should_fail: raise CircuitBreakerTestException("Decorated failure") return f"success_{call_count}" # Successful calls assert decorated_func() == "success_1" assert decorated_func() == "success_2" # Failures with pytest.raises(CircuitBreakerTestException): decorated_func(should_fail=True) with pytest.raises(CircuitBreakerTestException): decorated_func(should_fail=True) # Circuit should be open now with pytest.raises(CircuitBreakerError): decorated_func() def test_circuit_breaker_ignores_unexpected_exceptions(): """Test that circuit breaker ignores unexpected exception types.""" config = CircuitBreakerConfig( failure_threshold=2, expected_exception=CircuitBreakerTestException ) breaker = CircuitBreaker(config) def func_with_unexpected_error(): raise ValueError("Unexpected error") # Unexpected exceptions should pass through without affecting circuit state with pytest.raises(ValueError): breaker.call(func_with_unexpected_error) assert breaker.failure_count == 0 assert breaker.state == CircuitState.CLOSED def test_circuit_breaker_half_open_success(): """Test successful recovery from half-open state.""" config = CircuitBreakerConfig( failure_threshold=1, recovery_timeout=0.1, expected_exception=CircuitBreakerTestException, ) breaker = CircuitBreaker(config) # Open the circuit with pytest.raises(CircuitBreakerTestException): breaker.call(lambda: (_ for _ in ()).throw(CircuitBreakerTestException("fail"))) # Wait and verify half-open transition time.sleep(0.2) # Success should reset to closed result = breaker.call(lambda: "recovered") assert result == "recovered" assert breaker.state == CircuitState.CLOSED def test_circuit_breaker_half_open_failure(): """Test failure in half-open state returns to open.""" config = CircuitBreakerConfig( failure_threshold=1, recovery_timeout=0.1, expected_exception=CircuitBreakerTestException, ) breaker = CircuitBreaker(config) # Open the circuit with pytest.raises(CircuitBreakerTestException): breaker.call(lambda: (_ for _ in ()).throw(CircuitBreakerTestException("fail"))) # Wait for recovery timeout time.sleep(0.2) # Failure in half-open should return to open with pytest.raises(CircuitBreakerTestException): breaker.call( lambda: (_ for _ in ()).throw(CircuitBreakerTestException("fail again")) ) assert breaker.state == CircuitState.OPEN

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Evan-Kim2028/igloo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server