forked from HAL9000/cleveragents-core
1878998b7a
Rename the TDD tag system from tdd_bug/tdd_bug_<N> to tdd_issue/tdd_issue_<N> across the entire codebase. The tdd_expected_fail tag is unchanged. The TDD expected-failure workflow is not limited to bug fixes — it applies equally to any issue type (features, tasks, refactors). The _bug suffix was misleading and narrowed the perceived scope. The new _issue suffix accurately reflects that the TDD tagging system applies to any Forgejo issue. Changes span 92 files: - features/environment.py: validate_tdd_tags(), should_invert_result(), and apply_tdd_inversion() updated — regex, variables, error messages - robot/tdd_expected_fail_listener.py: _validate_tdd_tags(), _should_invert_result(), start_test(), end_test() updated consistently - 33 Behave .feature files: all @tdd_bug/@tdd_bug_<N> tags renamed - 29 Robot .robot files: all tdd_bug/tdd_bug_<N> tags renamed - 3 Robot fixture files renamed (tdd_bug_alone, tdd_missing_tdd_bug, tdd_expected_fail_missing_bug_n) with content and references updated - Tag validation tests and helpers updated (function names, command dispatch keys, output strings, fixture references) - CONTRIBUTING.md: section renamed from 'TDD Bug Test Tags' to 'TDD Issue Test Tags', all tag references and examples updated - noxfile.py: comment references updated - Step definition files, mock helpers, and benchmark files: docstring references updated ISSUES CLOSED: #965
182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
"""Step definitions for tdd_plan_explain_plan_id.feature.
|
|
|
|
TDD issue-capture test for bug #968: ``plan explain`` expects a decision_id
|
|
as its first positional argument, but the M3 acceptance test passes a plan_id.
|
|
Because ``svc.get_decision(plan_id)`` raises ``DecisionNotFoundError`` (the
|
|
plan ID is not a decision ID), the command exits with rc=1 and "Decision not
|
|
found" error.
|
|
|
|
These steps use the ``@tdd_expected_fail`` tag so that the assertions — which
|
|
expect the *fixed* behaviour (rc=0 with decision details) — do not fail CI
|
|
while the bug is still unfixed. Once bug #968 is fixed the tag will be
|
|
removed and the tests will run normally.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from typer.testing import CliRunner
|
|
from ulid import ULID
|
|
|
|
from cleveragents.application.services.decision_service import (
|
|
DecisionNotFoundError,
|
|
)
|
|
from cleveragents.cli.commands.plan import app as plan_app
|
|
from cleveragents.domain.models.core.decision import (
|
|
Decision,
|
|
DecisionType,
|
|
)
|
|
|
|
runner = CliRunner()
|
|
|
|
_PATCH_CONTAINER = "cleveragents.application.container.get_container"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_decision(
|
|
decision_id: str | None = None,
|
|
plan_id: str | None = None,
|
|
sequence: int = 0,
|
|
parent_id: str | None = None,
|
|
dtype: DecisionType = DecisionType.PROMPT_DEFINITION,
|
|
question: str = "What should we build?",
|
|
chosen: str = "A REST API",
|
|
) -> Decision:
|
|
"""Build a minimal Decision with sensible defaults."""
|
|
did = decision_id or str(ULID())
|
|
pid = plan_id or str(ULID())
|
|
kwargs: dict = {
|
|
"decision_id": did,
|
|
"plan_id": pid,
|
|
"sequence_number": sequence,
|
|
"decision_type": dtype,
|
|
"question": question,
|
|
"chosen_option": chosen,
|
|
}
|
|
if parent_id is not None:
|
|
kwargs["parent_decision_id"] = parent_id
|
|
elif dtype != DecisionType.PROMPT_DEFINITION:
|
|
kwargs["parent_decision_id"] = str(ULID())
|
|
return Decision(**kwargs)
|
|
|
|
|
|
def _mock_container_with_decision_svc(svc_mock: MagicMock) -> MagicMock:
|
|
"""Create a mock container whose ``decision_service()`` returns *svc_mock*."""
|
|
container = MagicMock()
|
|
container.decision_service.return_value = svc_mock
|
|
return container
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given(
|
|
"tdd968 a mock DecisionService where get_decision raises DecisionNotFoundError for a plan id"
|
|
)
|
|
def step_tdd968_mock_get_decision_none(context: Context) -> None:
|
|
"""Simulate the current buggy behaviour: plan ID is not a decision ID."""
|
|
context.tdd968_plan_id = str(ULID())
|
|
svc = MagicMock()
|
|
# get_decision raises DecisionNotFoundError when called with a plan_id (the bug)
|
|
svc.get_decision.side_effect = DecisionNotFoundError(context.tdd968_plan_id)
|
|
context.tdd968_svc = svc
|
|
context.tdd968_container = _mock_container_with_decision_svc(svc)
|
|
|
|
|
|
@given(
|
|
"tdd968 the same mock DecisionService returns decisions "
|
|
"via list_decisions for the plan id"
|
|
)
|
|
def step_tdd968_mock_list_decisions(context: Context) -> None:
|
|
"""Set up list_decisions to return real decisions for the plan.
|
|
|
|
After the fix, the command should fall back to this lookup when
|
|
get_decision raises DecisionNotFoundError.
|
|
"""
|
|
root_id = str(ULID())
|
|
child_id = str(ULID())
|
|
pid = context.tdd968_plan_id
|
|
context.tdd968_root_question = "What should we build?"
|
|
decisions = [
|
|
_make_decision(
|
|
decision_id=root_id,
|
|
plan_id=pid,
|
|
sequence=0,
|
|
question=context.tdd968_root_question,
|
|
chosen="A REST API",
|
|
),
|
|
_make_decision(
|
|
decision_id=child_id,
|
|
plan_id=pid,
|
|
parent_id=root_id,
|
|
sequence=1,
|
|
dtype=DecisionType.STRATEGY_CHOICE,
|
|
question="Which framework?",
|
|
chosen="FastAPI",
|
|
),
|
|
]
|
|
context.tdd968_svc.list_decisions.return_value = decisions
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("tdd968 I invoke plan explain with the plan id")
|
|
def step_tdd968_invoke_explain(context: Context) -> None:
|
|
"""Invoke ``plan explain <plan_id>`` via CliRunner with mocked container."""
|
|
with patch(_PATCH_CONTAINER, return_value=context.tdd968_container):
|
|
result = runner.invoke(
|
|
plan_app,
|
|
["explain", context.tdd968_plan_id, "--format", "json"],
|
|
)
|
|
context.tdd968_result = result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("tdd968 the command should exit with return code 0")
|
|
def step_tdd968_rc_zero(context: Context) -> None:
|
|
"""Assert rc=0 — this will FAIL while bug #968 is unfixed (rc=1)."""
|
|
assert context.tdd968_result.exit_code == 0, (
|
|
f"Expected exit code 0 but got {context.tdd968_result.exit_code}. "
|
|
f"Output: {context.tdd968_result.output}"
|
|
)
|
|
|
|
|
|
@then("tdd968 the output should contain decision details")
|
|
def step_tdd968_output_has_details(context: Context) -> None:
|
|
"""Assert the output contains decision fields — fails while bug exists."""
|
|
output = context.tdd968_result.output
|
|
# When the bug is fixed, the output should contain decision fields
|
|
assert "decision_id" in output and "question" in output, (
|
|
f"Expected decision details in output, got: {output}"
|
|
)
|
|
# Verify that the fix called list_decisions with the correct plan_id
|
|
context.tdd968_svc.list_decisions.assert_called_once_with(
|
|
context.tdd968_plan_id,
|
|
)
|
|
|
|
|
|
@then("tdd968 the output should contain the root decision question")
|
|
def step_tdd968_output_has_question(context: Context) -> None:
|
|
"""Assert the root decision question appears — fails while bug exists."""
|
|
output = context.tdd968_result.output
|
|
assert context.tdd968_root_question in output, (
|
|
f"Expected root question '{context.tdd968_root_question}' "
|
|
f"in output, got: {output}"
|
|
)
|