"""U-015 tests: judge_root_cause's 2/3-majority logic and the test_failed/ human_reported wiring, with fake perspective_fns (CLAUDE.md Testing constraint -- real perspectives shell out to `claude`, mocked here). Real coverage is in src/test_root_cause_trace_e2e.py (RUN_REAL_AGENT_E2E=1). Run: python3 -m unittest src.test_root_cause_trace -v """ import unittest from src import bug_record, root_cause_trace, schema def _fixed(category, detail="detail"): return lambda: {"category": category, "detail": detail} class JudgeRootCauseTest(unittest.TestCase): def test_unanimous_agreement(self): result = root_cause_trace.judge_root_cause( [_fixed("coder", "d1"), _fixed("coder", "d2"), _fixed("coder", "d3")] ) self.assertEqual(result["root_cause_category"], "coder") self.assertIn("d1", result["root_cause_detail"]) self.assertIn("d2", result["root_cause_detail"]) def test_two_of_three_agreement_wins(self): result = root_cause_trace.judge_root_cause( [_fixed("planning", "d1"), _fixed("planning", "d2"), _fixed("coder", "d3")] ) self.assertEqual(result["root_cause_category"], "planning") self.assertNotIn("d3", result["root_cause_detail"]) def test_three_way_split_is_inconclusive(self): with self.assertRaises(root_cause_trace.InconclusiveEscalateError) as ctx: root_cause_trace.judge_root_cause( [_fixed("planning"), _fixed("coder"), _fixed("skill_defect")] ) self.assertEqual(len(ctx.exception.judgments), 3) def test_below_two_thirds_with_more_perspectives_is_inconclusive(self): # 2 out of 5 agreeing is not >=2/3 (04-P: "至少 2/3 视角一致") with self.assertRaises(root_cause_trace.InconclusiveEscalateError): root_cause_trace.judge_root_cause( [_fixed("coder"), _fixed("coder"), _fixed("planning"), _fixed("planning"), _fixed("skill_defect")] ) class TraceRootCauseTestFailedTest(unittest.TestCase): def setUp(self): self.conn = schema.connect(":memory:") schema.create_schema(self.conn) self.conn.execute( "INSERT INTO project (project_id, name, target_folder_path, has_git_repo, status) " "VALUES ('proj-1', 'P', '/tmp/proj-1', 1, 'active')" ) self.conn.execute( "INSERT INTO task (task_id, project_id, goal_text, task_type, status, assigned_org_node) " "VALUES ('task-1', 'proj-1', 'goal', 'goal', 'validating', 'validator')" ) self.conn.commit() def tearDown(self): self.conn.close() def test_agreement_creates_bug_record(self): result = root_cause_trace.trace_root_cause_test_failed( self.conn, "task-1", "wall collision misjudged", [_fixed("coder", "SDD §3.2"), _fixed("coder", "code line 42"), _fixed("planning", "no")], suggested_route="fast_lane", ) self.assertEqual(result["root_cause_category"], "coder") row = self.conn.execute( "SELECT source, status, task_id FROM bug_record WHERE bug_id = ?", (result["bug_id"],) ).fetchone() self.assertEqual(row, ("validator", "routed", "task-1")) def test_disagreement_raises_and_creates_no_record(self): with self.assertRaises(root_cause_trace.InconclusiveEscalateError): root_cause_trace.trace_root_cause_test_failed( self.conn, "task-1", "x", [_fixed("planning"), _fixed("coder"), _fixed("skill_defect")], suggested_route="fast_lane", ) count = self.conn.execute("SELECT COUNT(*) FROM bug_record").fetchone()[0] self.assertEqual(count, 0) class TraceRootCauseHumanReportedTest(unittest.TestCase): def setUp(self): self.conn = schema.connect(":memory:") schema.create_schema(self.conn) self.conn.execute( "INSERT INTO bug_record (bug_id, source, description, status) " "VALUES ('bug-1', 'human', 'something looks broken', 'triaging')" ) self.conn.commit() def tearDown(self): self.conn.close() def test_completes_existing_record_not_new_one(self): root_cause_trace.trace_root_cause_human_reported( self.conn, "bug-1", [_fixed("planning", "gap"), _fixed("planning", "gap2"), _fixed("coder")], suggested_route="full_replan", ) count = self.conn.execute("SELECT COUNT(*) FROM bug_record").fetchone()[0] self.assertEqual(count, 1) row = self.conn.execute("SELECT status, root_cause_category FROM bug_record WHERE bug_id = 'bug-1'").fetchone() self.assertEqual(row, ("routed", "planning")) class RealValidatorPerspectiveParsingTest(unittest.TestCase): def test_parses_category_and_detail(self): fake_trigger = lambda org_node, prompt, target, timeout=300: "CATEGORY: coder\nDETAIL: off-by-one in bounds check" perspective_fn = root_cause_trace.real_validator_perspective( "correctness", "bug desc", "context", "/tmp/p", trigger_fn=fake_trigger ) result = perspective_fn() self.assertEqual(result, {"category": "coder", "detail": "off-by-one in bounds check"}) def test_missing_category_line_raises(self): fake_trigger = lambda org_node, prompt, target, timeout=300: "I have no idea what happened." perspective_fn = root_cause_trace.real_validator_perspective( "correctness", "bug desc", "context", "/tmp/p", trigger_fn=fake_trigger ) with self.assertRaises(ValueError): perspective_fn() if __name__ == "__main__": unittest.main()