Chore: Got code coverage up to 98%

This commit is contained in:
2025-11-19 22:40:53 -05:00
parent 89197cd4d3
commit af5093c881
24 changed files with 2498 additions and 333 deletions
+168 -1
View File
@@ -7,6 +7,7 @@ from types import SimpleNamespace
from behave import given, then, when
from cleveragents.core import retry_patterns as retry_patterns_module
from cleveragents.core.exceptions import (
NetworkError,
RateLimitError,
@@ -24,12 +25,13 @@ from cleveragents.core.retry_patterns import (
retry_auto_debug,
retry_file_operation,
retry_network_operation,
retry_on_result,
retry_provider_operation,
retry_with_exponential_backoff,
retry_with_jitter,
retry_with_timeout,
should_retry_result,
)
from cleveragents.core import retry_patterns as retry_patterns_module
@given("I have the retry patterns module imported")
@@ -372,6 +374,28 @@ def step_verify_circuit_closed(context):
assert context.circuit_breaker.state == "closed"
@given("the circuit breaker has recorded failures")
def step_circuit_breaker_recorded_failures(context):
"""Simulate prior failures to exercise closed-state success path."""
context.circuit_breaker.failure_count = 3
context.circuit_breaker.state = "closed"
context.circuit_breaker.last_failure_time = time.time()
@when("I execute a successful call while the circuit is closed")
def step_success_call_closed_state(context):
"""Execute a successful call without half-open transition."""
context.closed_state_result = context.circuit_breaker.call(lambda: "closed success")
@then("the circuit breaker should reset failure tracking after success")
def step_verify_closed_state_success(context):
"""Ensure closed-state success resets counters."""
assert context.closed_state_result == "closed success"
assert context.circuit_breaker.failure_count == 0
assert context.circuit_breaker.state == "closed"
# Retry context tests
@given('I have a retry context for "{operation}"')
def step_create_retry_context(context, operation):
@@ -475,6 +499,96 @@ def step_verify_async_retry_context_manager_capture(context):
assert isinstance(context.retry_context.errors[-1], Exception)
@when("I execute a successful block under the retry context manager")
def step_retry_context_manager_success(context):
"""Execute a successful block to cover no-error path."""
errors_before = len(context.retry_context.errors)
with context.retry_context:
context.retry_context_success_value = "sync context success"
context.retry_context_errors_delta_sync = (
len(context.retry_context.errors) - errors_before
)
@then("the retry context manager should not record errors")
def step_verify_retry_context_manager_success(context):
"""Confirm no errors were recorded for successful context block."""
assert context.retry_context_success_value == "sync context success"
assert context.retry_context_errors_delta_sync == 0
@when("I execute a successful block under the async retry context manager")
def step_async_retry_context_manager_success(context):
"""Execute a successful async block to cover no-error path."""
errors_before = len(context.retry_context.errors)
async def run_async_block():
async with context.retry_context:
return "async context success"
loop = asyncio.new_event_loop()
try:
context.async_retry_context_manager_result = loop.run_until_complete(
run_async_block()
)
finally:
loop.close()
context.retry_context_errors_delta_async = (
len(context.retry_context.errors) - errors_before
)
@then("the async retry context manager should not record errors")
def step_verify_async_retry_context_manager_success(context):
"""Confirm async context manager leaves errors untouched when successful."""
assert context.async_retry_context_manager_result == "async context success"
assert context.retry_context_errors_delta_async == 0
@when("I execute the async function with the retry context")
def step_execute_async_with_retry_context(context):
"""Execute async function using RetryContext.async_execute."""
loop = asyncio.new_event_loop()
try:
context.async_execute_result = loop.run_until_complete(
context.retry_context.async_execute(context.test_function)
)
finally:
loop.close()
context.async_execute_attempts = context.retry_context.attempt_count
@then("the async retry context execute should succeed after {attempts:d} attempts")
def step_verify_async_execute_attempts(context, attempts):
"""Verify async execute succeeded after expected attempts."""
assert context.async_execute_result == "async success"
assert context.async_execute_attempts == attempts
@when("I apply retry with timeout of {max_attempts:d} attempts and {timeout} seconds")
def step_apply_retry_with_timeout(context, max_attempts, timeout):
"""Apply retry_with_timeout while stubbing out sleep delays."""
timeout_seconds = float(timeout)
original_sleep = time.sleep
time.sleep = lambda _seconds: None
def restore_sleep():
time.sleep = original_sleep
context.add_cleanup(restore_sleep)
decorated_func = retry_with_timeout(
max_attempts=max_attempts, timeout_seconds=timeout_seconds
)(context.test_function)
try:
context.result = decorated_func()
context.retry_succeeded = True
except Exception as exc: # pragma: no cover - defensive safety
context.retry_error = exc
context.retry_succeeded = False
# Retry with jitter tests
@given("I have multiple concurrent operations")
def step_setup_concurrent_operations(context):
@@ -528,6 +642,59 @@ def step_verify_no_simultaneous_retry(context):
assert max_in_bucket <= 3, "Too many operations retrying simultaneously"
# Retry on result tests
@given("I have sequential result payloads requiring a retry")
def step_setup_sequential_retry_results(context):
"""Create a deterministic sequence of results for retry_on_result tests."""
context.call_count = 0
context.result_sequence = [
{"retry": True, "value": "try-again"},
{"retry": False, "value": "done"},
]
def result_function():
context.call_count += 1
index = min(context.call_count - 1, len(context.result_sequence) - 1)
return context.result_sequence[index]
context.test_function = result_function
@given("I have a retry predicate that checks for retry flag")
def step_setup_retry_predicate(context):
"""Define predicate used by retry_on_result decorator."""
def predicate(result):
return isinstance(result, dict) and bool(result.get("retry"))
context.retry_predicate = predicate
@when("I apply retry on result with max {max_attempts:d} attempts")
def step_apply_retry_on_result(context, max_attempts):
"""Apply retry_on_result decorator and capture the outcome."""
decorated_func = retry_on_result(
context.retry_predicate, max_attempts=max_attempts
)(context.test_function)
try:
context.retry_on_result_output = decorated_func()
context.retry_on_result_succeeded = True
except Exception as exc: # pragma: no cover - defensive safety
context.retry_on_result_output = exc
context.retry_on_result_succeeded = False
@then("the decorated function should return the successful payload")
def step_verify_retry_on_result_payload(context):
"""Ensure retry_on_result eventually returns the non-retry payload."""
assert context.retry_on_result_succeeded, (
f"Retry on result failed: {context.retry_on_result_output}"
)
assert isinstance(context.retry_on_result_output, dict)
assert context.retry_on_result_output.get("retry") is False
# Auto-debug retry tests
@given("I have a function that fails with errors")
def step_create_failing_function_with_errors(context):