forked from HAL9000/cleveragents-core
64cfdc782a
Add a call to bootstrap_builtin_types() in init_command() (project.py) immediately after initialize_project() returns. This seeds the built-in resource types (fs-directory, git-checkout, etc.) into the database so that "resource add" commands succeed without "Resource type not found" errors. The call is idempotent — invoking it multiple times will not create duplicate types. Also fix the TDD robot test (resource_type_bootstrap_git.robot) to initialize a project before running "resource add", and fix a pre-existing parallel test failure in plan_commands_new_coverage where unittest.mock.patch could not reliably intercept PlanApplyService under behave-parallel fork() workers. ISSUES CLOSED: #523, #524
251 lines
9.0 KiB
Python
251 lines
9.0 KiB
Python
"""Step definitions for plan_commands_new_coverage.feature.
|
|
|
|
Covers the remaining uncovered lines in plan.py:
|
|
- Lines 1817, 1821, 1822: _get_apply_service() function body
|
|
- Lines 1845-1855: plan_diff command body (correction branch + normal path)
|
|
- Lines 1878-1888: plan_artifacts command body
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, use_step_matcher, 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
|
|
|
|
runner = CliRunner()
|
|
|
|
_PATCH_GET_APPLY = "cleveragents.cli.commands.plan._get_apply_service"
|
|
_PATCH_LIFECYCLE = "cleveragents.cli.commands.plan._get_lifecycle_service"
|
|
_PATCH_PAS_CLASS = (
|
|
"cleveragents.application.services.plan_apply_service.PlanApplyService"
|
|
)
|
|
|
|
|
|
def _build_mock_service(
|
|
diff_return=None,
|
|
diff_side_effect=None,
|
|
artifacts_return=None,
|
|
artifacts_side_effect=None,
|
|
):
|
|
"""Build a mock PlanApplyService with configurable diff/artifacts behaviour."""
|
|
svc = MagicMock()
|
|
if diff_side_effect:
|
|
svc.diff.side_effect = diff_side_effect
|
|
else:
|
|
svc.diff.return_value = diff_return or ""
|
|
if artifacts_side_effect:
|
|
svc.artifacts.side_effect = artifacts_side_effect
|
|
else:
|
|
svc.artifacts.return_value = artifacts_return or ""
|
|
return svc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GIVEN steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
use_step_matcher("parse")
|
|
|
|
|
|
@given("new_cov no service mocks are needed")
|
|
def step_new_cov_no_mocks(context: Context) -> None:
|
|
"""The correction branch returns early before calling any service."""
|
|
context.new_cov_mock_service = None
|
|
|
|
|
|
@given('new_cov the apply service diff returns "{output}"')
|
|
def step_new_cov_diff_returns_dq(context: Context, output: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(diff_return=output)
|
|
|
|
|
|
@given("new_cov the apply service diff returns '{output}'")
|
|
def step_new_cov_diff_returns_sq(context: Context, output: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(diff_return=output)
|
|
|
|
|
|
@given('new_cov the apply service diff raises PlanError "{msg}"')
|
|
def step_new_cov_diff_plan_error(context: Context, msg: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(
|
|
diff_side_effect=PlanError(msg),
|
|
)
|
|
|
|
|
|
@given('new_cov the apply service diff raises CleverAgentsError "{msg}"')
|
|
def step_new_cov_diff_ca_error(context: Context, msg: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(
|
|
diff_side_effect=CleverAgentsError(msg),
|
|
)
|
|
|
|
|
|
@given('new_cov the apply service artifacts returns "{output}"')
|
|
def step_new_cov_artifacts_returns_dq(context: Context, output: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(artifacts_return=output)
|
|
|
|
|
|
@given("new_cov the apply service artifacts returns '{output}'")
|
|
def step_new_cov_artifacts_returns_sq(context: Context, output: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(artifacts_return=output)
|
|
|
|
|
|
@given('new_cov the apply service artifacts raises PlanError "{msg}"')
|
|
def step_new_cov_artifacts_plan_error(context: Context, msg: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(
|
|
artifacts_side_effect=PlanError(msg),
|
|
)
|
|
|
|
|
|
@given('new_cov the apply service artifacts raises CleverAgentsError "{msg}"')
|
|
def step_new_cov_artifacts_ca_error(context: Context, msg: str) -> None:
|
|
context.new_cov_mock_service = _build_mock_service(
|
|
artifacts_side_effect=CleverAgentsError(msg),
|
|
)
|
|
|
|
|
|
@given("new_cov a mocked lifecycle service for apply service creation")
|
|
def step_new_cov_mock_lifecycle(context: Context) -> None:
|
|
context.new_cov_mock_lifecycle = MagicMock()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# WHEN steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
use_step_matcher("re")
|
|
|
|
|
|
@when(
|
|
r'new_cov I run plan diff for "(?P<plan_id>[^"]+)" with correction "(?P<corr>[^"]+)"'
|
|
)
|
|
def step_new_cov_diff_with_correction(
|
|
context: Context, plan_id: str, corr: str
|
|
) -> None:
|
|
"""Invoke ``plan diff <plan_id> --correction <corr>`` via the CLI runner.
|
|
|
|
The correction branch returns before calling _get_apply_service, so
|
|
we do NOT need to mock it.
|
|
"""
|
|
context.new_cov_result = runner.invoke(
|
|
plan_app, ["diff", plan_id, "--correction", corr]
|
|
)
|
|
|
|
|
|
@when(r'new_cov I run plan diff for "(?P<plan_id>[^"]+)" with format "(?P<fmt>[^"]+)"')
|
|
def step_new_cov_diff_with_fmt(context: Context, plan_id: str, fmt: str) -> None:
|
|
with patch(_PATCH_GET_APPLY, return_value=context.new_cov_mock_service):
|
|
context.new_cov_result = runner.invoke(
|
|
plan_app, ["diff", plan_id, "--format", fmt]
|
|
)
|
|
|
|
|
|
@when(r'new_cov I run plan diff for "(?P<plan_id>[^"]+)"')
|
|
def step_new_cov_diff(context: Context, plan_id: str) -> None:
|
|
with patch(_PATCH_GET_APPLY, return_value=context.new_cov_mock_service):
|
|
context.new_cov_result = runner.invoke(plan_app, ["diff", plan_id])
|
|
|
|
|
|
@when(
|
|
r'new_cov I run plan artifacts for "(?P<plan_id>[^"]+)" with format "(?P<fmt>[^"]+)"'
|
|
)
|
|
def step_new_cov_artifacts_with_fmt(context: Context, plan_id: str, fmt: str) -> None:
|
|
with patch(_PATCH_GET_APPLY, return_value=context.new_cov_mock_service):
|
|
context.new_cov_result = runner.invoke(
|
|
plan_app, ["artifacts", plan_id, "--format", fmt]
|
|
)
|
|
|
|
|
|
@when(r'new_cov I run plan artifacts for "(?P<plan_id>[^"]+)"')
|
|
def step_new_cov_artifacts(context: Context, plan_id: str) -> None:
|
|
with patch(_PATCH_GET_APPLY, return_value=context.new_cov_mock_service):
|
|
context.new_cov_result = runner.invoke(plan_app, ["artifacts", plan_id])
|
|
|
|
|
|
# Switch back to parse for the remaining non-parameterized WHEN step
|
|
use_step_matcher("parse")
|
|
|
|
|
|
@when("new_cov I call _get_apply_service directly")
|
|
def step_new_cov_call_get_apply(context: Context) -> None:
|
|
"""Call the real ``_get_apply_service``, mocking its dependencies.
|
|
|
|
This exercises the actual function body (import, lifecycle lookup,
|
|
``PlanApplyService`` construction) to cover lines 1817, 1821, 1822.
|
|
|
|
Both ``_get_lifecycle_service`` **and** ``PlanApplyService`` are
|
|
patched. The class is patched at two locations — the canonical
|
|
source module and the plan module (with ``create=True``) — so the
|
|
lazy ``from … import PlanApplyService`` inside the function always
|
|
resolves to our mock, even under ``behave-parallel``'s
|
|
``fork()``-based workers.
|
|
"""
|
|
from cleveragents.cli.commands.plan import _get_apply_service
|
|
|
|
mock_lifecycle = MagicMock()
|
|
mock_pas_cls = MagicMock()
|
|
mock_pas_instance = MagicMock()
|
|
mock_pas_cls.return_value = mock_pas_instance
|
|
|
|
with (
|
|
patch(_PATCH_LIFECYCLE, return_value=mock_lifecycle),
|
|
patch(
|
|
"cleveragents.cli.commands.plan.PlanApplyService",
|
|
mock_pas_cls,
|
|
create=True,
|
|
),
|
|
patch(_PATCH_PAS_CLASS, mock_pas_cls),
|
|
):
|
|
result = _get_apply_service()
|
|
|
|
context.new_cov_apply_result = result
|
|
context.new_cov_mock_lifecycle_used = mock_lifecycle
|
|
context.new_cov_mock_pas_cls = mock_pas_cls
|
|
context.new_cov_mock_pas_instance = mock_pas_instance
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# THEN steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("new_cov the exit code should be {code:d}")
|
|
def step_new_cov_exit_code(context: Context, code: int) -> None:
|
|
assert context.new_cov_result.exit_code == code, (
|
|
f"Expected exit code {code}, got {context.new_cov_result.exit_code}. "
|
|
f"Output: {context.new_cov_result.output}"
|
|
)
|
|
|
|
|
|
@then("new_cov the exit code should be nonzero")
|
|
def step_new_cov_exit_nonzero(context: Context) -> None:
|
|
assert context.new_cov_result.exit_code != 0, (
|
|
f"Expected non-zero exit code, got {context.new_cov_result.exit_code}. "
|
|
f"Output: {context.new_cov_result.output}"
|
|
)
|
|
|
|
|
|
@then('new_cov the output should contain "{text}"')
|
|
def step_new_cov_output_contains(context: Context, text: str) -> None:
|
|
output = context.new_cov_result.output
|
|
assert text in output, f"Expected '{text}' in output. Got:\n{output}"
|
|
|
|
|
|
@then("new_cov the returned object should be a PlanApplyService instance")
|
|
def step_new_cov_result_is_pas(context: Context) -> None:
|
|
result = context.new_cov_apply_result
|
|
expected = context.new_cov_mock_pas_instance
|
|
assert result is expected, (
|
|
f"Expected _get_apply_service to return the mock PlanApplyService "
|
|
f"instance, got {type(result).__name__}"
|
|
)
|
|
|
|
|
|
@then("new_cov PlanApplyService was constructed with the lifecycle service")
|
|
def step_new_cov_pas_called_with_lifecycle(context: Context) -> None:
|
|
context.new_cov_mock_pas_cls.assert_called_once_with(
|
|
lifecycle_service=context.new_cov_mock_lifecycle_used,
|
|
)
|