Files
cleveragents-core/features/steps/plan_prompt_command_steps.py
T
freemo 02250473ad fix(ci): restore all CI quality gates to passing on master
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 (commit 9c6d6915) 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 (commit 0d5d9cf0 and 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 (commit 1a07a891):
   - '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 (commit 300a5d6d):
   - 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
2026-04-04 20:38:16 +00:00

182 lines
6.3 KiB
Python

"""Step definitions for the ``agents plan prompt`` CLI command."""
from __future__ import annotations
import json
from unittest.mock import MagicMock, patch
from behave import given, then, when
from typer.testing import CliRunner
from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest
from cleveragents.cli.commands.plan import app as plan_app
from cleveragents.core.exceptions import PlanError
@when("I run plan command help")
def step_run_plan_help(context) -> None:
runner = CliRunner()
context.prompt_result = runner.invoke(plan_app, ["--help"])
@then("help output should include plan prompt command")
def step_help_includes_prompt(context) -> None:
assert context.prompt_result.exit_code == 0, context.prompt_result.output
assert "prompt" in context.prompt_result.output
@given("a mocked lifecycle service prompt response")
def step_mock_lifecycle_prompt_response(context) -> None:
context.prompt_service_mock = MagicMock()
context.prompt_service_mock.prompt_plan.return_value = {
"guidance_added": {
"plan": "01HXM8C2ZK4Q7C2B3F2R4VYV6J",
"guidance": "Use mocks for database tests",
"scope": "next execution step",
"phase": "execute",
"state_transition": "errored -> processing",
},
"decision_created": {
"type": "user_intervention",
"id": "01HXM9C5G7R2X8S3K4Z5Q8R6Y3",
"parent": "01HXM9A1C2Q7W3R5G8Z0P4Q1X9",
},
"queue": {"pending": 1, "applied": 0},
}
@given("lifecycle service rejects prompt for inactive plan phase")
def step_mock_lifecycle_prompt_reject(context) -> None:
context.prompt_service_mock = MagicMock()
context.prompt_service_mock.prompt_plan.side_effect = PlanError(
"Plan is not in active execute phase"
)
@when(
'I run plan prompt with plan id "{plan_id}" and guidance "{guidance}" in format "{fmt}"'
)
def step_run_plan_prompt(context, plan_id: str, guidance: str, fmt: str) -> None:
runner = CliRunner()
with patch(
"cleveragents.cli.commands.plan._get_lifecycle_service",
return_value=context.prompt_service_mock,
):
context.prompt_result = runner.invoke(
plan_app,
["prompt", plan_id, guidance, "--format", fmt],
)
@then("the prompt command should succeed")
def step_prompt_success(context) -> None:
assert context.prompt_result.exit_code == 0, context.prompt_result.output
@then("the prompt command should abort")
def step_prompt_abort(context) -> None:
assert context.prompt_result.exit_code != 0
@then(
'lifecycle prompt should be called with plan id "{plan_id}" and guidance "{guidance}"'
)
def step_prompt_called(context, plan_id: str, guidance: str) -> None:
context.prompt_service_mock.prompt_plan.assert_called_once_with(plan_id, guidance)
def _json_output(result_output: str) -> dict[str, object]:
return json.loads(result_output.strip())
@then('prompt output envelope should contain command "{command}"')
def step_prompt_envelope_command(context, command: str) -> None:
payload = _json_output(context.prompt_result.output)
assert payload.get("command") == command
@then('prompt output envelope should contain status "{status}"')
def step_prompt_envelope_status(context, status: str) -> None:
payload = _json_output(context.prompt_result.output)
assert payload.get("status") == status
@then("prompt output data should include queued guidance")
def step_prompt_envelope_data(context) -> None:
payload = _json_output(context.prompt_result.output)
data = payload.get("data")
assert isinstance(data, dict)
guidance_added = data.get("guidance_added")
assert isinstance(guidance_added, dict)
assert guidance_added.get("guidance") == "Use mocks for database tests"
queue = data.get("queue")
assert isinstance(queue, dict)
assert queue.get("pending") == 1
@then('prompt output should include guidance text "{guidance}"')
def step_prompt_output_contains_guidance(context, guidance: str) -> None:
normalized_output = " ".join(context.prompt_result.output.split())
normalized_guidance = " ".join(guidance.split())
assert normalized_guidance in normalized_output
@then("prompt output should mention inactive execute phase")
def step_prompt_output_inactive_phase(context) -> None:
assert "active execute phase" in context.prompt_result.output.lower()
@given("an A2A facade with a lifecycle prompt service")
def step_facade_with_prompt_service(context) -> None:
class _PromptLifecycleService:
def prompt_plan(self, plan_id: str, guidance: str) -> dict[str, object]:
return {
"guidance_added": {
"plan": plan_id,
"guidance": guidance,
"scope": "next execution step",
"phase": "execute",
"state_transition": "errored -> processing",
},
"decision_created": {
"type": "user_intervention",
"id": "01HXM9C5G7R2X8S3K4Z5Q8R6Y3",
"parent": "01HXM9A1C2Q7W3R5G8Z0P4Q1X9",
},
"queue": {"pending": 1, "applied": 0},
}
context.prompt_facade = A2aLocalFacade(
{"plan_lifecycle_service": _PromptLifecycleService()}
)
@when(
'I dispatch _cleveragents plan prompt for plan "{plan_id}" and guidance "{guidance}"'
)
def step_dispatch_facade_prompt(context, plan_id: str, guidance: str) -> None:
request = A2aRequest(
method="_cleveragents/plan/prompt",
params={"plan_id": plan_id, "guidance": guidance},
)
context.facade_prompt_response = context.prompt_facade.dispatch(request)
@then("facade prompt response should not be a stub")
def step_facade_prompt_not_stub(context) -> None:
data = context.facade_prompt_response.result or {}
assert data.get("stub") is not True
@then('facade prompt response should contain plan id "{plan_id}"')
def step_facade_prompt_plan(context, plan_id: str) -> None:
data = context.facade_prompt_response.result or {}
assert data.get("plan_id") == plan_id
@then('facade prompt response should contain guidance "{guidance}"')
def step_facade_prompt_guidance(context, guidance: str) -> None:
data = context.facade_prompt_response.result or {}
assert data.get("guidance") == guidance