"""U-010 tests: c_org_node_trigger's full guard + transition logic, mocked at the subprocess boundary (src.org_node_trigger.trigger_org_node) per CLAUDE.md's Testing constraint. Real chained-trigger coverage (all three Org Agents, actually invoked) is in src/test_org_node_lifecycle_e2e.py (RUN_REAL_AGENT_E2E=1). Run: python3 -m unittest src.test_org_node_lifecycle -v """ import unittest from unittest.mock import patch from src import org_node_lifecycle, schema, task_create class LifecycleTestBase(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.commit() self.task_id = task_create.create_task(self.conn, "proj-1", "实现贪吃蛇游戏")["task_id"] def tearDown(self): self.conn.close() def _pass_artifact(self, artifact_type, run_id="run-1"): self.conn.execute( "INSERT INTO work_graph_run (run_id, task_id, org_node, graph_manifest, status) " "VALUES (?, ?, 'planner', '{}', 'done') ON CONFLICT(run_id) DO NOTHING", (run_id, self.task_id), ) self.conn.execute( "INSERT INTO artifact (artifact_id, artifact_type, task_id, produced_by_run_id, " "file_path, git_commit_sha, validation_status) VALUES (?, ?, ?, ?, 'f.md', 'sha', 'passed')", (artifact_type + "-artifact", artifact_type, self.task_id, run_id), ) self.conn.commit() class PlannerTriggerTest(LifecycleTestBase): def test_success_transitions_queued_to_planning(self): with patch("src.org_node_lifecycle.trigger_org_node", return_value="ok"): result = org_node_lifecycle.trigger_org_node_full( self.conn, "planner", self.task_id, "/tmp/proj-1", "go" ) self.assertEqual(result["status"], "planning_brd_prd_sdd") self.assertEqual(result["assigned_org_node"], "planner") row = self.conn.execute("SELECT status FROM task WHERE task_id = ?", (self.task_id,)).fetchone() self.assertEqual(row[0], "planning_brd_prd_sdd") def test_writes_task_status_changed_event(self): with patch("src.org_node_lifecycle.trigger_org_node", return_value="ok"): org_node_lifecycle.trigger_org_node_full(self.conn, "planner", self.task_id, "/tmp/proj-1", "go") event = self.conn.execute( "SELECT event_type FROM execution_event WHERE task_id = ? ORDER BY created_at DESC LIMIT 1", (self.task_id,), ).fetchone() self.assertEqual(event[0], "task_status_changed") def test_wrong_status_raises_task_not_ready(self): self.conn.execute("UPDATE task SET status = 'coding' WHERE task_id = ?", (self.task_id,)) self.conn.commit() with self.assertRaises(org_node_lifecycle.TaskNotReadyError): org_node_lifecycle.trigger_org_node_full(self.conn, "planner", self.task_id, "/tmp/proj-1", "go") def test_concurrency_limit_reached_blocks_trigger(self): self.conn.execute( "INSERT INTO org_concurrency_config (config_id, max_concurrent_goals, updated_by) " "VALUES ('default', 1, 'test')" ) other_task = task_create.create_task(self.conn, "proj-1", "other goal")["task_id"] self.conn.execute("UPDATE task SET status = 'coding' WHERE task_id = ?", (other_task,)) self.conn.commit() with self.assertRaises(org_node_lifecycle.ConcurrencyLimitReachedError): org_node_lifecycle.trigger_org_node_full(self.conn, "planner", self.task_id, "/tmp/proj-1", "go") def test_unknown_org_node_raises_value_error(self): with self.assertRaises(ValueError): org_node_lifecycle.trigger_org_node_full(self.conn, "not-a-node", self.task_id, "/tmp/proj-1", "go") def test_usage_limit_phrase_maps_to_usage_limit_reached(self): from src.org_node_trigger import OrgNodeTriggerError with patch( "src.org_node_lifecycle.trigger_org_node", side_effect=OrgNodeTriggerError("claude --agent planner: You've hit your session limit"), ): with self.assertRaises(org_node_lifecycle.UsageLimitReachedError): org_node_lifecycle.trigger_org_node_full(self.conn, "planner", self.task_id, "/tmp/proj-1", "go") def test_other_trigger_errors_are_not_reclassified(self): from src.org_node_trigger import OrgNodeTriggerError with patch( "src.org_node_lifecycle.trigger_org_node", side_effect=OrgNodeTriggerError("some other failure") ): with self.assertRaises(OrgNodeTriggerError): org_node_lifecycle.trigger_org_node_full(self.conn, "planner", self.task_id, "/tmp/proj-1", "go") class CoderTriggerTest(LifecycleTestBase): def setUp(self): super().setUp() self.conn.execute("UPDATE task SET status = 'planning_brd_prd_sdd' WHERE task_id = ?", (self.task_id,)) self.conn.commit() def test_blocked_until_all_four_artifacts_pass(self): with self.assertRaises(org_node_lifecycle.TaskNotReadyError): org_node_lifecycle.trigger_org_node_full(self.conn, "coder", self.task_id, "/tmp/proj-1", "go") for artifact_type in ("brd", "prd", "sdd"): # missing benchmark on purpose self._pass_artifact(artifact_type) with self.assertRaises(org_node_lifecycle.TaskNotReadyError): org_node_lifecycle.trigger_org_node_full(self.conn, "coder", self.task_id, "/tmp/proj-1", "go") def test_succeeds_once_all_four_artifacts_pass(self): for artifact_type in ("brd", "prd", "sdd", "benchmark"): self._pass_artifact(artifact_type) with patch("src.org_node_lifecycle.trigger_org_node", return_value="ok"): result = org_node_lifecycle.trigger_org_node_full( self.conn, "coder", self.task_id, "/tmp/proj-1", "go" ) self.assertEqual(result["status"], "coding") self.assertEqual(result["assigned_org_node"], "coder") class ValidatorTriggerTest(LifecycleTestBase): def setUp(self): super().setUp() self.conn.execute("UPDATE task SET status = 'coding' WHERE task_id = ?", (self.task_id,)) self.conn.commit() def test_blocked_until_code_diff_and_test_report_pass(self): with self.assertRaises(org_node_lifecycle.TaskNotReadyError): org_node_lifecycle.trigger_org_node_full(self.conn, "validator", self.task_id, "/tmp/proj-1", "go") def test_succeeds_once_both_artifacts_pass(self): self._pass_artifact("code_diff") self._pass_artifact("test_report") with patch("src.org_node_lifecycle.trigger_org_node", return_value="ok"): result = org_node_lifecycle.trigger_org_node_full( self.conn, "validator", self.task_id, "/tmp/proj-1", "go" ) self.assertEqual(result["status"], "validating") self.assertEqual(result["assigned_org_node"], "validator") if __name__ == "__main__": unittest.main()