forked from HAL9000/cleveragents-core
48cff5cfe0
Renames `plan lifecycle-list` to `plan list` and `plan lifecycle-apply` to `plan apply` to align with the specification's canonical command names. Removes legacy V2 plan commands that occupied those names. - Renamed CLI command registrations from lifecycle-list/lifecycle-apply to list/apply - Removed legacy V2 apply and list commands (~200 lines) - Updated apply shortcut in main.py to delegate to v3 lifecycle - Added defensive null check for plan existence in apply command - Updated 63+ test, doc, and benchmark files for consistency Closes #881 Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
609 lines
22 KiB
Python
609 lines
22 KiB
Python
"""Step definitions for plan_cli_uncovered_region_coverage.feature.
|
|
|
|
Covers uncovered lines/branches in cleveragents/cli/commands/plan.py:
|
|
- Lines 1950-1954: list_plans empty result after filtering
|
|
- Lines 1956-1957: list_plans non-rich format output
|
|
- Line 2132: revert_plan CleverAgentsError handler
|
|
- Lines 2136-2143: _get_apply_service function body
|
|
- Lines 2149-2184: plan_diff command (correction branch + error paths)
|
|
- Lines 2202-2233: plan_artifacts command (success + error paths)
|
|
- Lines 2231-2273: correct_decision command (execution, dry-run, error handlers)
|
|
|
|
All step text uses the ``uncov-rgn`` prefix to avoid collisions with
|
|
other step files in the project.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
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 app as plan_app
|
|
from cleveragents.core.exceptions import (
|
|
CleverAgentsError,
|
|
PlanError,
|
|
ResourceNotFoundError,
|
|
ValidationError,
|
|
)
|
|
from cleveragents.domain.models.core.plan import (
|
|
NamespacedName,
|
|
Plan,
|
|
PlanIdentity,
|
|
PlanPhase,
|
|
PlanTimestamps,
|
|
ProcessingState,
|
|
ProjectLink,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Constants
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_ULID_A = "01ARZ3NDEKTSV4RRFFQ69G5FAA"
|
|
|
|
_PATCH_GET_LIFECYCLE = "cleveragents.cli.commands.plan._get_lifecycle_service"
|
|
_PATCH_GET_APPLY = "cleveragents.cli.commands.plan._get_apply_service"
|
|
_PATCH_RESOLVE_ACTIVE = "cleveragents.cli.commands.plan._resolve_active_plan_id"
|
|
_PATCH_CONTAINER = "cleveragents.application.container.get_container"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_plan(
|
|
*,
|
|
plan_id: str = _ULID_A,
|
|
name: str = "local/uncov-test-plan",
|
|
phase: PlanPhase = PlanPhase.STRATEGIZE,
|
|
processing_state: ProcessingState = ProcessingState.QUEUED,
|
|
action_name: str = "local/test-action",
|
|
project_links: list[ProjectLink] | None = None,
|
|
) -> Plan:
|
|
"""Build a real Plan domain object for testing."""
|
|
return Plan(
|
|
identity=PlanIdentity(plan_id=plan_id),
|
|
namespaced_name=NamespacedName.parse(name),
|
|
action_name=action_name,
|
|
description="Coverage test plan",
|
|
definition_of_done=None,
|
|
strategy_actor=None,
|
|
execution_actor=None,
|
|
phase=phase,
|
|
processing_state=processing_state,
|
|
project_links=project_links or [],
|
|
timestamps=PlanTimestamps(
|
|
created_at=datetime(2025, 6, 15, 10, 0, 0),
|
|
updated_at=datetime(2025, 6, 15, 11, 0, 0),
|
|
),
|
|
reusable=True,
|
|
read_only=False,
|
|
created_by=None,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — CLI runner and shared mocked lifecycle service
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("an uncov-rgn CLI runner and mocked lifecycle service")
|
|
def step_uncov_rgn_cli_runner(context: Context) -> None:
|
|
"""Initialise a CliRunner, mock lifecycle service, and cleanup list."""
|
|
context.uncov_runner = CliRunner()
|
|
context.uncov_mock_svc = MagicMock()
|
|
if not hasattr(context, "_cleanup_handlers"):
|
|
context._cleanup_handlers = []
|
|
|
|
p = patch(_PATCH_GET_LIFECYCLE, return_value=context.uncov_mock_svc)
|
|
p.start()
|
|
context._cleanup_handlers.append(p.stop)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — list_plans scenarios
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given(
|
|
"the uncov-rgn lifecycle service returns plans that do not match an action filter"
|
|
)
|
|
def step_uncov_rgn_plans_no_match(context: Context) -> None:
|
|
"""Return plans whose action_name differs from the filter value."""
|
|
plans = [
|
|
_make_plan(action_name="local/other-action"),
|
|
]
|
|
context.uncov_mock_svc.list_plans.return_value = plans
|
|
|
|
|
|
@given("the uncov-rgn lifecycle service returns a single plan for listing")
|
|
def step_uncov_rgn_single_plan_for_list(context: Context) -> None:
|
|
"""Return one plan so the list command renders in non-rich format."""
|
|
plans = [_make_plan()]
|
|
context.uncov_mock_svc.list_plans.return_value = plans
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — revert_plan CleverAgentsError
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("the uncov-rgn lifecycle service revert raises CleverAgentsError")
|
|
def step_uncov_rgn_revert_ca_error(context: Context) -> None:
|
|
"""Configure revert_plan to raise CleverAgentsError."""
|
|
context.uncov_mock_svc.revert_plan.side_effect = CleverAgentsError(
|
|
"revert service unavailable"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — _get_apply_service
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("an uncov-rgn mocked lifecycle service for apply service creation")
|
|
def step_uncov_rgn_mock_lifecycle_for_apply(context: Context) -> None:
|
|
"""Prepare patches for _get_apply_service test."""
|
|
context.uncov_mock_lifecycle = MagicMock()
|
|
if not hasattr(context, "_cleanup_handlers"):
|
|
context._cleanup_handlers = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — plan_diff scenarios
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('the uncov-rgn apply service diff returns formatted output "{output}"')
|
|
def step_uncov_rgn_apply_diff_ok(context: Context, output: str) -> None:
|
|
"""Set up a mock apply service whose diff() returns the given output."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.diff.return_value = output
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
@given('the uncov-rgn apply service diff raises PlanError "{msg}"')
|
|
def step_uncov_rgn_apply_diff_plan_error(context: Context, msg: str) -> None:
|
|
"""Set up a mock apply service whose diff() raises PlanError."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.diff.side_effect = PlanError(msg)
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
@given('the uncov-rgn apply service diff raises CleverAgentsError "{msg}"')
|
|
def step_uncov_rgn_apply_diff_ca_error(context: Context, msg: str) -> None:
|
|
"""Set up a mock apply service whose diff() raises CleverAgentsError."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.diff.side_effect = CleverAgentsError(msg)
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — plan_artifacts scenarios
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('the uncov-rgn apply service artifacts returns "{output}"')
|
|
def step_uncov_rgn_apply_artifacts_ok_dq(context: Context, output: str) -> None:
|
|
"""Set up a mock apply service whose artifacts() returns the given output."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.artifacts.return_value = output
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
@given("the uncov-rgn apply service artifacts returns '{output}'")
|
|
def step_uncov_rgn_apply_artifacts_ok_sq(context: Context, output: str) -> None:
|
|
"""Set up a mock apply service with single-quoted output."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.artifacts.return_value = output
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
@given('the uncov-rgn apply service artifacts raises PlanError "{msg}"')
|
|
def step_uncov_rgn_apply_artifacts_plan_error(context: Context, msg: str) -> None:
|
|
"""Set up a mock apply service whose artifacts() raises PlanError."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.artifacts.side_effect = PlanError(msg)
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
@given('the uncov-rgn apply service artifacts raises CleverAgentsError "{msg}"')
|
|
def step_uncov_rgn_apply_artifacts_ca_error(context: Context, msg: str) -> None:
|
|
"""Set up a mock apply service whose artifacts() raises CleverAgentsError."""
|
|
mock_apply = MagicMock()
|
|
mock_apply.artifacts.side_effect = CleverAgentsError(msg)
|
|
context.uncov_mock_apply = mock_apply
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given — correct_decision scenarios
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_mock_correction_svc(
|
|
*,
|
|
request_return: object | None = None,
|
|
request_side_effect: Exception | None = None,
|
|
impact_return: object | None = None,
|
|
execute_return: object | None = None,
|
|
) -> MagicMock:
|
|
"""Build a mock CorrectionService."""
|
|
svc = MagicMock()
|
|
|
|
if request_side_effect:
|
|
svc.request_correction.side_effect = request_side_effect
|
|
elif request_return:
|
|
svc.request_correction.return_value = request_return
|
|
else:
|
|
# Default request
|
|
svc.request_correction.return_value = SimpleNamespace(
|
|
correction_id="CORR-001",
|
|
mode=SimpleNamespace(value="revert"),
|
|
target_decision_id="DEC-001",
|
|
guidance="test guidance",
|
|
)
|
|
|
|
if impact_return:
|
|
svc.analyze_impact.return_value = impact_return
|
|
|
|
if execute_return:
|
|
svc.execute_correction.return_value = execute_return
|
|
|
|
return svc
|
|
|
|
|
|
@given(
|
|
"the uncov-rgn correction service returns a revert result with reverted decisions"
|
|
)
|
|
def step_uncov_rgn_correction_revert(context: Context) -> None:
|
|
"""Mock CorrectionService for a successful revert execution."""
|
|
request = SimpleNamespace(
|
|
correction_id="CORR-REVERT-01",
|
|
mode=SimpleNamespace(value="revert"),
|
|
target_decision_id="DEC-001",
|
|
guidance="Use Flask instead",
|
|
)
|
|
result = SimpleNamespace(
|
|
correction_id="CORR-REVERT-01",
|
|
status=SimpleNamespace(value="applied"),
|
|
new_decisions=[],
|
|
reverted_decisions=["DEC-001", "DEC-002"],
|
|
)
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_return=request,
|
|
execute_return=result,
|
|
)
|
|
|
|
|
|
@given("the uncov-rgn correction service returns an append result with new decisions")
|
|
def step_uncov_rgn_correction_append(context: Context) -> None:
|
|
"""Mock CorrectionService for a successful append execution."""
|
|
request = SimpleNamespace(
|
|
correction_id="CORR-APPEND-01",
|
|
mode=SimpleNamespace(value="append"),
|
|
target_decision_id="DEC-003",
|
|
guidance="Add caching layer",
|
|
)
|
|
result = SimpleNamespace(
|
|
correction_id="CORR-APPEND-01",
|
|
status=SimpleNamespace(value="applied"),
|
|
new_decisions=["DEC-004", "DEC-005"],
|
|
reverted_decisions=[],
|
|
)
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_return=request,
|
|
execute_return=result,
|
|
)
|
|
|
|
|
|
@given("the uncov-rgn correction service returns an impact analysis")
|
|
def step_uncov_rgn_correction_impact(context: Context) -> None:
|
|
"""Mock CorrectionService for dry-run impact analysis."""
|
|
request = SimpleNamespace(
|
|
correction_id="CORR-DRY-01",
|
|
mode=SimpleNamespace(value="revert"),
|
|
target_decision_id="DEC-010",
|
|
guidance="Preview changes",
|
|
)
|
|
impact = SimpleNamespace(
|
|
affected_decisions=["DEC-010", "DEC-011"],
|
|
affected_files=["main.py", "utils.py"],
|
|
estimated_cost=3.0,
|
|
risk_level="medium",
|
|
)
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_return=request,
|
|
impact_return=impact,
|
|
)
|
|
|
|
|
|
@given("the uncov-rgn correction service request raises ResourceNotFoundError")
|
|
def step_uncov_rgn_correction_rnf(context: Context) -> None:
|
|
"""Mock CorrectionService to raise ResourceNotFoundError."""
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_side_effect=ResourceNotFoundError("decision DEC-999 not found"),
|
|
)
|
|
|
|
|
|
@given("the uncov-rgn correction service request raises ValidationError")
|
|
def step_uncov_rgn_correction_val_error(context: Context) -> None:
|
|
"""Mock CorrectionService to raise ValidationError."""
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_side_effect=ValidationError("plan_id must not be empty"),
|
|
)
|
|
|
|
|
|
@given("the uncov-rgn correction service request raises CleverAgentsError")
|
|
def step_uncov_rgn_correction_ca_error(context: Context) -> None:
|
|
"""Mock CorrectionService to raise CleverAgentsError."""
|
|
context.uncov_mock_correction = _make_mock_correction_svc(
|
|
request_side_effect=CleverAgentsError("correction service unavailable"),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — list_plans
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I uncov-rgn invoke list with action filter "{action}"')
|
|
def step_uncov_rgn_invoke_list_action(context: Context, action: str) -> None:
|
|
"""Invoke list with an --action filter."""
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["list", "--action", action],
|
|
)
|
|
|
|
|
|
@when('I uncov-rgn invoke list with format "{fmt}"')
|
|
def step_uncov_rgn_invoke_list_fmt(context: Context, fmt: str) -> None:
|
|
"""Invoke list with a given --format."""
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["list", "--format", fmt],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — revert_plan
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I uncov-rgn invoke revert for plan "{pid}"')
|
|
def step_uncov_rgn_invoke_revert(context: Context, pid: str) -> None:
|
|
"""Invoke the revert command for a given plan ID."""
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["revert", pid],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — _get_apply_service
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I uncov-rgn call _get_apply_service directly")
|
|
def step_uncov_rgn_call_get_apply(context: Context) -> None:
|
|
"""Invoke _get_apply_service and capture the result."""
|
|
from cleveragents.cli.commands.plan import _get_apply_service
|
|
|
|
mock_pas_cls = MagicMock()
|
|
mock_pas_instance = MagicMock()
|
|
mock_pas_cls.return_value = mock_pas_instance
|
|
|
|
with (
|
|
patch(
|
|
_PATCH_GET_LIFECYCLE,
|
|
return_value=context.uncov_mock_lifecycle,
|
|
),
|
|
patch(
|
|
"cleveragents.cli.commands.plan.PlanApplyService",
|
|
mock_pas_cls,
|
|
create=True,
|
|
),
|
|
patch(
|
|
"cleveragents.application.services.plan_apply_service.PlanApplyService",
|
|
mock_pas_cls,
|
|
),
|
|
):
|
|
context.uncov_apply_result = _get_apply_service()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — plan_diff
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I uncov-rgn invoke diff for plan "{pid}" with correction "{corr}"')
|
|
def step_uncov_rgn_invoke_diff_correction(
|
|
context: Context, pid: str, corr: str
|
|
) -> None:
|
|
"""Invoke the diff command with --correction flag."""
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["diff", pid, "--correction", corr],
|
|
)
|
|
|
|
|
|
@when('I uncov-rgn invoke diff for plan "{pid}" without correction')
|
|
def step_uncov_rgn_invoke_diff_no_correction(context: Context, pid: str) -> None:
|
|
"""Invoke the diff command without --correction flag."""
|
|
with patch(_PATCH_GET_APPLY, return_value=context.uncov_mock_apply):
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["diff", pid],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — plan_artifacts
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I uncov-rgn invoke artifacts for plan "{pid}" in rich format')
|
|
def step_uncov_rgn_invoke_artifacts_rich(context: Context, pid: str) -> None:
|
|
"""Invoke the artifacts command in default rich format."""
|
|
with patch(_PATCH_GET_APPLY, return_value=context.uncov_mock_apply):
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["artifacts", pid],
|
|
)
|
|
|
|
|
|
@when('I uncov-rgn invoke artifacts for plan "{pid}" with format "{fmt}"')
|
|
def step_uncov_rgn_invoke_artifacts_fmt(context: Context, pid: str, fmt: str) -> None:
|
|
"""Invoke the artifacts command with a given --format."""
|
|
with patch(_PATCH_GET_APPLY, return_value=context.uncov_mock_apply):
|
|
context.uncov_result = context.uncov_runner.invoke(
|
|
plan_app,
|
|
["artifacts", pid, "--format", fmt],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When — correct_decision
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _invoke_correct(
|
|
context: Context,
|
|
*,
|
|
mode: str = "revert",
|
|
guidance: str = "test guidance",
|
|
yes: bool = True,
|
|
dry_run: bool = False,
|
|
fmt: str = "rich",
|
|
plan_id: str = "PLAN-ACTIVE-01",
|
|
decision_id: str = "DEC-001",
|
|
) -> None:
|
|
"""Helper to invoke the correct command with patches."""
|
|
args = ["correct", decision_id, "--mode", mode, "--guidance", guidance]
|
|
if yes:
|
|
args.append("--yes")
|
|
if dry_run:
|
|
args.append("--dry-run")
|
|
if fmt != "rich":
|
|
args.extend(["--format", fmt])
|
|
args.extend(["--plan", plan_id])
|
|
|
|
mock_correction = getattr(context, "uncov_mock_correction", MagicMock())
|
|
|
|
# Mock DecisionService resolved via DI container (issue #606 fix)
|
|
mock_decision_svc = MagicMock()
|
|
mock_decision_svc.list_decisions.return_value = []
|
|
mock_decision_svc.get_influence_edges.return_value = {}
|
|
mock_container = MagicMock()
|
|
mock_container.decision_service.return_value = mock_decision_svc
|
|
mock_container.correction_service.return_value = mock_correction
|
|
|
|
with patch(_PATCH_CONTAINER, return_value=mock_container):
|
|
context.uncov_result = context.uncov_runner.invoke(plan_app, args)
|
|
|
|
|
|
@when('I uncov-rgn invoke correct in revert mode with --yes and guidance "{guidance}"')
|
|
def step_uncov_rgn_correct_revert_yes(context: Context, guidance: str) -> None:
|
|
"""Invoke correct with revert mode, --yes, and given guidance."""
|
|
_invoke_correct(context, mode="revert", guidance=guidance, yes=True)
|
|
|
|
|
|
@when('I uncov-rgn invoke correct in append mode with --yes and guidance "{guidance}"')
|
|
def step_uncov_rgn_correct_append_yes(context: Context, guidance: str) -> None:
|
|
"""Invoke correct with append mode, --yes, and given guidance."""
|
|
_invoke_correct(context, mode="append", guidance=guidance, yes=True)
|
|
|
|
|
|
@when(
|
|
'I uncov-rgn invoke correct in revert mode with --yes, format "{fmt}", '
|
|
'and guidance "{guidance}"'
|
|
)
|
|
def step_uncov_rgn_correct_revert_fmt(
|
|
context: Context, fmt: str, guidance: str
|
|
) -> None:
|
|
"""Invoke correct with revert mode, --yes, custom format, and given guidance."""
|
|
_invoke_correct(context, mode="revert", guidance=guidance, yes=True, fmt=fmt)
|
|
|
|
|
|
@when(
|
|
"I uncov-rgn invoke correct in dry-run mode with rich format "
|
|
'and guidance "{guidance}"'
|
|
)
|
|
def step_uncov_rgn_correct_dryrun_rich(context: Context, guidance: str) -> None:
|
|
"""Invoke correct with dry-run flag in rich format."""
|
|
_invoke_correct(
|
|
context, mode="revert", guidance=guidance, yes=True, dry_run=True, fmt="rich"
|
|
)
|
|
|
|
|
|
@when(
|
|
'I uncov-rgn invoke correct in dry-run mode with format "{fmt}" '
|
|
'and guidance "{guidance}"'
|
|
)
|
|
def step_uncov_rgn_correct_dryrun_fmt(
|
|
context: Context, fmt: str, guidance: str
|
|
) -> None:
|
|
"""Invoke correct with dry-run flag in custom format."""
|
|
_invoke_correct(
|
|
context, mode="revert", guidance=guidance, yes=True, dry_run=True, fmt=fmt
|
|
)
|
|
|
|
|
|
@when('I uncov-rgn invoke correct with invalid mode "{mode}" and guidance "{guidance}"')
|
|
def step_uncov_rgn_correct_invalid_mode(
|
|
context: Context, mode: str, guidance: str
|
|
) -> None:
|
|
"""Invoke correct with an invalid mode."""
|
|
_invoke_correct(context, mode=mode, guidance=guidance, yes=True)
|
|
|
|
|
|
@when('I uncov-rgn invoke correct with valid mode "{mode}" and empty guidance')
|
|
def step_uncov_rgn_correct_empty_guidance(context: Context, mode: str) -> None:
|
|
"""Invoke correct with empty guidance string."""
|
|
_invoke_correct(context, mode=mode, guidance="", yes=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then — assertions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the uncov-rgn command should exit normally")
|
|
def step_uncov_rgn_exit_ok(context: Context) -> None:
|
|
"""Assert the CLI exited with code 0."""
|
|
assert context.uncov_result.exit_code == 0, (
|
|
f"Expected exit_code=0, got {context.uncov_result.exit_code}. "
|
|
f"Output: {context.uncov_result.output}"
|
|
)
|
|
|
|
|
|
@then("the uncov-rgn command should abort")
|
|
def step_uncov_rgn_abort(context: Context) -> None:
|
|
"""Assert the CLI exited with a non-zero code (abort)."""
|
|
assert context.uncov_result.exit_code != 0, (
|
|
f"Expected non-zero exit_code, got {context.uncov_result.exit_code}. "
|
|
f"Output: {context.uncov_result.output}"
|
|
)
|
|
|
|
|
|
@then('the uncov-rgn output should contain "{text}"')
|
|
def step_uncov_rgn_output_contains(context: Context, text: str) -> None:
|
|
"""Assert the CLI output contains the expected text."""
|
|
output = context.uncov_result.output
|
|
assert text in output, (
|
|
f"Expected output to contain '{text}'. Actual output:\n{output}"
|
|
)
|
|
|
|
|
|
@then("the uncov-rgn apply service should be returned successfully")
|
|
def step_uncov_rgn_apply_ok(context: Context) -> None:
|
|
"""Assert _get_apply_service returned a non-None object."""
|
|
assert context.uncov_apply_result is not None, (
|
|
"Expected _get_apply_service to return a value, got None"
|
|
)
|