test(plan-tree): add failing BDD scenario proving corrected nodes not visually marked
CI / push-validation (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 30s
CI / lint (pull_request) Successful in 54s
CI / build (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 1m24s
CI / security (pull_request) Successful in 1m32s
CI / typecheck (pull_request) Successful in 1m35s
CI / e2e_tests (pull_request) Successful in 3m34s
CI / integration_tests (pull_request) Failing after 4m3s
CI / unit_tests (pull_request) Failing after 4m6s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Failing after 2m21s
CI / push-validation (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 30s
CI / lint (pull_request) Successful in 54s
CI / build (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 1m24s
CI / security (pull_request) Successful in 1m32s
CI / typecheck (pull_request) Successful in 1m35s
CI / e2e_tests (pull_request) Successful in 3m34s
CI / integration_tests (pull_request) Failing after 4m3s
CI / unit_tests (pull_request) Failing after 4m6s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Failing after 2m21s
Fix step definitions to correctly fail with AssertionError (not AttributeError) so the @tdd_expected_fail tag properly inverts the CI result. Key changes: - Remove # type: ignore[import-untyped] from behave import (policy violation) - Fix find_corrected_node() to accept list[dict] (build_decision_tree returns a list of root nodes, not a single dict) - Remove dead code _make_decision() helper that was never called - Update CONTRIBUTORS.md with TDD contribution acknowledgement The scenario now correctly fails with AssertionError when build_decision_tree does not include a visual marker for corrected decisions (is_correction=True), allowing @tdd_expected_fail to invert the result and keep CI green. ISSUES CLOSED: #8576
This commit is contained in:
+1
-1
@@ -14,5 +14,5 @@ Below are some of the specific details of various contributions.
|
||||
|
||||
* Jeffrey Phillips Freeman has acted as Lead Developer, daily contributor, and Project Owner.
|
||||
* Brent E. Edwards has contributed quality assurance, test coverage, and CI pipeline improvements.
|
||||
* HAL 9000 has contributed automated implementation, bug fixes, and feature development as part of the CleverAgents automation pool.
|
||||
* HAL 9000 has contributed automated implementation, bug fixes, feature development, and TDD issue-capture test scenarios as part of the CleverAgents automation pool.
|
||||
* This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc.
|
||||
|
||||
@@ -7,7 +7,7 @@ where the current implementation does NOT visually distinguish corrected nodes.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from behave import given, then, when # type: ignore[import-untyped]
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
from ulid import ULID
|
||||
|
||||
@@ -15,28 +15,12 @@ from cleveragents.cli.commands.plan import build_decision_tree
|
||||
from cleveragents.domain.models.core.decision import Decision, DecisionType
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_PLAN_ID = str(ULID())
|
||||
|
||||
|
||||
def _make_decision(**overrides: object) -> Decision:
|
||||
"""Build a Decision with sensible defaults."""
|
||||
defaults: dict[str, object] = {
|
||||
"plan_id": _PLAN_ID,
|
||||
"sequence_number": 0,
|
||||
"decision_type": DecisionType.PROMPT_DEFINITION,
|
||||
"question": "What should we build?",
|
||||
"chosen_option": "A REST API",
|
||||
}
|
||||
dt = overrides.get("decision_type", defaults["decision_type"])
|
||||
if dt != DecisionType.PROMPT_DEFINITION and "parent_decision_id" not in overrides:
|
||||
defaults["parent_decision_id"] = str(ULID())
|
||||
defaults.update(overrides)
|
||||
return Decision(**defaults)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Given steps
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -108,23 +92,27 @@ def step_build_tree_default(context: Context) -> None:
|
||||
@then("the tree should include a visual marker for the corrected decision")
|
||||
def step_tree_has_correction_marker(context: Context) -> None:
|
||||
"""Verify that the tree data includes a marker for the corrected decision."""
|
||||
tree_data = context.tdd_tree_data
|
||||
tree_nodes: list[dict[str, object]] = context.tdd_tree_data
|
||||
|
||||
# The tree_data is a dict representation of the tree structure
|
||||
# The tree_data is a list of root-level node dicts.
|
||||
# We need to search through it to find the corrected decision node
|
||||
# and verify it has a visual marker
|
||||
# and verify it has a visual marker.
|
||||
|
||||
def find_corrected_node(node: dict) -> dict | None:
|
||||
def find_corrected_node(
|
||||
nodes: list[dict[str, object]],
|
||||
) -> dict[str, object] | None:
|
||||
"""Recursively search for the corrected decision node."""
|
||||
if node.get("decision_id") == context.tdd_corrected_id:
|
||||
return node
|
||||
for child in node.get("children", []):
|
||||
result = find_corrected_node(child)
|
||||
if result:
|
||||
for node in nodes:
|
||||
if node.get("decision_id") == context.tdd_corrected_id:
|
||||
return node
|
||||
children = node.get("children", [])
|
||||
assert isinstance(children, list)
|
||||
result = find_corrected_node(children)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
corrected_node = find_corrected_node(tree_data)
|
||||
corrected_node = find_corrected_node(tree_nodes)
|
||||
assert corrected_node is not None, (
|
||||
f"Corrected decision {context.tdd_corrected_id} not found in tree"
|
||||
)
|
||||
@@ -137,18 +125,31 @@ def step_tree_has_correction_marker(context: Context) -> None:
|
||||
'the corrected decision label should contain "[corrected]" or "✎" or similar marker'
|
||||
)
|
||||
def step_corrected_label_has_marker(context: Context) -> None:
|
||||
"""Verify that the corrected decision's label contains a visual marker."""
|
||||
"""Verify that the corrected decision's label contains a visual marker.
|
||||
|
||||
This assertion is expected to FAIL (via @tdd_expected_fail) because
|
||||
build_decision_tree does not currently include a 'label' key in its
|
||||
node dicts, and even if it did, it does not add visual markers for
|
||||
corrected decisions (is_correction=True). This TDD scenario documents
|
||||
the gap described in Spec Requirement #7.
|
||||
"""
|
||||
corrected_node = context.tdd_corrected_node
|
||||
|
||||
# The label should be in the node's label or display_label field
|
||||
label = corrected_node.get("label", "")
|
||||
# The label should be in the node's label field.
|
||||
# build_decision_tree currently does NOT include a 'label' key in its
|
||||
# node dicts -- this assertion will fail with AssertionError, proving
|
||||
# the bug exists (Spec Requirement #7: corrected nodes must be visually
|
||||
# marked in the plan tree output).
|
||||
label = str(corrected_node.get("label", ""))
|
||||
|
||||
# Check for common visual markers for corrections
|
||||
markers = ["[corrected]", "✎", "[correction]", "✏", "[fix]", "[amended]"]
|
||||
markers = ["[corrected]", "\u270e", "[correction]", "\u270f", "[fix]", "[amended]"]
|
||||
|
||||
has_marker = any(marker in label for marker in markers)
|
||||
|
||||
assert has_marker, (
|
||||
f"Corrected decision label '{label}' does not contain any visual marker. "
|
||||
f"Expected one of: {markers}"
|
||||
f"Expected one of: {markers}. "
|
||||
f"build_decision_tree does not currently add visual markers for "
|
||||
f"decisions with is_correction=True (Spec Requirement #7 gap)."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user