Files
temp/features/steps/plan_cli_streaming_coverage_steps.py
brent.edwards 434cad5d1a chore(lint): fix ruff findings in coverage step files
- Normalize import ordering and remove unused imports
- Replace non-ASCII dashes in comments with ASCII hyphens
- Clean up unused assignments and use contextlib.suppress
- Adjust unused variable name in plan_cli_streaming steps
2026-02-13 05:01:27 +00:00

577 lines
21 KiB
Python

"""Step definitions for plan CLI streaming and helper function coverage tests.
These steps cover _print_lifecycle_plan, _get_current_project, and the
programmatic wrapper functions in cleveragents.cli.commands.plan.
"""
from __future__ import annotations
import re
from io import StringIO
from unittest.mock import MagicMock, patch
import typer
from behave import then, when
from rich.console import Console
from cleveragents.core.exceptions import CleverAgentsError
def _make_mock_container(project=None):
"""Create a mock container with plan and project services."""
mock_container = MagicMock()
mock_plan_service = MagicMock()
mock_project_service = MagicMock()
mock_project_service.get_current_project.return_value = project
mock_container.plan_service.return_value = mock_plan_service
mock_container.project_service.return_value = mock_project_service
return mock_container, mock_plan_service, mock_project_service
def _strip_markup(text: str) -> str:
"""Strip Rich markup tags from text."""
return re.sub(r"\[[^\]]*\]", "", text)
def _make_lifecycle_plan(
description: str = "Test plan description",
error_message: str | None = None,
project_links: list | None = None,
):
"""Create a real LifecyclePlan (v3 Plan) instance for testing."""
from cleveragents.domain.models.core.plan import (
NamespacedName,
Plan,
PlanIdentity,
PlanPhase,
PlanTimestamps,
ProcessingState,
ProjectLink,
)
links = (
project_links
if project_links is not None
else [
ProjectLink(project_name="test-project"),
]
)
return Plan(
identity=PlanIdentity(plan_id="01HXYZ01HXYZ01HXYZ01HXYZ01"),
namespaced_name=NamespacedName(namespace="local", name="test-plan"),
action_name="local/test-action",
description=description,
phase=PlanPhase.STRATEGIZE,
state=ProcessingState.QUEUED,
project_links=links,
error_message=error_message,
timestamps=PlanTimestamps(),
)
# ---------------------------------------------------------------------------
# _print_lifecycle_plan scenarios
# ---------------------------------------------------------------------------
@when("I call print_lifecycle_plan with a non-lifecycle plan object")
def step_call_print_lifecycle_plan_non_lifecycle(context):
"""Call _print_lifecycle_plan with a plain object (not a LifecyclePlan)."""
from cleveragents.cli.commands.plan import _print_lifecycle_plan
output = StringIO()
test_console = Console(file=output, force_terminal=False, width=120)
fake_plan = MagicMock()
fake_plan.__str__ = lambda self: "FakePlanRepr"
with patch("cleveragents.cli.commands.plan.console", test_console):
_print_lifecycle_plan(fake_plan, title="Fallback Test")
context.captured_output = output.getvalue()
@then("the fallback display path should be used for non-lifecycle plan")
def step_assert_fallback_display(context):
"""Assert the fallback path was used, printing a simple panel."""
output = _strip_markup(context.captured_output)
assert "Plan:" in output or "FakePlanRepr" in output, (
f"Expected fallback display but got: {output}"
)
@when("I call print_lifecycle_plan with a lifecycle plan that has an error_message")
def step_call_print_lifecycle_plan_with_error(context):
"""Call _print_lifecycle_plan with a plan that has error_message set."""
from cleveragents.cli.commands.plan import _print_lifecycle_plan
output = StringIO()
test_console = Console(file=output, force_terminal=False, width=120)
plan = _make_lifecycle_plan(
description="Plan with error",
error_message="Something went terribly wrong",
)
with patch("cleveragents.cli.commands.plan.console", test_console):
_print_lifecycle_plan(plan, title="Error Plan")
context.captured_output = output.getvalue()
@then("the lifecycle plan error_message should appear in the output")
def step_assert_error_message_displayed(context):
"""Assert the error_message text is rendered."""
output = _strip_markup(context.captured_output)
assert "Something went terribly wrong" in output, (
f"Expected error message in output but got: {output}"
)
@when("I call print_lifecycle_plan with a description longer than 200 characters")
def step_call_print_lifecycle_plan_long_desc(context):
"""Call _print_lifecycle_plan with a >200 char description."""
from cleveragents.cli.commands.plan import _print_lifecycle_plan
output = StringIO()
test_console = Console(file=output, force_terminal=False, width=120)
long_description = "A" * 250
plan = _make_lifecycle_plan(description=long_description)
with patch("cleveragents.cli.commands.plan.console", test_console):
_print_lifecycle_plan(plan, title="Long Desc Plan")
context.captured_output = output.getvalue()
@then("the lifecycle plan description should be truncated with ellipsis")
def step_assert_description_truncated(context):
"""Assert the description is truncated and ends with '...'."""
output = _strip_markup(context.captured_output)
# The full 250-char string should NOT appear; the truncated version (200 chars + ...) should
assert "A" * 250 not in output, "Full description should be truncated"
assert "..." in output, f"Expected ellipsis for truncation but got: {output}"
@when("I call print_lifecycle_plan with a lifecycle plan with no project links")
def step_call_print_lifecycle_plan_no_projects(context):
"""Call _print_lifecycle_plan with empty project_links."""
from cleveragents.cli.commands.plan import _print_lifecycle_plan
output = StringIO()
test_console = Console(file=output, force_terminal=False, width=120)
plan = _make_lifecycle_plan(project_links=[])
with patch("cleveragents.cli.commands.plan.console", test_console):
_print_lifecycle_plan(plan, title="No Projects Plan")
context.captured_output = output.getvalue()
@then("the lifecycle plan output should show projects as none")
def step_assert_projects_none(context):
"""Assert the output shows (none) for projects."""
output = _strip_markup(context.captured_output)
assert "(none)" in output, f"Expected '(none)' for empty projects but got: {output}"
# ---------------------------------------------------------------------------
# _get_current_project scenarios
# ---------------------------------------------------------------------------
@when("I call the get_current_project helper with a valid project")
def step_call_get_current_project_valid(context):
"""Call _get_current_project when a project is available."""
from cleveragents.cli.commands.plan import _get_current_project
mock_project = MagicMock()
mock_project.name = "valid-project"
container, _, _project_service = _make_mock_container(project=mock_project)
with patch(
"cleveragents.application.container.get_container", return_value=container
):
context.returned_project = _get_current_project()
context.expected_project = mock_project
@then("the get_current_project helper should return the mock project")
def step_assert_get_current_project_returns(context):
"""Assert _get_current_project returned the mock project."""
assert context.returned_project is context.expected_project
@when("I call the get_current_project helper with no project available")
def step_call_get_current_project_no_project(context):
"""Call _get_current_project when no project exists."""
from cleveragents.cli.commands.plan import _get_current_project
container, _, _ = _make_mock_container(project=None)
context.call_exception = None
with patch(
"cleveragents.application.container.get_container", return_value=container
):
# Also patch the console to avoid side effects
output = StringIO()
test_console = Console(file=output, force_terminal=False, width=120)
with patch("cleveragents.cli.commands.plan.console", test_console):
try:
_get_current_project()
except Exception as exc:
context.call_exception = exc
@then("the get_current_project helper should raise typer Abort")
def step_assert_get_current_project_aborts(context):
"""Assert _get_current_project raised typer.Abort."""
assert isinstance(context.call_exception, typer.Abort), (
f"Expected typer.Abort but got: {type(context.call_exception)}"
)
# ---------------------------------------------------------------------------
# tell_command programmatic wrapper
# ---------------------------------------------------------------------------
@when(
"I invoke the plan tell_command programmatic wrapper with valid project and prompt"
)
def step_invoke_tell_command_wrapper(context):
"""Call tell_command with a valid project."""
from cleveragents.cli.commands.plan import tell_command
mock_project = MagicMock()
mock_project.name = "wrapper-project"
container, plan_service, _ = _make_mock_container(project=mock_project)
with patch(
"cleveragents.application.container.get_container", return_value=container
):
tell_command(prompt="Build a REST API", name="api-plan")
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan tell_command programmatic wrapper should call create_plan")
def step_assert_tell_command_calls_create(context):
"""Assert tell_command called create_plan with correct args."""
context.plan_service_mock.create_plan.assert_called_once_with(
project=context.mock_project, prompt="Build a REST API", name="api-plan"
)
@when("I invoke the plan tell_command programmatic wrapper with no project")
def step_invoke_tell_command_wrapper_no_project(context):
"""Call tell_command when no project is available."""
from cleveragents.cli.commands.plan import tell_command
container, _, _ = _make_mock_container(project=None)
context.call_exception = None
with patch(
"cleveragents.application.container.get_container", return_value=container
):
try:
tell_command(prompt="Something")
except Exception as exc:
context.call_exception = exc
@then("the plan tell_command programmatic wrapper should raise CleverAgentsError")
def step_assert_tell_command_raises(context):
"""Assert tell_command raised CleverAgentsError."""
assert isinstance(context.call_exception, CleverAgentsError), (
f"Expected CleverAgentsError but got: {type(context.call_exception)}"
)
assert "No project found" in str(context.call_exception)
# ---------------------------------------------------------------------------
# build_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan build_command programmatic wrapper with valid project")
def step_invoke_build_command_wrapper(context):
"""Call build_command with a valid project."""
from cleveragents.cli.commands.plan import build_command
mock_project = MagicMock()
mock_change = MagicMock()
mock_change.file_path = "file.py"
mock_change.operation = "create"
container, plan_service, _ = _make_mock_container(project=mock_project)
plan_service.build_plan.return_value = [mock_change]
with patch(
"cleveragents.application.container.get_container", return_value=container
):
context.build_result = build_command()
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan build_command programmatic wrapper should return the changes list")
def step_assert_build_command_returns_changes(context):
"""Assert build_command returned the changes list."""
assert len(context.build_result) == 1
context.plan_service_mock.build_plan.assert_called_once()
@when("I invoke the plan build_command programmatic wrapper with no project")
def step_invoke_build_command_wrapper_no_project(context):
"""Call build_command when no project is available."""
from cleveragents.cli.commands.plan import build_command
container, _, _ = _make_mock_container(project=None)
context.call_exception = None
with patch(
"cleveragents.application.container.get_container", return_value=container
):
try:
build_command()
except Exception as exc:
context.call_exception = exc
@then("the plan build_command programmatic wrapper should raise CleverAgentsError")
def step_assert_build_command_raises(context):
"""Assert build_command raised CleverAgentsError."""
assert isinstance(context.call_exception, CleverAgentsError)
assert "No project found" in str(context.call_exception)
# ---------------------------------------------------------------------------
# apply_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan apply_command programmatic wrapper with valid project")
def step_invoke_apply_command_wrapper(context):
"""Call apply_command with a valid project."""
from cleveragents.cli.commands.plan import apply_command
mock_project = MagicMock()
container, plan_service, _ = _make_mock_container(project=mock_project)
plan_service.apply_changes.return_value = 5
with patch(
"cleveragents.application.container.get_container", return_value=container
):
context.apply_result = apply_command()
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan apply_command programmatic wrapper should return applied count")
def step_assert_apply_command_returns_count(context):
"""Assert apply_command returned the applied count."""
assert context.apply_result == 5
context.plan_service_mock.apply_changes.assert_called_once_with(
project=context.mock_project
)
# ---------------------------------------------------------------------------
# new_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan new_command programmatic wrapper with valid project")
def step_invoke_new_command_wrapper(context):
"""Call new_command with a valid project."""
from cleveragents.cli.commands.plan import new_command
mock_project = MagicMock()
container, plan_service, _ = _make_mock_container(project=mock_project)
with patch(
"cleveragents.application.container.get_container", return_value=container
):
new_command(name="my-new-plan")
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan new_command programmatic wrapper should call new_plan")
def step_assert_new_command_calls_new_plan(context):
"""Assert new_command called new_plan with correct args."""
context.plan_service_mock.new_plan.assert_called_once_with(
project=context.mock_project, name="my-new-plan"
)
# ---------------------------------------------------------------------------
# current_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan current_command programmatic wrapper with valid project")
def step_invoke_current_command_wrapper(context):
"""Call current_command with a valid project."""
from cleveragents.cli.commands.plan import current_command
mock_project = MagicMock()
mock_plan = MagicMock()
mock_plan.name = "current-plan"
container, plan_service, _ = _make_mock_container(project=mock_project)
plan_service.get_current_plan.return_value = mock_plan
with patch(
"cleveragents.application.container.get_container", return_value=container
):
context.current_result = current_command()
context.plan_service_mock = plan_service
context.mock_project = mock_project
context.expected_plan = mock_plan
@then("the plan current_command programmatic wrapper should return the plan")
def step_assert_current_command_returns_plan(context):
"""Assert current_command returned the current plan."""
assert context.current_result is context.expected_plan
context.plan_service_mock.get_current_plan.assert_called_once_with(
project=context.mock_project
)
# ---------------------------------------------------------------------------
# list_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan list_command programmatic wrapper with valid project")
def step_invoke_list_command_wrapper(context):
"""Call list_command with a valid project."""
from cleveragents.cli.commands.plan import list_command
mock_project = MagicMock()
mock_plan_a = MagicMock()
mock_plan_a.name = "plan-a"
mock_plan_b = MagicMock()
mock_plan_b.name = "plan-b"
container, plan_service, _ = _make_mock_container(project=mock_project)
plan_service.list_plans.return_value = [mock_plan_a, mock_plan_b]
with patch(
"cleveragents.application.container.get_container", return_value=container
):
context.list_result = list_command()
context.plan_service_mock = plan_service
@then("the plan list_command programmatic wrapper should return the plans list")
def step_assert_list_command_returns_plans(context):
"""Assert list_command returned the plans list."""
assert len(context.list_result) == 2
context.plan_service_mock.list_plans.assert_called_once()
# ---------------------------------------------------------------------------
# cd_command programmatic wrapper
# ---------------------------------------------------------------------------
@when("I invoke the plan cd_command programmatic wrapper with valid project")
def step_invoke_cd_command_wrapper(context):
"""Call cd_command with a valid project."""
from cleveragents.cli.commands.plan import cd_command
mock_project = MagicMock()
container, plan_service, _ = _make_mock_container(project=mock_project)
with patch(
"cleveragents.application.container.get_container", return_value=container
):
cd_command(name="target-plan")
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan cd_command programmatic wrapper should call switch_to_plan")
def step_assert_cd_command_calls_switch(context):
"""Assert cd_command called switch_to_plan with correct args."""
context.plan_service_mock.switch_to_plan.assert_called_once_with(
project=context.mock_project, name="target-plan"
)
# ---------------------------------------------------------------------------
# continue_command programmatic wrapper
# ---------------------------------------------------------------------------
@when(
"I invoke the plan continue_command programmatic wrapper with prompt and valid project"
)
def step_invoke_continue_command_wrapper_with_prompt(context):
"""Call continue_command with prompt and valid project."""
from cleveragents.cli.commands.plan import continue_command
mock_project = MagicMock()
container, plan_service, _ = _make_mock_container(project=mock_project)
with patch(
"cleveragents.application.container.get_container", return_value=container
):
continue_command(prompt="Add authentication")
context.plan_service_mock = plan_service
context.mock_project = mock_project
@then("the plan continue_command programmatic wrapper should call continue_plan")
def step_assert_continue_command_calls_continue(context):
"""Assert continue_command called continue_plan with the prompt."""
context.plan_service_mock.continue_plan.assert_called_once_with(
project=context.mock_project, prompt="Add authentication"
)
@when(
"I invoke the plan continue_command programmatic wrapper without prompt and no current plan"
)
def step_invoke_continue_command_wrapper_no_prompt_no_plan(context):
"""Call continue_command without prompt when no plan exists."""
from cleveragents.cli.commands.plan import continue_command
mock_project = MagicMock()
container, plan_service, _ = _make_mock_container(project=mock_project)
plan_service.get_current_plan.return_value = None
context.call_exception = None
with patch(
"cleveragents.application.container.get_container", return_value=container
):
try:
continue_command(prompt=None)
except Exception as exc:
context.call_exception = exc
@then(
"the plan continue_command programmatic wrapper should raise CleverAgentsError for no plan"
)
def step_assert_continue_command_raises_no_plan(context):
"""Assert continue_command raised CleverAgentsError for missing plan."""
assert isinstance(context.call_exception, CleverAgentsError), (
f"Expected CleverAgentsError but got: {type(context.call_exception)}"
)
assert "No current plan to continue" in str(context.call_exception)