"""new_goal_dispatch.py tests (2026-08-13, FR-1 -- docs/done/scheduler-new-goal-dispatch-requirements.md): same mocked-at-the- subprocess-boundary / real-sqlite split as test_task_reconciliation.py. Run: python3 -m unittest src.test_new_goal_dispatch -v """ import subprocess import tempfile import unittest from unittest.mock import patch from src import new_goal_dispatch, project_scaffold, schema, task_execution class NewGoalDispatchTestBase(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.addCleanup(self.tmp.cleanup) subprocess.run(["git", "init"], cwd=self.tmp.name, check=True, capture_output=True) self.conn = schema.connect(":memory:") schema.create_schema(self.conn) project = project_scaffold.create_project(self.conn, self.tmp.name, init_git_if_missing=False) self.project_id = project["project_id"] def tearDown(self): self.conn.close() def _insert_task(self, task_id, status, goal_text="a goal"): self.conn.execute( "INSERT INTO task (task_id, project_id, goal_text, task_type, status, assigned_org_node, created_at) " "VALUES (?, ?, ?, 'goal', ?, 'planner', CURRENT_TIMESTAMP)", (task_id, self.project_id, goal_text, status), ) self.conn.commit() class FindQueuedTasksTest(NewGoalDispatchTestBase): def test_finds_queued_task(self): self._insert_task("task-1", "queued") result = new_goal_dispatch.find_queued_tasks(self.conn) self.assertEqual(result, [{"task_id": "task-1", "target_folder_path": self.tmp.name, "goal_text": "a goal"}]) def test_skips_non_queued_statuses(self): for status in ("coding", "validating", "done", "blocked_pending_human", "planning_brd_prd_sdd"): self._insert_task("task-" + status, status) result = new_goal_dispatch.find_queued_tasks(self.conn) self.assertEqual(result, []) def test_excludes_in_flight_task(self): self._insert_task("task-1", "queued") with patch.object(task_execution, "is_in_flight", return_value=True): result = new_goal_dispatch.find_queued_tasks(self.conn) self.assertEqual(result, []) def test_no_candidates_returns_empty_list(self): result = new_goal_dispatch.find_queued_tasks(self.conn) self.assertEqual(result, []) class DispatchOnceTest(unittest.TestCase): """Real file db (own connection, mirrors ReconcileOnceTest) -- dispatch itself is mocked so this stays at the "did we correctly decide to call it" boundary.""" def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.addCleanup(self.tmp.cleanup) subprocess.run(["git", "init"], cwd=self.tmp.name, check=True, capture_output=True) self.tmp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False) self.tmp_db.close() self.db_path = self.tmp_db.name self.addCleanup(lambda: __import__("os").path.exists(self.db_path) and __import__("os").remove(self.db_path)) conn = schema.connect(self.db_path) schema.create_schema(conn) project = project_scaffold.create_project(conn, self.tmp.name, init_git_if_missing=False) self.project_id = project["project_id"] conn.execute( "INSERT INTO task (task_id, project_id, goal_text, task_type, status, assigned_org_node, created_at) " "VALUES ('queued-1', ?, 'goal 1', 'goal', 'queued', 'planner', CURRENT_TIMESTAMP)", (self.project_id,), ) conn.execute( "INSERT INTO task (task_id, project_id, goal_text, task_type, status, assigned_org_node, created_at) " "VALUES ('coding-1', ?, 'goal 2', 'goal', 'coding', 'coder', CURRENT_TIMESTAMP)", (self.project_id,), ) conn.commit() conn.close() def test_dispatches_only_queued_candidates(self): with patch.object(task_execution, "start_task_execution_in_background") as mock_start: result = new_goal_dispatch.dispatch_once(self.db_path) self.assertEqual([c["task_id"] for c in result], ["queued-1"]) mock_start.assert_called_once_with(self.db_path, "queued-1", self.tmp.name, "goal 1") def test_one_dispatch_failure_does_not_block_others(self): conn = schema.connect(self.db_path) conn.execute( "INSERT INTO task (task_id, project_id, goal_text, task_type, status, assigned_org_node, created_at) " "VALUES ('queued-2', ?, 'goal 3', 'goal', 'queued', 'planner', CURRENT_TIMESTAMP)", (self.project_id,), ) conn.commit() conn.close() with patch.object( task_execution, "start_task_execution_in_background", side_effect=[task_execution.TaskExecutionError("already in flight"), None], ) as mock_start: result = new_goal_dispatch.dispatch_once(self.db_path) self.assertEqual(len(result), 2) self.assertEqual(mock_start.call_count, 2) def test_no_candidates_returns_gracefully(self): conn = schema.connect(self.db_path) conn.execute("UPDATE task SET status = 'done'") conn.commit() conn.close() with patch.object(task_execution, "start_task_execution_in_background") as mock_start: result = new_goal_dispatch.dispatch_once(self.db_path) self.assertEqual(result, []) mock_start.assert_not_called() if __name__ == "__main__": unittest.main()