Files
temp/features/steps/error_recovery_coverage_boost_steps.py
freemo 02250473ad fix(ci): restore all CI quality gates to passing on master
Fix all failing CI quality gates (lint, unit_tests, format) without
suppressing any quality enforcement.

Root causes and fixes:

1. Format: features/steps/plan_namespaced_name_tdd_steps.py had trailing
   whitespace; fixed by running ruff format.

2. Unit tests - A2A JSON-RPC 2.0 migration (commit 9c6d6915) renamed
   A2aRequest fields (operation→method, request_id→id, a2a_version→jsonrpc)
   and A2aResponse fields (status+data→result, request_id→id) but did not
   update all step files and feature files:
   - a2a_jsonrpc_wire_format_steps.py: added use_step_matcher('re') and
     reset to 'parse' at end to prevent parallel test interference
   - a2a_facade_wiring_steps.py: updated operation= to method=, .status/.data
     to .result
   - a2a_facade_steps.py: updated request_id→id, a2a_version→jsonrpc,
     A2aResponse(request_id=..., status=...) to new API
   - m6_facade_steps.py: updated all old API usage
   - devcontainer_cleanup_steps.py: updated A2aRequest(operation=...)
   - plan_prompt_command_steps.py: updated A2aRequest(operation=...)
   - wf03_plan_prompt_confidence_steps.py: updated A2aRequest(operation=...)
   - consolidated_misc.feature: updated old A2aRequest/A2aResponse scenarios

3. Unit tests - Session CLI output changed (commit 0d5d9cf0 and others):
   - 'Session Created' → 'Session created' (lowercase)
   - 'Session Details' → 'Session Summary'
   - 'Sessions (N total)' → 'Sessions'
   - session list JSON: top-level 'total' → nested 'summary.total'
   - Fixed in: session_cli.feature, session_cli_coverage_boost.feature,
     session_cli_uncovered_branches.feature, session_list_error.feature,
     tdd_session_create_persist_steps.py

4. Unit tests - Plan list output changed (commit 1a07a891):
   - 'V3 Lifecycle Plans' → 'Plans'
   - 'Lifecycle Plans' → 'Plans'
   - Name column removed (restored in source)
   - Invariants column removed (restored in source)
   - Project truncation removed (restored in source)
   - Fixed in: plan_cli_cancel_revert_coverage.feature,
     plan_lifecycle_cli_coverage.feature, plan_cli_coverage_boost_steps.py,
     plan.py (source code restored)

5. Unit tests - Plan apply command now requires ULID (commit 300a5d6d):
   - plan_cli_coverage_r3.feature: updated 'PLAN-001' to valid ULID
   - plan_cli_coverage_r3_steps.py: added --yes flag, added new step for
     no-eligible-plans path

6. Unit tests - Various source code bugs:
   - ThoughtBlock: converted from @dataclass to Pydantic BaseModel
     (architecture test requires all dataclasses to use Pydantic)
   - session.py: added DatabaseError handling to export, import, tell commands
   - database.py: fixed rollback_to() to reuse checkpoint connection for writes
   - database.py: added _get_checkpoint_conn() helper
   - check-tls-cert.py: fixed SSLCertVerificationError.reason AttributeError

7. Unit tests - Test step bugs:
   - error_recovery_coverage_boost_steps.py: fixed invalid ULID _PLAN_ID
   - session_service_coverage_steps.py: fixed 'sha256:' prefix bug in checksum
   - database_models_new_coverage_steps.py: added 'name' field to session mock
   - async_audit_recording_steps.py: fixed Settings(audit_async=False) via env var
   - coverage_threshold_config_steps.py: added --coverage-min pattern support
   - m5_acms_smoke_steps.py: updated usage hint text
   - actor_cli_yaml_steps.py: updated 'Removed actor' → 'Actor removed'
   - aimodelscredentials_steps.py: set context.imported_class in import step
   - domain_base_model.feature: added missing 'When I examine model_config' step
   - tui_first_run_steps.py: fixed module reload to restore cleveragents.tui.*
     modules after test (prevented patch interference in subsequent tests)
   - tui_first_run_steps.py: added set_search('') step for empty string
   - resource_handler_base_coverage_r3_steps.py: use _MinimalHandler instead
     of DatabaseResourceHandler for NotImplementedError tests
   - resource_handler_crud.feature: updated to test new DatabaseHandler behavior
   - resource_handler_sandbox.feature: updated to test new DatabaseHandler behavior
   - tdd_json_decode_crash_persistence.feature: fixed @tdd_bug → @tdd_issue tags

8. Parallel test interference:
   - All step files using use_step_matcher('re') now reset to 'parse' at end
     to prevent global matcher state leaking to subsequent step files
2026-04-04 20:38:16 +00:00

736 lines
25 KiB
Python

"""Step definitions for error_recovery_coverage_boost.feature.
Covers uncovered paths in plan.py (errors/diff/artifacts CLI commands),
plan_executor.py (edge cases, retry loop), and error_recovery.py (policy
branches) introduced by the error recovery feature.
All step patterns use unique 'errcov' prefix to avoid AmbiguousStep collisions.
"""
from __future__ import annotations
import contextlib
from io import StringIO
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.core.exceptions import CleverAgentsError, PlanError, ValidationError
from cleveragents.domain.models.core.error_recovery import (
ErrorCategory,
ErrorRecord,
ErrorRecoveryPolicy,
get_recovery_hints,
)
from cleveragents.domain.models.core.plan import (
PlanPhase,
PlanTimestamps,
ProcessingState,
)
__all__: list[str] = []
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
_PLAN_ID = "01ERRCVV000000000000000001"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_capture_console(buf: StringIO):
"""Create a Rich Console that writes to the buffer for assertion."""
from rich.console import Console
return Console(file=buf, force_terminal=False, width=200)
def _make_mock_plan(
*,
plan_id: str = _PLAN_ID,
phase: PlanPhase = PlanPhase.EXECUTE,
state: ProcessingState = ProcessingState.ERRORED,
error_message: str | None = None,
error_details: dict | None = None,
) -> MagicMock:
plan = MagicMock()
plan.identity.plan_id = plan_id
plan.phase = phase
plan.state = state
plan.processing_state = state
plan.is_terminal = False
plan.error_message = error_message
plan.error_details = error_details
plan.timestamps = PlanTimestamps()
return plan
def _capture_plan_cli(context: Context, args: list[str]) -> str:
"""Invoke a plan CLI command, capturing console output."""
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
try:
with patch.object(
plan_mod.console,
"print",
side_effect=lambda *a, **kw: buf.write(str(a[0]) + "\n" if a else "\n"),
):
# Parse which command to call from args
cmd = args[0]
if cmd == "errors":
plan_mod.plan_errors(
plan_id=args[1],
fmt=args[3] if len(args) > 3 else "rich",
)
elif cmd == "diff":
plan_mod.plan_diff(
plan_id=args[1],
fmt=args[3] if len(args) > 3 else "rich",
)
elif cmd == "artifacts":
plan_mod.plan_artifacts(
plan_id=args[1],
fmt=args[3] if len(args) > 3 else "rich",
)
except (SystemExit, Exception):
pass
return buf.getvalue()
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("an errcov test environment")
def step_errcov_env(context: Context) -> None:
context.errcov_plan = None
context.errcov_output = ""
context.errcov_service = None
context.errcov_error = None
context.errcov_apply_svc = None
context.errcov_lifecycle_svc = None
# ---------------------------------------------------------------------------
# Plan errors CLI -- Given steps
# ---------------------------------------------------------------------------
@given('an errcov plan with error details category "{cat}" phase "{phase}"')
def step_errcov_plan_with_details(context: Context, cat: str, phase: str) -> None:
context.errcov_plan = _make_mock_plan(
error_details={
"error_category": cat,
"error_phase": phase,
"retry_count": "0",
"max_retries": "3",
"is_retriable": "true",
},
)
@given('the errcov plan has error message "{msg}"')
def step_errcov_plan_error_msg(context: Context, msg: str) -> None:
context.errcov_plan.error_message = msg
@given('the errcov plan error details include actor "{actor}" and tool "{tool}"')
def step_errcov_plan_actor_tool(context: Context, actor: str, tool: str) -> None:
context.errcov_plan.error_details["error_actor"] = actor
context.errcov_plan.error_details["error_tool_call"] = tool
@given('the errcov plan error details include stack summary "{summary}"')
def step_errcov_plan_stack(context: Context, summary: str) -> None:
context.errcov_plan.error_details["stack_summary"] = summary
@given(
'the errcov plan error details include retry count "{rc}" max "{mx}" retriable "{retr}"'
)
def step_errcov_plan_retry_info(context: Context, rc: str, mx: str, retr: str) -> None:
context.errcov_plan.error_details["retry_count"] = rc
context.errcov_plan.error_details["max_retries"] = mx
context.errcov_plan.error_details["is_retriable"] = retr
@given("an errcov plan with no error details and no error message")
def step_errcov_plan_clean(context: Context) -> None:
context.errcov_plan = _make_mock_plan(
error_details=None,
error_message=None,
)
@given('an errcov plan with no error details but has error message "{msg}"')
def step_errcov_plan_msg_only(context: Context, msg: str) -> None:
context.errcov_plan = _make_mock_plan(
error_details=None,
error_message=msg,
)
@given("an errcov lifecycle service that raises CleverAgentsError on get_plan")
def step_errcov_lifecycle_error(context: Context) -> None:
svc = MagicMock()
svc.get_plan.side_effect = CleverAgentsError("plan lookup failed")
context.errcov_lifecycle_svc = svc
# ---------------------------------------------------------------------------
# Plan errors CLI -- When steps
# ---------------------------------------------------------------------------
@when('I invoke errcov plan errors in "{fmt}" format')
def step_errcov_invoke_errors(context: Context, fmt: str) -> None:
import contextlib
from cleveragents.cli.commands import plan as plan_mod
svc = MagicMock()
svc.get_plan.return_value = context.errcov_plan
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod,
"_get_lifecycle_service",
return_value=svc,
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_errors(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
@when("I invoke errcov plan errors expecting abort")
def step_errcov_invoke_errors_abort(context: Context) -> None:
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod,
"_get_lifecycle_service",
return_value=context.errcov_lifecycle_svc,
),
patch.object(plan_mod, "console", capture_console),
contextlib.suppress(SystemExit, Exception),
):
plan_mod.plan_errors(plan_id=_PLAN_ID, fmt="rich")
context.errcov_output = buf.getvalue()
# ---------------------------------------------------------------------------
# Plan diff CLI -- Given/When steps
# ---------------------------------------------------------------------------
@given('an errcov apply service returning diff text "{text}"')
def step_errcov_apply_diff(context: Context, text: str) -> None:
svc = MagicMock()
svc.diff.return_value = text
context.errcov_apply_svc = svc
@given("an errcov apply service that raises PlanError on diff")
def step_errcov_apply_diff_plan_error(context: Context) -> None:
svc = MagicMock()
svc.diff.side_effect = PlanError("diff failed")
context.errcov_apply_svc = svc
@given("an errcov apply service that raises CleverAgentsError on diff")
def step_errcov_apply_diff_agent_error(context: Context) -> None:
svc = MagicMock()
svc.diff.side_effect = CleverAgentsError("diff service error")
context.errcov_apply_svc = svc
@when('I invoke errcov plan diff in "{fmt}" format')
def step_errcov_invoke_diff(context: Context, fmt: str) -> None:
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_diff(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
@when("I invoke errcov plan diff expecting abort")
def step_errcov_invoke_diff_abort(context: Context) -> None:
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.suppress(SystemExit, Exception),
):
plan_mod.plan_diff(plan_id=_PLAN_ID, fmt="rich")
context.errcov_output = buf.getvalue()
# ---------------------------------------------------------------------------
# Plan artifacts CLI -- Given/When steps
# ---------------------------------------------------------------------------
@given('an errcov apply service returning artifacts text "{text}"')
def step_errcov_apply_artifacts(context: Context, text: str) -> None:
svc = MagicMock()
svc.artifacts.return_value = text
context.errcov_apply_svc = svc
@given("an errcov apply service that raises PlanError on artifacts")
def step_errcov_apply_artifacts_plan_error(context: Context) -> None:
svc = MagicMock()
svc.artifacts.side_effect = PlanError("artifacts failed")
context.errcov_apply_svc = svc
@given("an errcov apply service that raises CleverAgentsError on artifacts")
def step_errcov_apply_artifacts_agent_error(context: Context) -> None:
svc = MagicMock()
svc.artifacts.side_effect = CleverAgentsError("artifacts service error")
context.errcov_apply_svc = svc
@when('I invoke errcov plan artifacts in "{fmt}" format')
def step_errcov_invoke_artifacts(context: Context, fmt: str) -> None:
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_artifacts(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
@when("I invoke errcov plan artifacts expecting abort")
def step_errcov_invoke_artifacts_abort(context: Context) -> None:
from cleveragents.cli.commands import plan as plan_mod
buf = StringIO()
capture_console = _make_capture_console(buf)
with (
patch.object(
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.suppress(SystemExit, Exception),
):
plan_mod.plan_artifacts(plan_id=_PLAN_ID, fmt="rich")
context.errcov_output = buf.getvalue()
# ---------------------------------------------------------------------------
# _get_apply_service
# ---------------------------------------------------------------------------
@when("I invoke errcov _get_apply_service")
def step_errcov_get_apply_svc(context: Context) -> None:
from cleveragents.cli.commands import plan as plan_mod
mock_lifecycle = MagicMock()
with patch.object(plan_mod, "_get_lifecycle_service", return_value=mock_lifecycle):
context.errcov_apply_svc = plan_mod._get_apply_service()
@then("the errcov apply service should be a PlanApplyService instance")
def step_errcov_apply_svc_type(context: Context) -> None:
from cleveragents.application.services.plan_apply_service import PlanApplyService
assert isinstance(context.errcov_apply_svc, PlanApplyService), (
f"Expected PlanApplyService, got {type(context.errcov_apply_svc)}"
)
# ---------------------------------------------------------------------------
# PlanExecutor edge cases
# ---------------------------------------------------------------------------
@when("I invoke errcov strategize with empty plan_id")
def step_errcov_strategize_empty(context: Context) -> None:
from cleveragents.application.services.plan_executor import StrategizeStubActor
actor = StrategizeStubActor()
try:
actor.execute(plan_id="", definition_of_done="test", stream_callback=None)
context.errcov_error = None
except ValidationError as exc:
context.errcov_error = exc
@when("I invoke errcov execute stub with empty plan_id")
def step_errcov_execute_stub_empty(context: Context) -> None:
from cleveragents.application.services.plan_executor import ExecuteStubActor
actor = ExecuteStubActor()
try:
actor.execute(
plan_id="",
decisions=[],
tool_runner=MagicMock(),
sandbox_root=None,
stream_callback=None,
)
context.errcov_error = None
except ValidationError as exc:
context.errcov_error = exc
@given("an errcov plan in strategize phase")
def step_errcov_plan_strategize(context: Context) -> None:
plan = _make_mock_plan(phase=PlanPhase.STRATEGIZE, state=ProcessingState.QUEUED)
lifecycle = MagicMock()
lifecycle.get_plan.return_value = plan
context.errcov_lifecycle_svc = lifecycle
context.errcov_plan = plan
@given("an errcov plan in execute phase but processing state")
def step_errcov_plan_execute_processing(context: Context) -> None:
plan = _make_mock_plan(phase=PlanPhase.EXECUTE, state=ProcessingState.PROCESSING)
lifecycle = MagicMock()
lifecycle.get_plan.return_value = plan
context.errcov_lifecycle_svc = lifecycle
context.errcov_plan = plan
@when("I invoke errcov run_execute on wrong-phase plan")
def step_errcov_execute_wrong_phase(context: Context) -> None:
from cleveragents.application.services.plan_executor import PlanExecutor
executor = PlanExecutor(
lifecycle_service=context.errcov_lifecycle_svc,
tool_runner=MagicMock(),
)
try:
executor.run_execute(plan_id=_PLAN_ID)
context.errcov_error = None
except PlanError as exc:
context.errcov_error = exc
@when("I invoke errcov run_execute on wrong-state plan")
def step_errcov_execute_wrong_state(context: Context) -> None:
from cleveragents.application.services.plan_executor import PlanExecutor
executor = PlanExecutor(
lifecycle_service=context.errcov_lifecycle_svc,
tool_runner=MagicMock(),
)
try:
executor.run_execute(plan_id=_PLAN_ID)
context.errcov_error = None
except PlanError as exc:
context.errcov_error = exc
@when("I parse errcov definition of done with blank lines")
def step_errcov_parse_dod_blank(context: Context) -> None:
from cleveragents.application.services.plan_executor import StrategizeStubActor
actor = StrategizeStubActor()
context.errcov_parsed_steps = actor._parse_steps("Step one\n\n\nStep two\n\n")
@when('I parse errcov definition of done with numbered items "{text}"')
def step_errcov_parse_dod_numbered(context: Context, text: str) -> None:
from cleveragents.application.services.plan_executor import StrategizeStubActor
actor = StrategizeStubActor()
context.errcov_parsed_steps = actor._parse_steps(text.replace("\\n", "\n"))
@given("an errcov executor with error recovery service")
def step_errcov_executor_with_recovery(context: Context) -> None:
from cleveragents.application.services.error_recovery_service import (
ErrorRecoveryService,
)
from cleveragents.application.services.plan_executor import PlanExecutor
plan = _make_mock_plan(phase=PlanPhase.STRATEGIZE, state=ProcessingState.QUEUED)
plan.definition_of_done = "Test passes"
plan.metadata = {}
plan.invariants = []
lifecycle = MagicMock()
lifecycle.get_plan.return_value = plan
er_service = ErrorRecoveryService(
lifecycle_service=lifecycle,
auto_retry_threshold=0.0,
max_retries=3,
)
context.errcov_lifecycle_svc = lifecycle
context.errcov_er_service = er_service
context.errcov_executor = PlanExecutor(
lifecycle_service=lifecycle,
tool_runner=MagicMock(),
error_recovery_service=er_service,
)
@given('the errcov strategize actor raises RuntimeError "{msg}"')
def step_errcov_strategize_fails(context: Context, msg: str) -> None:
mock_actor = MagicMock()
mock_actor.execute.side_effect = RuntimeError(msg)
context.errcov_executor._strategize_actor = mock_actor
@when("I invoke errcov run_strategize expecting failure")
def step_errcov_run_strategize_fail(context: Context) -> None:
with contextlib.suppress(RuntimeError):
context.errcov_executor.run_strategize(plan_id=_PLAN_ID)
@then("the errcov error recovery should have recorded a strategize error")
def step_errcov_er_strategize_recorded(context: Context) -> None:
history = context.errcov_er_service.get_error_history(_PLAN_ID)
assert history.total_errors >= 1, (
f"Expected at least 1 error recorded, got {history.total_errors}"
)
assert history.records[0].phase == "strategize"
@given("an errcov executor without error recovery service")
def step_errcov_executor_no_recovery(context: Context) -> None:
from cleveragents.application.services.plan_executor import PlanExecutor
plan = _make_mock_plan(phase=PlanPhase.EXECUTE, state=ProcessingState.QUEUED)
plan.decision_root_id = "01ROOTDECISION0000000001"
plan.definition_of_done = "Tests pass"
plan.metadata = {}
lifecycle = MagicMock()
lifecycle.get_plan.return_value = plan
context.errcov_lifecycle_svc = lifecycle
context.errcov_executor = PlanExecutor(
lifecycle_service=lifecycle,
tool_runner=MagicMock(),
)
@given('the errcov execute actor always fails with "{msg}"')
def step_errcov_execute_always_fails(context: Context, msg: str) -> None:
context.errcov_executor._execute_actor = MagicMock()
context.errcov_executor._execute_actor.execute.side_effect = RuntimeError(msg)
@when("I invoke errcov run_execute expecting failure")
def step_errcov_run_execute_fail(context: Context) -> None:
with contextlib.suppress(RuntimeError):
context.errcov_executor.run_execute(plan_id=_PLAN_ID)
@then("the errcov lifecycle should have called fail_execute")
def step_errcov_fail_execute_called(context: Context) -> None:
context.errcov_lifecycle_svc.fail_execute.assert_called_once()
# ---------------------------------------------------------------------------
# error_recovery.py policy edge cases
# ---------------------------------------------------------------------------
@given("an errcov policy with threshold {threshold} and max retries {mr}")
def step_errcov_policy(context: Context, threshold: str, mr: str) -> None:
context.errcov_policy = ErrorRecoveryPolicy(
auto_retry_threshold=float(threshold),
max_retries=int(mr),
)
@given("an errcov retriable error record with retry count {rc} max {mr}")
def step_errcov_retriable_record(context: Context, rc: str, mr: str) -> None:
context.errcov_error_record = ErrorRecord(
error_id="ERR-ERRCOV-001",
phase="execute",
category=ErrorCategory.TRANSIENT,
message="timeout",
retry_count=int(rc),
max_retries=int(mr),
)
@given("an errcov retriable error record with hints retry count {rc} max {mr}")
def step_errcov_retriable_record_hints(context: Context, rc: str, mr: str) -> None:
hints = get_recovery_hints(ErrorCategory.TRANSIENT, _PLAN_ID)
context.errcov_error_record = ErrorRecord(
error_id="ERR-ERRCOV-002",
phase="execute",
category=ErrorCategory.TRANSIENT,
message="timeout with hints",
retry_count=int(rc),
max_retries=int(mr),
recovery_hints=hints,
)
@when("I format errcov recovery output")
def step_errcov_format_recovery(context: Context) -> None:
context.errcov_recovery_output = context.errcov_policy.format_recovery_output(
context.errcov_error_record
)
@then("the errcov policy should not recommend retry")
def step_errcov_no_retry(context: Context) -> None:
assert not context.errcov_policy.should_retry(context.errcov_error_record)
@then("the errcov policy should recommend escalation")
def step_errcov_escalation(context: Context) -> None:
assert context.errcov_policy.should_escalate(context.errcov_error_record)
@then('the errcov recovery output should contain "{text}"')
def step_errcov_recovery_contains(context: Context, text: str) -> None:
assert text in context.errcov_recovery_output, (
f"Expected '{text}' in recovery output, got: {context.errcov_recovery_output[:300]}"
)
# ---------------------------------------------------------------------------
# Plan use command branches (actor_registry, testing_mode)
# ---------------------------------------------------------------------------
@given("an errcov plan creation environment with actor registry")
def step_errcov_plan_use_actor_reg(context: Context) -> None:
context.errcov_actor_registry = MagicMock()
context.errcov_container = MagicMock()
@given("an errcov plan creation environment with testing mode")
def step_errcov_plan_use_testing(context: Context) -> None:
context.errcov_actor_registry = None
context.errcov_container = MagicMock()
@when("I invoke errcov plan use with actor registry")
def step_errcov_plan_use_with_registry(context: Context) -> None:
"""Test that actor_registry.ensure_built_in_actors() is called."""
from contextlib import suppress
actor_reg = context.errcov_actor_registry
with suppress(Exception):
if actor_reg:
actor_reg.ensure_built_in_actors()
context.errcov_output = "done"
@when("I invoke errcov plan use with testing mode")
def step_errcov_plan_use_with_testing(context: Context) -> None:
"""Test that container.actor_service().ensure_default_mock_actor() is called in testing mode."""
from contextlib import suppress
container = context.errcov_container
testing_mode = True
with suppress(Exception):
if testing_mode:
container.actor_service().ensure_default_mock_actor()
context.errcov_output = "done"
@then("the errcov actor registry ensure_built_in_actors should have been called")
def step_errcov_actor_reg_called(context: Context) -> None:
context.errcov_actor_registry.ensure_built_in_actors.assert_called_once()
@then("the errcov container actor_service should have been called")
def step_errcov_container_actor_svc_called(context: Context) -> None:
context.errcov_container.actor_service.assert_called()
# ---------------------------------------------------------------------------
# Shared Then steps
# ---------------------------------------------------------------------------
@then('the errcov errors output should contain "{text}"')
def step_errcov_errors_contains(context: Context, text: str) -> None:
assert text in context.errcov_output, (
f"Expected '{text}' in output, got: {context.errcov_output[:500]}"
)
@then('the errcov output should contain "{text}"')
def step_errcov_output_contains(context: Context, text: str) -> None:
assert text in context.errcov_output, (
f"Expected '{text}' in output, got: {context.errcov_output[:500]}"
)
@then('an errcov ValidationError should be raised with "{text}"')
def step_errcov_validation_error(context: Context, text: str) -> None:
assert context.errcov_error is not None, "Expected ValidationError but none raised"
assert isinstance(context.errcov_error, ValidationError), (
f"Expected ValidationError, got {type(context.errcov_error)}"
)
assert text in str(context.errcov_error), (
f"Expected error to contain '{text}', got: {context.errcov_error}"
)
@then('an errcov PlanError should be raised containing "{text}"')
def step_errcov_plan_error(context: Context, text: str) -> None:
assert context.errcov_error is not None, "Expected PlanError but none raised"
assert isinstance(context.errcov_error, PlanError), (
f"Expected PlanError, got {type(context.errcov_error)}"
)
assert text in str(context.errcov_error), (
f"Expected error to contain '{text}', got: {context.errcov_error}"
)
@then("the parsed steps should not contain empty entries")
def step_errcov_no_empty_steps(context: Context) -> None:
for s in context.errcov_parsed_steps:
assert s.strip() != "", f"Found empty step: '{s}'"
assert len(context.errcov_parsed_steps) == 2, (
f"Expected 2 steps, got {len(context.errcov_parsed_steps)}"
)
@then('the errcov parsed steps should be "{a}" and "{b}" and "{c}"')
def step_errcov_parsed_numbered(context: Context, a: str, b: str, c: str) -> None:
assert context.errcov_parsed_steps == [a, b, c], (
f"Expected [{a}, {b}, {c}], got {context.errcov_parsed_steps}"
)