forked from HAL9000/cleveragents-core
228 lines
7.7 KiB
Python
228 lines
7.7 KiB
Python
"""Step definitions for cleanup CLI uncovered branches.
|
|
|
|
Covers missed lines and branches in
|
|
``cleveragents.cli.commands.cleanup``:
|
|
|
|
* L49-50 - inner ``OSError`` on ``p.stat()`` inside ``_detect_active_plan_ids``
|
|
* L52-58 - outer ``OSError`` from ``_get_sandbox_dirs`` -> warning + empty frozenset
|
|
* L44->42 - empty sandbox dir list (loop end)
|
|
* L87->94 - ``scan()`` with no stale items -> "No stale resources found."
|
|
* L128-131 - ``purge --dry-run`` with stale items -> "Would clean:" listing
|
|
* L139->143 - ``purge`` confirmation prompt accepted -> proceeds
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.application.services.cleanup_service import (
|
|
CleanupReport,
|
|
StaleItem,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _empty_report() -> CleanupReport:
|
|
"""Return a ``CleanupReport`` with no stale items (dry-run)."""
|
|
return CleanupReport(dry_run=True)
|
|
|
|
|
|
def _report_with_stale_items() -> CleanupReport:
|
|
"""Return a dry-run ``CleanupReport`` containing stale items."""
|
|
report = CleanupReport(dry_run=True)
|
|
report.sandboxes.scanned = 1
|
|
report.sandboxes.removed = 1
|
|
report.stale_items = [
|
|
StaleItem(
|
|
resource_type="sandbox",
|
|
path="/tmp/fake-sandbox",
|
|
age_description="3 days old",
|
|
plan_id="plan-001",
|
|
),
|
|
]
|
|
return report
|
|
|
|
|
|
def _purge_report() -> CleanupReport:
|
|
"""Return a non-dry-run ``CleanupReport`` for purge results."""
|
|
report = CleanupReport(dry_run=False)
|
|
report.sandboxes.scanned = 1
|
|
report.sandboxes.removed = 1
|
|
return report
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Givens
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("cleanup cli branch mock service returns empty report")
|
|
def step_mock_service_empty_report(context: Context) -> None:
|
|
mock_svc = MagicMock()
|
|
mock_svc.scan.return_value = _empty_report()
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
@given("cleanup cli branch mock service returns report with stale items")
|
|
def step_mock_service_stale_report(context: Context) -> None:
|
|
mock_svc = MagicMock()
|
|
mock_svc.scan.return_value = _report_with_stale_items()
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
@given("cleanup cli branch mock service returns purge report")
|
|
def step_mock_service_purge_report(context: Context) -> None:
|
|
mock_svc = MagicMock()
|
|
mock_svc.scan.return_value = _empty_report()
|
|
mock_svc.purge.return_value = _purge_report()
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
@given("cleanup cli branch a service whose sandbox dir stat raises OSError")
|
|
def step_service_stat_oserror(context: Context) -> None:
|
|
"""Build a mock service where one sandbox path raises OSError on stat().
|
|
|
|
``extract_plan_id_from_sandbox`` returns a truthy plan ID so the code
|
|
enters the ``try: mtime = p.stat()`` block and hits the inner ``except
|
|
OSError: pass`` (L49-50).
|
|
"""
|
|
bad_path = MagicMock(spec=Path)
|
|
bad_path.stat.side_effect = OSError("permission denied")
|
|
|
|
mock_svc = MagicMock()
|
|
mock_svc._get_sandbox_dirs.return_value = [bad_path]
|
|
mock_svc.extract_plan_id_from_sandbox.return_value = "plan-abc"
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
@given("cleanup cli branch a service whose get sandbox dirs raises OSError")
|
|
def step_service_get_sandbox_dirs_oserror(context: Context) -> None:
|
|
mock_svc = MagicMock()
|
|
mock_svc._get_sandbox_dirs.side_effect = OSError("cannot list /tmp")
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
@given("cleanup cli branch a service with no sandbox dirs")
|
|
def step_service_empty_sandbox_dirs(context: Context) -> None:
|
|
mock_svc = MagicMock()
|
|
mock_svc._get_sandbox_dirs.return_value = []
|
|
context._cleanup_branch_mock_svc = mock_svc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Whens
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cleanup cli branch I invoke scan via main app")
|
|
def step_invoke_scan(context: Context) -> None:
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app
|
|
|
|
runner = CliRunner()
|
|
with patch(
|
|
"cleveragents.cli.commands.cleanup._get_cleanup_service",
|
|
return_value=context._cleanup_branch_mock_svc,
|
|
):
|
|
context._cleanup_branch_result = runner.invoke(app, ["cleanup", "scan"])
|
|
|
|
|
|
@when("cleanup cli branch I invoke purge dry-run via main app")
|
|
def step_invoke_purge_dry_run(context: Context) -> None:
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app
|
|
|
|
runner = CliRunner()
|
|
with patch(
|
|
"cleveragents.cli.commands.cleanup._get_cleanup_service",
|
|
return_value=context._cleanup_branch_mock_svc,
|
|
):
|
|
context._cleanup_branch_result = runner.invoke(
|
|
app, ["cleanup", "purge", "--dry-run"]
|
|
)
|
|
|
|
|
|
@when("cleanup cli branch I invoke purge with confirmation y via main app")
|
|
def step_invoke_purge_confirm_y(context: Context) -> None:
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app
|
|
|
|
runner = CliRunner()
|
|
with patch(
|
|
"cleveragents.cli.commands.cleanup._get_cleanup_service",
|
|
return_value=context._cleanup_branch_mock_svc,
|
|
):
|
|
context._cleanup_branch_result = runner.invoke(
|
|
app, ["cleanup", "purge"], input="y\n"
|
|
)
|
|
|
|
|
|
@when("cleanup cli branch I call detect active plan ids")
|
|
def step_call_detect_active_plan_ids(context: Context) -> None:
|
|
from cleveragents.cli.commands.cleanup import _detect_active_plan_ids
|
|
|
|
# Capture stderr output for the warning assertion
|
|
context._cleanup_branch_warnings: list[str] = []
|
|
|
|
mock_err_console = MagicMock()
|
|
|
|
def capture_print(*args, **kwargs):
|
|
context._cleanup_branch_warnings.append(str(args))
|
|
|
|
mock_err_console.print = capture_print
|
|
|
|
with patch(
|
|
"cleveragents.cli.commands.cleanup.get_err_console",
|
|
return_value=mock_err_console,
|
|
):
|
|
context._cleanup_branch_detect_result = _detect_active_plan_ids(
|
|
context._cleanup_branch_mock_svc,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Thens
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("cleanup cli branch exit code is {code:d}")
|
|
def step_check_exit_code(context: Context, code: int) -> None:
|
|
result = context._cleanup_branch_result
|
|
assert result.exit_code == code, (
|
|
f"Expected exit code {code}, got {result.exit_code}.\n"
|
|
f"Output: {result.output}\n"
|
|
f"Exception: {result.exception!r}"
|
|
)
|
|
|
|
|
|
@then('cleanup cli branch output contains "{text}"')
|
|
def step_output_contains(context: Context, text: str) -> None:
|
|
output = context._cleanup_branch_result.output
|
|
assert text in output, f"Expected '{text}' in output, got:\n{output}"
|
|
|
|
|
|
@then("cleanup cli branch the result is an empty frozenset")
|
|
def step_result_empty_frozenset(context: Context) -> None:
|
|
result = context._cleanup_branch_detect_result
|
|
assert isinstance(result, frozenset), f"Expected frozenset, got {type(result)}"
|
|
assert len(result) == 0, f"Expected empty frozenset, got {result}"
|
|
|
|
|
|
@then("cleanup cli branch a warning was printed about active plans")
|
|
def step_warning_printed(context: Context) -> None:
|
|
warnings = context._cleanup_branch_warnings
|
|
combined = " ".join(warnings)
|
|
assert "active plans" in combined.lower() or "Warning" in combined, (
|
|
f"Expected warning about active plans, got: {combined}"
|
|
)
|