forked from HAL9000/cleveragents-core
02250473ad
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 (commit9c6d6915) 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 (commit0d5d9cf0and 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 (commit1a07a891): - '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 (commit300a5d6d): - 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
544 lines
22 KiB
Python
544 lines
22 KiB
Python
"""Step definitions for plan_cli_coverage_r3.feature.
|
|
|
|
Targets remaining uncovered lines in cleveragents/cli/commands/plan.py:
|
|
- Lines 104, 107-108: validate_namespaced_actor invalid / valid
|
|
- Line 184: _plan_spec_dict with execution_environment
|
|
- Line 790: build command PlanError handler
|
|
- Lines 799-858: apply command (v3 and legacy paths)
|
|
- Lines 1205-1224: continue_plan command body
|
|
- Lines 1529-1542, 1631-1633, 1751-1752: use_action with actor overrides
|
|
- Lines 1789-1792, 1794: use_action with execution_env_priority
|
|
- Lines 2740-2741, 2743: plan_diff command body
|
|
- Lines 3047-3048, 3051: correct_decision error handlers
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.commands.plan import (
|
|
_plan_spec_dict,
|
|
validate_namespaced_actor,
|
|
)
|
|
from cleveragents.cli.commands.plan import (
|
|
app as plan_app,
|
|
)
|
|
from cleveragents.core.exceptions import (
|
|
CleverAgentsError,
|
|
PlanError,
|
|
ValidationError,
|
|
)
|
|
from cleveragents.domain.models.core.plan import (
|
|
ExecutionEnvPriority,
|
|
NamespacedName,
|
|
Plan,
|
|
PlanIdentity,
|
|
PlanPhase,
|
|
PlanTimestamps,
|
|
ProcessingState,
|
|
)
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# Constants
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
_ULID_A = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
|
|
|
|
_PATCH_CONTAINER = "cleveragents.application.container.get_container"
|
|
_PATCH_GET_LIFECYCLE = "cleveragents.cli.commands.plan._get_lifecycle_service"
|
|
_PATCH_GET_APPLY_SVC = "cleveragents.cli.commands.plan._get_apply_service"
|
|
_PATCH_GET_PROJECT = "cleveragents.cli.commands.plan._get_current_project"
|
|
_PATCH_NOTIFY_FACADE = "cleveragents.cli.commands.plan._notify_facade"
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# Helpers
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _make_plan(
|
|
*,
|
|
plan_id: str = _ULID_A,
|
|
name: str = "local/test-plan",
|
|
phase: PlanPhase = PlanPhase.EXECUTE,
|
|
processing_state: ProcessingState = ProcessingState.COMPLETE,
|
|
read_only: bool = False,
|
|
execution_environment: str | None = None,
|
|
execution_env_priority: ExecutionEnvPriority | None = None,
|
|
) -> Plan:
|
|
"""Build a real Plan object for testing."""
|
|
plan = Plan(
|
|
identity=PlanIdentity(plan_id=plan_id),
|
|
namespaced_name=NamespacedName.parse(name),
|
|
action_name="local/test-action",
|
|
description="Coverage test plan",
|
|
phase=phase,
|
|
processing_state=processing_state,
|
|
project_links=[],
|
|
timestamps=PlanTimestamps(
|
|
created_at=datetime(2025, 6, 15, 10, 0, 0),
|
|
updated_at=datetime(2025, 6, 15, 11, 0, 0),
|
|
),
|
|
reusable=True,
|
|
read_only=read_only,
|
|
)
|
|
if execution_environment is not None:
|
|
plan.execution_environment = execution_environment
|
|
if execution_env_priority is not None:
|
|
plan.execution_env_priority = execution_env_priority
|
|
return plan
|
|
|
|
|
|
def _start_patch(context: Context, target: str, **kwargs) -> MagicMock:
|
|
"""Start a mock patch and register cleanup on context."""
|
|
patcher = patch(target, **kwargs)
|
|
mock_obj = patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
return mock_obj
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────
|
|
# Given — CLI runner
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@given("a plcov3 CLI runner")
|
|
def step_plcov3_cli_runner(context: Context) -> None:
|
|
context.plcov3_runner = CliRunner()
|
|
context.plcov3_result = None
|
|
context.plcov3_error = None
|
|
context.plcov3_return_value = None
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# validate_namespaced_actor (lines 104, 107-108)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@when('I plcov3 call validate_namespaced_actor with value "{value}" and flag "{flag}"')
|
|
def step_plcov3_call_validate_actor(context: Context, value: str, flag: str) -> None:
|
|
try:
|
|
context.plcov3_return_value = validate_namespaced_actor(value, flag)
|
|
except Exception as exc:
|
|
context.plcov3_error = exc
|
|
|
|
|
|
@then(
|
|
'the plcov3 error should be a ValidationError with message containing "{fragment}"'
|
|
)
|
|
def step_plcov3_error_is_validation(context: Context, fragment: str) -> None:
|
|
assert context.plcov3_error is not None, "Expected an error but none was raised"
|
|
assert isinstance(context.plcov3_error, ValidationError), (
|
|
f"Expected ValidationError, got {type(context.plcov3_error).__name__}"
|
|
)
|
|
assert fragment in str(context.plcov3_error), (
|
|
f"Expected '{fragment}' in error message, got: {context.plcov3_error}"
|
|
)
|
|
|
|
|
|
@then('the plcov3 result should equal "{expected}"')
|
|
def step_plcov3_result_equals(context: Context, expected: str) -> None:
|
|
assert context.plcov3_error is None, f"Unexpected error: {context.plcov3_error}"
|
|
assert context.plcov3_return_value == expected, (
|
|
f"Expected '{expected}', got '{context.plcov3_return_value}'"
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# _plan_spec_dict with execution_environment (line 184)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given('a plcov3 v3 plan with execution_environment "{env}"')
|
|
def step_plcov3_plan_with_exec_env(context: Context, env: str) -> None:
|
|
context.plcov3_plan = _make_plan(
|
|
execution_environment=env,
|
|
execution_env_priority=ExecutionEnvPriority.FALLBACK,
|
|
)
|
|
|
|
|
|
@when("I plcov3 call _plan_spec_dict")
|
|
def step_plcov3_call_plan_spec_dict(context: Context) -> None:
|
|
context.plcov3_spec_dict = _plan_spec_dict(context.plcov3_plan)
|
|
|
|
|
|
@then('the plcov3 spec dict should contain key "{key}" with value "{value}"')
|
|
def step_plcov3_spec_dict_contains(context: Context, key: str, value: str) -> None:
|
|
assert key in context.plcov3_spec_dict, (
|
|
f"Key '{key}' not in spec dict: {list(context.plcov3_spec_dict.keys())}"
|
|
)
|
|
assert str(context.plcov3_spec_dict[key]) == value, (
|
|
f"Expected '{value}', got '{context.plcov3_spec_dict[key]}'"
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# build command PlanError handler (line 790)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given("a plcov3 mocked build environment that raises PlanError")
|
|
def step_plcov3_mock_build_plan_error(context: Context) -> None:
|
|
container = MagicMock()
|
|
plan_service = MagicMock()
|
|
plan_service.build_plan.side_effect = PlanError("Build failed internally")
|
|
container.plan_service.return_value = plan_service
|
|
container.actor_registry.return_value = MagicMock()
|
|
|
|
project = MagicMock()
|
|
project.name = "test-project"
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(context, _PATCH_GET_PROJECT, return_value=project)
|
|
|
|
|
|
@when("I plcov3 invoke the build command")
|
|
def step_plcov3_invoke_build(context: Context) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(plan_app, ["build"])
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# apply command — v3 lifecycle path (lines 799-824)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given("a plcov3 mocked lifecycle service for apply happy path")
|
|
def step_plcov3_mock_apply_v3(context: Context) -> None:
|
|
service = MagicMock()
|
|
# Walk through the full apply flow
|
|
pre_plan = _make_plan(
|
|
phase=PlanPhase.EXECUTE,
|
|
processing_state=ProcessingState.COMPLETE,
|
|
)
|
|
apply_queued = _make_plan(
|
|
phase=PlanPhase.APPLY,
|
|
processing_state=ProcessingState.QUEUED,
|
|
)
|
|
apply_processing = _make_plan(
|
|
phase=PlanPhase.APPLY,
|
|
processing_state=ProcessingState.PROCESSING,
|
|
)
|
|
apply_applied = _make_plan(
|
|
phase=PlanPhase.APPLY,
|
|
processing_state=ProcessingState.APPLIED,
|
|
)
|
|
service.get_plan.side_effect = [
|
|
pre_plan,
|
|
apply_queued,
|
|
apply_processing,
|
|
apply_applied,
|
|
]
|
|
service.apply_plan.return_value = None
|
|
service.start_apply.return_value = None
|
|
service.complete_apply.return_value = None
|
|
|
|
_start_patch(context, _PATCH_GET_LIFECYCLE, return_value=service)
|
|
_start_patch(context, _PATCH_NOTIFY_FACADE)
|
|
|
|
|
|
@when('I plcov3 invoke apply with plan_id "{plan_id}"')
|
|
def step_plcov3_invoke_apply_with_id(context: Context, plan_id: str) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(
|
|
plan_app, ["apply", plan_id, "--yes"]
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# apply command — legacy path with changes (lines 826-858)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
def _make_mock_change(file_path: str = "src/main.py", operation: str = "modify"):
|
|
"""Create a mock Change object."""
|
|
change = MagicMock()
|
|
change.file_path = file_path
|
|
change.operation = operation
|
|
return change
|
|
|
|
|
|
@given("a plcov3 mocked lifecycle service with no eligible apply plans")
|
|
def step_plcov3_mock_lifecycle_no_eligible_apply(context: Context) -> None:
|
|
"""Mock lifecycle service that returns no plans eligible for apply."""
|
|
service = MagicMock()
|
|
service.list_plans.return_value = []
|
|
_start_patch(context, _PATCH_GET_LIFECYCLE, return_value=service)
|
|
|
|
|
|
@given("a plcov3 mocked legacy apply environment with pending changes")
|
|
def step_plcov3_mock_legacy_apply_with_changes(context: Context) -> None:
|
|
container = MagicMock()
|
|
plan_service = MagicMock()
|
|
changes = [_make_mock_change("src/main.py", "modify")]
|
|
plan_service.get_pending_changes.return_value = changes
|
|
plan_service.apply_changes.return_value = 1
|
|
container.plan_service.return_value = plan_service
|
|
|
|
project = MagicMock()
|
|
project.name = "test-project"
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(context, _PATCH_GET_PROJECT, return_value=project)
|
|
|
|
# Set testing env var so confirmation is skipped
|
|
old_val = os.environ.get("CLEVERAGENTS_TESTING_USE_MOCK_AI", "")
|
|
os.environ["CLEVERAGENTS_TESTING_USE_MOCK_AI"] = "true"
|
|
context.add_cleanup(
|
|
lambda: (
|
|
os.environ.__setitem__("CLEVERAGENTS_TESTING_USE_MOCK_AI", old_val)
|
|
if old_val
|
|
else os.environ.pop("CLEVERAGENTS_TESTING_USE_MOCK_AI", None)
|
|
)
|
|
)
|
|
|
|
|
|
@given("a plcov3 mocked legacy apply environment with no pending changes")
|
|
def step_plcov3_mock_legacy_apply_no_changes(context: Context) -> None:
|
|
container = MagicMock()
|
|
plan_service = MagicMock()
|
|
plan_service.get_pending_changes.return_value = []
|
|
container.plan_service.return_value = plan_service
|
|
|
|
project = MagicMock()
|
|
project.name = "test-project"
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(context, _PATCH_GET_PROJECT, return_value=project)
|
|
|
|
|
|
@when("I plcov3 invoke apply without plan_id")
|
|
def step_plcov3_invoke_apply_no_id(context: Context) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(plan_app, ["apply"])
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# continue_plan command (lines 1205-1224)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given("a plcov3 mocked continue environment")
|
|
def step_plcov3_mock_continue(context: Context) -> None:
|
|
container = MagicMock()
|
|
plan_service = MagicMock()
|
|
plan_service.continue_plan.return_value = None
|
|
container.plan_service.return_value = plan_service
|
|
|
|
project = MagicMock()
|
|
project.name = "test-project"
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(context, _PATCH_GET_PROJECT, return_value=project)
|
|
|
|
|
|
@given("a plcov3 mocked continue environment with current plan")
|
|
def step_plcov3_mock_continue_with_plan(context: Context) -> None:
|
|
container = MagicMock()
|
|
plan_service = MagicMock()
|
|
current_plan = MagicMock()
|
|
current_plan.name = "my-existing-plan"
|
|
plan_service.get_current_plan.return_value = current_plan
|
|
container.plan_service.return_value = plan_service
|
|
|
|
project = MagicMock()
|
|
project.name = "test-project"
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(context, _PATCH_GET_PROJECT, return_value=project)
|
|
|
|
|
|
@when('I plcov3 invoke continue with prompt "{prompt}"')
|
|
def step_plcov3_invoke_continue_with_prompt(context: Context, prompt: str) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(plan_app, ["continue", prompt])
|
|
|
|
|
|
@when("I plcov3 invoke continue without prompt")
|
|
def step_plcov3_invoke_continue_no_prompt(context: Context) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(plan_app, ["continue"])
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# use_action with overrides (lines 1529-1542, 1631-1633, 1751-1752,
|
|
# 1789-1792, 1794)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
def _make_mock_action():
|
|
"""Create a mock Action with a namespaced_name."""
|
|
action = MagicMock()
|
|
action.namespaced_name = NamespacedName.parse("local/test")
|
|
return action
|
|
|
|
|
|
@given("a plcov3 mocked use_action environment")
|
|
def step_plcov3_mock_use_action(context: Context) -> None:
|
|
service = MagicMock()
|
|
action = _make_mock_action()
|
|
service.get_action_by_name.return_value = action
|
|
|
|
plan = _make_plan(
|
|
phase=PlanPhase.STRATEGIZE,
|
|
processing_state=ProcessingState.QUEUED,
|
|
)
|
|
service.use_action.return_value = plan
|
|
service.save_plan.return_value = None
|
|
service._commit_plan.return_value = None
|
|
|
|
_start_patch(context, _PATCH_GET_LIFECYCLE, return_value=service)
|
|
_start_patch(context, _PATCH_NOTIFY_FACADE)
|
|
|
|
|
|
@when('I plcov3 invoke use with action "{action}" and strategy-actor "{actor}"')
|
|
def step_plcov3_invoke_use_with_strategy_actor(
|
|
context: Context, action: str, actor: str
|
|
) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(
|
|
plan_app,
|
|
["use", action, "--strategy-actor", actor],
|
|
)
|
|
|
|
|
|
@when(
|
|
'I plcov3 invoke use with action "{action}" and execution-environment "{env}" and priority "{priority}"'
|
|
)
|
|
def step_plcov3_invoke_use_with_env_priority(
|
|
context: Context, action: str, env: str, priority: str
|
|
) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(
|
|
plan_app,
|
|
[
|
|
"use",
|
|
action,
|
|
"--execution-environment",
|
|
env,
|
|
"--execution-env-priority",
|
|
priority,
|
|
],
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# plan diff command (lines 2740-2741, 2743)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given("a plcov3 mocked apply service for diff")
|
|
def step_plcov3_mock_apply_svc_diff(context: Context) -> None:
|
|
apply_svc = MagicMock()
|
|
apply_svc.diff.return_value = "--- a/file.py\n+++ b/file.py\n@@ unified diff @@"
|
|
_start_patch(context, _PATCH_GET_APPLY_SVC, return_value=apply_svc)
|
|
|
|
|
|
@when('I plcov3 invoke diff with plan_id "{plan_id}"')
|
|
def step_plcov3_invoke_diff(context: Context, plan_id: str) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(plan_app, ["diff", plan_id])
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# correct_decision error handlers (lines 3047-3048, 3051)
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@given("a plcov3 mocked correction environment that raises ValidationError")
|
|
def step_plcov3_mock_correct_validation_error(context: Context) -> None:
|
|
container = MagicMock()
|
|
# Make the correction service raise ValidationError during request_correction
|
|
correction_svc = MagicMock()
|
|
correction_svc.request_correction.side_effect = ValidationError(
|
|
"Bad correction input"
|
|
)
|
|
container.correction_service.return_value = correction_svc
|
|
|
|
# Decision service to get past identifier resolution
|
|
decision_svc = MagicMock()
|
|
decision_svc.list_decisions.return_value = []
|
|
decision_svc.get_influence_edges.return_value = []
|
|
container.decision_service.return_value = decision_svc
|
|
|
|
# Lifecycle service to resolve identifier as non-plan
|
|
from cleveragents.core.exceptions import ResourceNotFoundError
|
|
|
|
lifecycle_svc = MagicMock()
|
|
lifecycle_svc.get_plan.side_effect = ResourceNotFoundError(
|
|
"Not found", resource_type="plan", resource_id="DEC-001"
|
|
)
|
|
container.plan_lifecycle_service.return_value = lifecycle_svc
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
# Mock _resolve_active_plan_id to return a plan ID
|
|
_start_patch(
|
|
context,
|
|
"cleveragents.cli.commands.plan._resolve_active_plan_id",
|
|
return_value="PLAN-ACTIVE",
|
|
)
|
|
|
|
|
|
@given("a plcov3 mocked correction environment that raises CleverAgentsError")
|
|
def step_plcov3_mock_correct_clever_error(context: Context) -> None:
|
|
container = MagicMock()
|
|
correction_svc = MagicMock()
|
|
correction_svc.request_correction.side_effect = CleverAgentsError(
|
|
"Internal correction error"
|
|
)
|
|
container.correction_service.return_value = correction_svc
|
|
|
|
decision_svc = MagicMock()
|
|
decision_svc.list_decisions.return_value = []
|
|
decision_svc.get_influence_edges.return_value = []
|
|
container.decision_service.return_value = decision_svc
|
|
|
|
from cleveragents.core.exceptions import ResourceNotFoundError
|
|
|
|
lifecycle_svc = MagicMock()
|
|
lifecycle_svc.get_plan.side_effect = ResourceNotFoundError(
|
|
"Not found", resource_type="plan", resource_id="DEC-001"
|
|
)
|
|
container.plan_lifecycle_service.return_value = lifecycle_svc
|
|
|
|
_start_patch(context, _PATCH_CONTAINER, return_value=container)
|
|
_start_patch(
|
|
context,
|
|
"cleveragents.cli.commands.plan._resolve_active_plan_id",
|
|
return_value="PLAN-ACTIVE",
|
|
)
|
|
|
|
|
|
@when(
|
|
'I plcov3 invoke correct with identifier "{ident}" mode "{mode}" guidance "{guidance}"'
|
|
)
|
|
def step_plcov3_invoke_correct(
|
|
context: Context, ident: str, mode: str, guidance: str
|
|
) -> None:
|
|
context.plcov3_result = context.plcov3_runner.invoke(
|
|
plan_app,
|
|
["correct", ident, "--mode", mode, "--guidance", guidance, "--yes"],
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Shared Then steps
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
@then('the plcov3 CLI output should contain "{fragment}"')
|
|
def step_plcov3_output_contains(context: Context, fragment: str) -> None:
|
|
result = context.plcov3_result
|
|
assert result is not None, "No CLI result captured"
|
|
combined = (result.output or "") + (getattr(result, "stderr", "") or "")
|
|
assert fragment in combined, f"Expected '{fragment}' in output, got:\n{combined}"
|
|
|
|
|
|
@then("the plcov3 CLI exit code should be {code:d}")
|
|
def step_plcov3_exit_code(context: Context, code: int) -> None:
|
|
result = context.plcov3_result
|
|
assert result is not None, "No CLI result captured"
|
|
assert result.exit_code == code, (
|
|
f"Expected exit code {code}, got {result.exit_code}.\n"
|
|
f"Output: {result.output}\n"
|
|
f"Exception: {result.exception}"
|
|
)
|