Files
cleveragents-core/features/steps/plugins_loader_coverage_steps.py
T
freemo 051ee7c290
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 52s
CI / build (pull_request) Successful in 56s
CI / e2e_tests (pull_request) Successful in 5m1s
CI / integration_tests (pull_request) Successful in 5m30s
CI / unit_tests (pull_request) Successful in 5m42s
CI / docker (pull_request) Successful in 58s
CI / coverage (pull_request) Successful in 7m35s
CI / build (push) Successful in 21s
CI / docker (push) Has been skipped
CI / benchmark-regression (pull_request) Failing after 49m24s
CI / lint (push) Successful in 22s
CI / quality (push) Successful in 39s
CI / security (push) Successful in 48s
CI / typecheck (push) Successful in 1m26s
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Successful in 5m53s
CI / coverage (push) Successful in 9m4s
CI / benchmark-publish (push) Successful in 19m10s
CI / integration_tests (push) Failing after 19m18s
CI / unit_tests (push) Failing after 19m20s
test(coverage): add Behave BDD tests to improve coverage across 52 source files
Added 52 new .feature files and corresponding _steps.py files targeting
previously uncovered code paths in the following areas:

- TUI layer: app, commands, persona (state/schema/registry), widgets,
  input (shell_exec, reference_parser)
- Application services: plan lifecycle/service/executor, session,
  project, repo indexing, correction, checkpoint, actor, llm_actors,
  strategy coordinator, resource file watcher, service retry wiring
- CLI commands: session, resource, repl, plan, db, automation_profile
- Domain models: retry_policy, resource_type, cost_budget,
  docker_compose_analyzer, detail_level, _sql_string_aware,
  _postgresql_helpers
- Core: circuit_breaker, retry_service_patterns
- Infrastructure: repositories, transaction_sandbox, strategy_registry,
  plugins/loader, container
- Config: settings
- Agents: plan_generation, context_analysis, auto_debug
- A2A: facade

All new tests follow the Behave/Gherkin BDD standard. Resolved step
definition collisions with unique prefixes. Fixed Alembic fileConfig
logger disabling issue (disable_existing_loggers=False).

ISSUES CLOSED: #1068
2026-03-20 21:22:10 +00:00

202 lines
7.2 KiB
Python

"""Step definitions for plugins_loader_coverage.feature.
These steps target specific uncovered lines in
cleveragents/infrastructure/plugins/loader.py:
- Lines 203-209: except block in load_from_entry_points when ep.load() fails
- Lines 242, 244-246: validate_protocol fallback to issubclass on
instantiation failure
- Lines 247-248: validate_protocol when issubclass raises TypeError
"""
from typing import Protocol, runtime_checkable
from unittest.mock import MagicMock, patch
from behave import given, then, when
from cleveragents.infrastructure.plugins.exceptions import ProtocolMismatchError
from cleveragents.infrastructure.plugins.loader import PluginLoader
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the plugin loader module is imported")
def step_plugin_loader_imported(context):
"""Verify the PluginLoader class is importable."""
assert PluginLoader is not None
context.plugin_loader = PluginLoader()
# ---------------------------------------------------------------------------
# Entry-point discovery: failure path (lines 203-209)
# ---------------------------------------------------------------------------
@given("the entry points are mocked with one that raises on load")
def step_mock_failing_entry_point(context):
"""Create a mock entry point whose load() raises an exception."""
failing_ep = MagicMock()
failing_ep.name = "broken_plugin"
failing_ep.value = "some.module:BrokenClass"
failing_ep.load.side_effect = ImportError("Simulated import failure")
# Mock the entry_points() call to return a selectable object
mock_eps = MagicMock()
mock_eps.select.return_value = [failing_ep]
context.mock_entry_points = mock_eps
context.failing_ep = failing_ep
@when("I call load_from_entry_points")
def step_call_load_from_entry_points(context):
"""Call load_from_entry_points with the mocked entry points."""
with patch(
"cleveragents.infrastructure.plugins.loader.importlib.metadata.entry_points",
return_value=context.mock_entry_points,
):
context.discovered = context.plugin_loader.load_from_entry_points()
@then("the plugin result should be an empty descriptor list")
def step_verify_empty_descriptors(context):
"""The failing entry point should have been skipped."""
assert context.discovered == [], f"Expected empty list, got {context.discovered}"
@then("the failed entry point should have been logged as a warning")
def step_verify_warning_logged(context):
"""Verify the entry point was attempted (load was called)."""
context.failing_ep.load.assert_called_once()
# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass succeeds (lines 242, 244-246)
# ---------------------------------------------------------------------------
@runtime_checkable
class _SampleProtocol(Protocol):
"""A simple runtime-checkable Protocol for testing issubclass fallback."""
def do_work(self) -> str: ...
@given("I have a class that requires constructor arguments")
def step_class_requires_args(context):
"""Create a class whose __init__ requires mandatory arguments."""
class RequiresArgs(_SampleProtocol):
def __init__(self, mandatory_arg):
self._arg = mandatory_arg
def do_work(self) -> str:
return "done"
context.non_instantiable_class = RequiresArgs
@given("I have a runtime checkable protocol the class satisfies via issubclass")
def step_protocol_satisfied_by_subclass(context):
"""Store the protocol that the class satisfies structurally."""
context.target_protocol = _SampleProtocol
@when("I call validate_protocol with the non-instantiable class and protocol")
def step_call_validate_protocol_subclass_fallback(context):
"""Call validate_protocol; expect it to fall back to issubclass and succeed."""
context.validate_result = PluginLoader.validate_protocol(
context.non_instantiable_class,
context.target_protocol,
)
@then("validate_protocol should return True")
def step_verify_validate_true(context):
"""The fallback issubclass check should have returned True."""
assert context.validate_result is True
# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass raises TypeError (lines 247-248)
# ---------------------------------------------------------------------------
@given("I have a class that cannot be instantiated without arguments")
def step_class_cannot_instantiate(context):
"""Create a class whose __init__ raises when called with no args."""
class NeedsArgs:
def __init__(self, required):
self._r = required
context.non_instantiable_class = NeedsArgs
@given("I have a protocol-like object that causes issubclass to raise TypeError")
def step_protocol_causes_typeerror(context):
"""Create a protocol-like object that causes issubclass to raise TypeError.
We need an object that:
- Has a __name__ attribute (so the error message in validate_protocol works)
- Causes issubclass() to raise TypeError when used as second arg
- Also causes isinstance() to raise TypeError
A class with a metaclass that raises TypeError on __instancecheck__
and __subclasscheck__ achieves this.
"""
class TypeErrorMeta(type):
def __instancecheck__(cls, instance):
raise TypeError("Simulated TypeError from isinstance")
def __subclasscheck__(cls, instance):
raise TypeError("Simulated TypeError from issubclass")
class BadProtocol(metaclass=TypeErrorMeta):
pass
context.target_protocol = BadProtocol
@when("I call validate_protocol expecting a mismatch error")
def step_call_validate_protocol_expect_error(context):
"""Call validate_protocol and capture the expected error."""
try:
PluginLoader.validate_protocol(
context.non_instantiable_class,
context.target_protocol,
)
context.validate_error = None
except ProtocolMismatchError as exc:
context.validate_error = exc
except Exception as exc:
context.validate_error = exc
@then("a plugin-loader ProtocolMismatchError should be raised")
def step_verify_protocol_mismatch(context):
"""Verify that ProtocolMismatchError was raised."""
assert context.validate_error is not None, "Expected an error but none was raised"
assert isinstance(context.validate_error, ProtocolMismatchError), (
f"Expected ProtocolMismatchError, got {type(context.validate_error).__name__}: "
f"{context.validate_error}"
)
# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass returns False
# ---------------------------------------------------------------------------
@given("I have a runtime checkable protocol the class does not satisfy")
def step_protocol_not_satisfied(context):
"""Store a protocol the class does NOT satisfy."""
@runtime_checkable
class UnrelatedProtocol(Protocol):
def completely_different_method(self) -> int: ...
context.target_protocol = UnrelatedProtocol