From 2696824e09d372409bd169db04d13dbaa7f85def Mon Sep 17 00:00:00 2001 From: frankbria Date: Tue, 7 Jul 2026 15:42:06 -0700 Subject: [PATCH 1/2] test(tasks): cover server-side execute_agent (BUILD) path (#749) Adds an API-level test that calls POST /tasks/{id}/start?execute=true with MockProvider injected (CODEFRAME_LLM_PROVIDER=mock), polls the run to a terminal status, and asserts the agent-dispatch branch (_run_agent -> runtime.execute_agent) completes and produces output. Joins the endpoint's fire-and-forget worker thread before teardown to avoid a DB-race on the temp workspace. Closes #749 --- tests/ui/test_v2_routers_integration.py | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/ui/test_v2_routers_integration.py b/tests/ui/test_v2_routers_integration.py index 57458c80..3521b113 100644 --- a/tests/ui/test_v2_routers_integration.py +++ b/tests/ui/test_v2_routers_integration.py @@ -709,6 +709,69 @@ def test_start_task_not_found(self, test_client): assert response.status_code == 404 + def test_start_task_execute_runs_agent_to_terminal( + self, test_client_with_task, monkeypatch + ): + """Start with execute=true dispatches the agent (BUILD path) to a terminal run. + + Covers the agent-dispatch branch (`_run_agent` -> `runtime.execute_agent`), + which the other `/start` tests skip by omitting `execute=true` (#749). + + MockProvider is injected via CODEFRAME_LLM_PROVIDER=mock: the router builds + it through `get_provider("mock")`, and its text-only response makes the + ReAct agent complete on the first iteration (react_agent.py:518). + """ + import threading + import time + + from codeframe.core import runtime, streaming + + monkeypatch.setenv("CODEFRAME_LLM_PROVIDER", "mock") + + task_id = test_client_with_task.task.id + workspace = test_client_with_task.workspace + + # Snapshot threads so we can join the endpoint's fire-and-forget worker + # before the fixture deletes the temp workspace (avoids a teardown race + # where the still-draining thread touches a removed DB). + threads_before = set(threading.enumerate()) + + response = test_client_with_task.post( + f"/api/v2/tasks/{task_id}/start?execute=true" + ) + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert data["status"] == "executing" + run_id = data["run_id"] + + # execute_agent runs in a background daemon thread; poll the run until it + # leaves RUNNING (terminal = COMPLETED / FAILED / BLOCKED). + deadline = time.monotonic() + 30.0 + status = "RUNNING" + while time.monotonic() < deadline: + run_resp = test_client_with_task.get(f"/api/v2/tasks/{task_id}/run") + assert run_resp.status_code == 200 + status = run_resp.json()["status"] + if status != runtime.RunStatus.RUNNING.value: + break + time.sleep(0.2) + + # Let the worker thread finish its tail work (logging/recording) before + # assertions and fixture teardown remove the workspace out from under it. + for thread in set(threading.enumerate()) - threads_before: + thread.join(timeout=10.0) + + assert status != runtime.RunStatus.RUNNING.value, ( + f"Run {run_id} never reached a terminal status (stuck at {status})" + ) + assert status == runtime.RunStatus.COMPLETED.value, ( + f"Expected COMPLETED for the mock-driven BUILD path, got {status}" + ) + + # The BUILD path produced output events (the whole point of dispatching). + assert streaming.run_output_exists(workspace, run_id) is True + def test_get_task_run(self, test_client_with_task): """Get task run status after starting.""" task_id = test_client_with_task.task.id From 4ca329bee2bd93bb427afb6bb53d5dc510a70ef4 Mon Sep 17 00:00:00 2001 From: frankbria Date: Tue, 7 Jul 2026 15:49:53 -0700 Subject: [PATCH 2/2] test(tasks): assert output file has content, not just existence (#749) Address claude-review: run_output_exists() is only path.exists(), and RunOutputLogger creates the file at execute_agent start, so existence only proved the run started. Assert st_size > 0 to prove the BUILD path actually emitted output. --- tests/ui/test_v2_routers_integration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/ui/test_v2_routers_integration.py b/tests/ui/test_v2_routers_integration.py index 3521b113..28c25e3a 100644 --- a/tests/ui/test_v2_routers_integration.py +++ b/tests/ui/test_v2_routers_integration.py @@ -769,8 +769,12 @@ def test_start_task_execute_runs_agent_to_terminal( f"Expected COMPLETED for the mock-driven BUILD path, got {status}" ) - # The BUILD path produced output events (the whole point of dispatching). - assert streaming.run_output_exists(workspace, run_id) is True + # The BUILD path emitted output events (the whole point of dispatching). + # Assert on file *content*, not mere existence: RunOutputLogger creates the + # output file at execute_agent start, so path.exists() only proves the run + # started — a non-empty file proves the agent actually wrote output. + output_path = streaming.get_run_output_path(workspace, run_id) + assert output_path.stat().st_size > 0, "BUILD path produced no output" def test_get_task_run(self, test_client_with_task): """Get task run status after starting."""