Skip to content

Commit fe5065a

Browse files
test(8a5b-e07b): RED — add single batch reduction tests for ticket-graph.py
Story bd09-9857: ticket-graph.py consolidated batch scan. 3 RED tests asserting reduce_all_tickets called once per invocation instead of per-ticket _reduce_ticket calls in _find_direct_blockers and _compute_dep_graph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 63fdfa0 commit fe5065a

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

.test-index

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ plugins/dso/scripts/issue-quality-check.sh:tests/scripts/test_issue_quality_chec
6060
plugins/dso/scripts/pre-commit-format-fix.sh:tests/scripts/test-precommit-format-fix-config-paths.sh
6161
plugins/dso/scripts/project-detect.sh:tests/scripts/test-ci-generator-integration.sh,tests/scripts/test-project-detect.sh
6262
plugins/dso/scripts/ticket-bridge-status.sh:tests/scripts/test_bridge_status.py
63-
plugins/dso/scripts/ticket-graph.py:tests/scripts/test_ticket_graph.py
63+
plugins/dso/scripts/ticket-graph.py:tests/scripts/test_ticket_graph.py [test_build_dep_graph_single_batch_scan]
6464
plugins/dso/scripts/ticket-lib.sh:tests/scripts/test_revert_event.py,tests/scripts/test-ticket-lib.sh,tests/scripts/test-ticket-health-guards.sh
6565
plugins/dso/scripts/ticket-list.sh:tests/scripts/test-ticket-list.sh,tests/scripts/test_bridge_alert_display.py
6666
plugins/dso/scripts/ticket-revert.sh:tests/scripts/test_revert_event.py

tests/scripts/test_ticket_graph.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,3 +1103,150 @@ def test_deps_archived_direct_target_error(tmp_path: Path) -> None:
11031103
import shutil
11041104

11051105
shutil.rmtree(str(tracker_dir.parent), ignore_errors=True)
1106+
1107+
1108+
# ── RED MARKER BOUNDARY ──────────────────────────────────────────────────────
1109+
# Tests below this line are expected to FAIL (RED) until ticket-graph.py is
1110+
# refactored to use a single reduce_all_tickets call for deps operations.
1111+
# The .test-index RED marker points to the first test below:
1112+
# test_build_dep_graph_single_batch_scan
1113+
# Tests ABOVE this line are GREEN and must always pass.
1114+
1115+
1116+
@pytest.mark.unit
1117+
@pytest.mark.scripts
1118+
def test_build_dep_graph_single_batch_scan(graph: ModuleType, tmp_path: Path) -> None:
1119+
"""build_dep_graph must use a single reduce_all_tickets call instead of per-ticket scans.
1120+
1121+
Setup:
1122+
- A tracker with 5 tickets: ticket-a (closed, blocks ticket-e), ticket-b,
1123+
ticket-c, ticket-d (all open), ticket-e (open, target ticket).
1124+
1125+
Expected: reduce_all_tickets is called exactly once during build_dep_graph.
1126+
1127+
Currently RED: build_dep_graph calls _reduce_ticket per-ticket via
1128+
_compute_dep_graph and _find_direct_blockers. It does not call reduce_all_tickets.
1129+
"""
1130+
from unittest.mock import patch
1131+
1132+
tracker_dir = tmp_path / "tracker"
1133+
tracker_dir.mkdir()
1134+
1135+
_write_ticket(tracker_dir, "ticket-a", status="closed")
1136+
_write_ticket(tracker_dir, "ticket-b", status="open")
1137+
_write_ticket(tracker_dir, "ticket-c", status="open")
1138+
_write_ticket(tracker_dir, "ticket-d", status="open")
1139+
_write_ticket(tracker_dir, "ticket-e", status="open")
1140+
_write_blocks_link(tracker_dir, "ticket-a", "ticket-e")
1141+
1142+
# Capture the real reduce_all_tickets so the patch can delegate to it
1143+
real_reduce_all = graph._reducer.reduce_all_tickets
1144+
1145+
call_count = []
1146+
1147+
def counting_reduce_all(*args, **kwargs): # type: ignore[no-untyped-def]
1148+
call_count.append(1)
1149+
return real_reduce_all(*args, **kwargs)
1150+
1151+
with patch.object(
1152+
graph._reducer, "reduce_all_tickets", side_effect=counting_reduce_all
1153+
):
1154+
graph.build_dep_graph("ticket-e", str(tracker_dir))
1155+
1156+
assert len(call_count) == 1, (
1157+
f"Expected reduce_all_tickets to be called exactly once during build_dep_graph, "
1158+
f"but it was called {len(call_count)} time(s). "
1159+
"build_dep_graph must pre-load all ticket states via a single reduce_all_tickets "
1160+
"call instead of calling _reduce_ticket per-ticket in _find_direct_blockers and "
1161+
"_compute_dep_graph."
1162+
)
1163+
1164+
1165+
@pytest.mark.unit
1166+
@pytest.mark.scripts
1167+
def test_find_direct_blockers_no_per_ticket_scan(
1168+
graph: ModuleType, tmp_path: Path
1169+
) -> None:
1170+
"""_find_direct_blockers must not call _reduce_ticket directly — use pre-loaded state.
1171+
1172+
Setup:
1173+
- ticket-blocker: open, blocks ticket-target
1174+
- ticket-target: open
1175+
1176+
Pre-loaded state dict is passed in. _reduce_ticket must NOT be called.
1177+
1178+
Currently RED: _find_direct_blockers calls _reduce_ticket directly for each
1179+
ticket dir it scans. After refactor, it must accept a pre-loaded all_states
1180+
dict and use that instead.
1181+
"""
1182+
from unittest.mock import patch
1183+
1184+
tracker_dir = tmp_path / "tracker"
1185+
tracker_dir.mkdir()
1186+
1187+
_write_ticket(tracker_dir, "ticket-blocker", status="open")
1188+
_write_ticket(tracker_dir, "ticket-target", status="open")
1189+
_write_blocks_link(tracker_dir, "ticket-blocker", "ticket-target")
1190+
1191+
reduce_ticket_calls = []
1192+
1193+
def spy_reduce_ticket(*args, **kwargs): # type: ignore[no-untyped-def]
1194+
reduce_ticket_calls.append(args)
1195+
return graph._reduce_ticket(*args, **kwargs)
1196+
1197+
with patch.object(graph, "_reduce_ticket", side_effect=spy_reduce_ticket):
1198+
# After refactor, _find_direct_blockers should accept all_states and not call _reduce_ticket
1199+
graph._find_direct_blockers("ticket-target", str(tracker_dir))
1200+
1201+
assert len(reduce_ticket_calls) == 0, (
1202+
f"Expected _reduce_ticket to be called 0 times in _find_direct_blockers "
1203+
f"(should use pre-loaded state), but it was called {len(reduce_ticket_calls)} time(s). "
1204+
"_find_direct_blockers must be refactored to accept a pre-loaded all_states dict "
1205+
"and look up ticket states from it instead of calling _reduce_ticket per ticket."
1206+
)
1207+
1208+
1209+
@pytest.mark.unit
1210+
@pytest.mark.scripts
1211+
def test_compute_dep_graph_children_use_preloaded_state(
1212+
graph: ModuleType, tmp_path: Path
1213+
) -> None:
1214+
"""_compute_dep_graph must not call _reduce_ticket for children discovery.
1215+
1216+
Setup:
1217+
- parent-epic: epic with 3 child stories
1218+
- story-a, story-b, story-c: open stories with parent_id=parent-epic
1219+
1220+
Expected: _reduce_ticket is NOT called during _compute_dep_graph. All state
1221+
lookups should use a pre-loaded all_states dict passed in from build_dep_graph.
1222+
1223+
Currently RED: _compute_dep_graph calls _reduce_ticket for each directory entry
1224+
to discover children. After refactor, it must use pre-loaded state.
1225+
"""
1226+
from unittest.mock import patch
1227+
1228+
tracker_dir = tmp_path / "tracker"
1229+
tracker_dir.mkdir()
1230+
1231+
_write_ticket(tracker_dir, "parent-epic", ticket_type="epic")
1232+
_write_ticket(tracker_dir, "story-a", parent_id="parent-epic", ticket_type="story")
1233+
_write_ticket(tracker_dir, "story-b", parent_id="parent-epic", ticket_type="story")
1234+
_write_ticket(tracker_dir, "story-c", parent_id="parent-epic", ticket_type="story")
1235+
1236+
reduce_ticket_calls = []
1237+
1238+
def spy_reduce_ticket(*args, **kwargs): # type: ignore[no-untyped-def]
1239+
reduce_ticket_calls.append(args)
1240+
return graph._reduce_ticket(*args, **kwargs)
1241+
1242+
with patch.object(graph, "_reduce_ticket", side_effect=spy_reduce_ticket):
1243+
graph._compute_dep_graph("parent-epic", str(tracker_dir))
1244+
1245+
assert len(reduce_ticket_calls) == 0, (
1246+
f"Expected _reduce_ticket to be called 0 times in _compute_dep_graph "
1247+
f"(should use pre-loaded state for children discovery), "
1248+
f"but it was called {len(reduce_ticket_calls)} time(s). "
1249+
"_compute_dep_graph must be refactored to receive a pre-loaded all_states dict "
1250+
"and use it for both children discovery and blocker resolution instead of "
1251+
"calling _reduce_ticket per directory entry."
1252+
)

0 commit comments

Comments
 (0)