fix(mock_concurrent_mma): make epic branch a catch-all for non-empty prompts
The stress test (tests/test_mma_concurrent_tracks_stress_sim.py) uses mma_epic_input='STRESS TEST: TRACK A AND TRACK B', which the mock's epic branch did NOT match (it only matched 'PATH: Epic Initialization'). The stress prompt fell to the Default branch which returns text (not JSON), and the production's orchestrator_pm.generate_tracks failed to parse it, returning 0 tracks. The test polled for proposed_tracks (60s timeout, never broke), clicked accept (no proposed_tracks to process), then asserted tracks >= 2 and found 0. Root cause: the mock's epic branch was a literal-substring check for a single test-specific prompt. It was not robust to other test prompts. Fix: restructure routing so that sprint and worker are checked first (more specific patterns), and ANY non-empty prompt that does not match those patterns is treated as an epic request (returns 2 tracks). Empty prompts fall to the Default branch. Verification: - test_mma_concurrent_tracks_execution: still PASSES (uses 'PATH: Epic Initialization' which matches the new catch-all since it doesn't contain sprint or worker patterns) - test_mma_concurrent_tracks_stress_sim: now PASSES (uses 'STRESS TEST: TRACK A AND TRACK B' which matches the new catch-all) - 3 consecutive PASS runs of both tests (13.94s, 14.81s, 14.13s) This is 'adjust the tests instead' per user directive - the mock is a test artifact, not production. The production's generate_tracks correctly returns [] for unparseable responses; the test mock should be robust enough to return valid JSON for any epic-like prompt.
This commit is contained in:
@@ -47,26 +47,7 @@ def main() -> None:
|
|||||||
|
|
||||||
call_n = _next_call_count()
|
call_n = _next_call_count()
|
||||||
|
|
||||||
# 1. Epic Initialization
|
# 1. Sprint Planning (different tickets for different tracks)
|
||||||
if 'PATH: Epic Initialization' in prompt:
|
|
||||||
mock_response = [
|
|
||||||
{"id": "track-a", "goal": "Track A Goal", "title": "Track A"},
|
|
||||||
{"id": "track-b", "goal": "Track B Goal", "title": "Track B"}
|
|
||||||
]
|
|
||||||
print(json.dumps({
|
|
||||||
"type": "message",
|
|
||||||
"role": "assistant",
|
|
||||||
"content": json.dumps(mock_response)
|
|
||||||
}), flush=True)
|
|
||||||
print(json.dumps({
|
|
||||||
"type": "result",
|
|
||||||
"status": "success",
|
|
||||||
"stats": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50},
|
|
||||||
"session_id": "mock-epic"
|
|
||||||
}), flush=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 2. Sprint Planning (different tickets for different tracks)
|
|
||||||
# Route on prompt content (the production passes the track_brief which
|
# Route on prompt content (the production passes the track_brief which
|
||||||
# contains "Track A" or "Track B"). The prior session_id-based routing was
|
# contains "Track A" or "Track B"). The prior session_id-based routing was
|
||||||
# fragile because:
|
# fragile because:
|
||||||
@@ -75,6 +56,7 @@ def main() -> None:
|
|||||||
# 2. session_id="mock-sprint-A" means "this is a follow-up call after
|
# 2. session_id="mock-sprint-A" means "this is a follow-up call after
|
||||||
# the 1st sprint returned mock-sprint-A", so the response should be
|
# the 1st sprint returned mock-sprint-A", so the response should be
|
||||||
# sprint-B (2nd track), not sprint-A.
|
# sprint-B (2nd track), not sprint-A.
|
||||||
|
# CHECK BEFORE epic so sprint takes priority over the catch-all epic branch.
|
||||||
if 'generate the implementation tickets' in prompt:
|
if 'generate the implementation tickets' in prompt:
|
||||||
if "Track A" in prompt: track_label = "A"
|
if "Track A" in prompt: track_label = "A"
|
||||||
elif "Track B" in prompt: track_label = "B"
|
elif "Track B" in prompt: track_label = "B"
|
||||||
@@ -83,7 +65,8 @@ def main() -> None:
|
|||||||
_emit_sprint_ticket(track_label)
|
_emit_sprint_ticket(track_label)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. Worker Execution
|
# 2. Worker Execution
|
||||||
|
# CHECK BEFORE epic so worker takes priority over the catch-all epic branch.
|
||||||
if 'You are assigned to Ticket' in prompt or session_id.startswith("mock-worker-"):
|
if 'You are assigned to Ticket' in prompt or session_id.startswith("mock-worker-"):
|
||||||
import re
|
import re
|
||||||
match = re.search(r'Ticket (ticket-[A-Ba-b]-1)', prompt, re.IGNORECASE)
|
match = re.search(r'Ticket (ticket-[A-Ba-b]-1)', prompt, re.IGNORECASE)
|
||||||
@@ -107,6 +90,31 @@ def main() -> None:
|
|||||||
}), flush=True)
|
}), flush=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 3. Epic Initialization (catch-all for any non-empty prompt that
|
||||||
|
# does not match the sprint or worker patterns above). This makes the
|
||||||
|
# mock robust to test-specific epic prompts (e.g. 'STRESS TEST: TRACK A
|
||||||
|
# AND TRACK B' used by test_mma_concurrent_tracks_stress_sim). The
|
||||||
|
# prior version only matched 'PATH: Epic Initialization', so other
|
||||||
|
# prompts fell to the Default branch and the production failed to parse
|
||||||
|
# the response as JSON, returning 0 tracks.
|
||||||
|
if prompt.strip():
|
||||||
|
mock_response = [
|
||||||
|
{"id": "track-a", "goal": "Track A Goal", "title": "Track A"},
|
||||||
|
{"id": "track-b", "goal": "Track B Goal", "title": "Track B"}
|
||||||
|
]
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "message",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": json.dumps(mock_response)
|
||||||
|
}), flush=True)
|
||||||
|
print(json.dumps({
|
||||||
|
"type": "result",
|
||||||
|
"status": "success",
|
||||||
|
"stats": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50},
|
||||||
|
"session_id": "mock-epic"
|
||||||
|
}), flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
# Default
|
# Default
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
"type": "message",
|
"type": "message",
|
||||||
|
|||||||
Reference in New Issue
Block a user