"""Tests for fix/taskmd-description (3 bugs at the analyst pipeline entry/exit): BUG A: start_pipeline built the analyst .task.md WITHOUT the description body (only Title), so analyst received a ~101-byte file and reported the "business request is empty". task_desc must now carry the description. BUG B: issue.updated ships only changed fields, so `name` is usually absent -> slug/branch became "untitled". start_pipeline must pull the real name from the Plane API (single fetch_issue_fields GET, above the slug build) so the branch slug is NOT "untitled". BUG C: the analyst "artifacts ready" comment used the obsolete ":approved:" wording. Under the status-only model it must ask for the **Approved** status (not ":approved:", not "In Progress") and link the docs that actually exist. """ import os import tempfile _test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_taskmd_desc.db") os.environ["ORCH_DB_PATH"] = _test_db os.environ.setdefault("ORCH_PLANE_WEBHOOK_SECRET", "") os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") import pytest # noqa: E402 from unittest.mock import patch, AsyncMock # noqa: E402 from fastapi.testclient import TestClient # noqa: E402 from src.main import app # noqa: E402 from src.db import init_db, get_db # noqa: E402 from src import projects as P # noqa: E402 from src.projects import reload_projects # noqa: E402 ENDURO_PLANE_ID = "7a79f0a9-5278-49cd-9007-9a338f238f9c" IN_PROGRESS = "b873d9eb-993c-48cd-97ac-99a9b1623967" BACKLOG = "113b24f6-cce8-4be9-9a22-a359b9cf0122" client = TestClient(app) @pytest.fixture(autouse=True) def setup(monkeypatch): monkeypatch.setattr(P.settings, "db_path", _test_db) import src.db as _db monkeypatch.setattr(_db.settings, "db_path", _test_db) if os.path.exists(_test_db): os.unlink(_test_db) init_db() monkeypatch.setattr("src.webhooks.plane.verify_plane_signature", lambda body, sig: True) registry_json = ( f'[{{"plane_project_id": "{ENDURO_PLANE_ID}", "repo": "enduro-trails",' f' "work_item_prefix": "ET", "name": "enduro-trails"}}]' ) monkeypatch.setattr(P.settings, "projects_json", registry_json) reload_projects() yield reload_projects() if os.path.exists(_test_db): os.unlink(_test_db) def _task(plane_id): conn = get_db() row = conn.execute("SELECT * FROM tasks WHERE plane_id=?", (plane_id,)).fetchone() conn.close() return row # --------------------------------------------------------------------------- # # BUG A: description reaches the analyst .task.md # --------------------------------------------------------------------------- # @patch("src.webhooks.plane.enqueue_job", return_value=1) @patch("src.webhooks.plane._create_initial_docs", new_callable=AsyncMock) @patch("src.webhooks.plane._create_gitea_branch", new_callable=AsyncMock) @patch("src.plane_sync.fetch_issue_sequence_id", return_value=11) @patch("src.plane_sync.fetch_issue_fields", return_value=("ET-011 real title", "REAL BUSINESS REQUEST BODY: user wants GPX upload with " "validation and a results map.")) def test_taskdesc_includes_description( mock_fields, mock_seq, mock_branch, mock_docs, mock_enqueue ): resp = client.post("/webhook/plane", json={ "event": "issue", "action": "updated", "data": { "id": "taskA", # status change payload: NO name, NO description (only changed field) "project": ENDURO_PLANE_ID, "state": {"id": IN_PROGRESS, "name": "In Progress", "group": "started"}, }, "activity": {"field": "state", "new_value": IN_PROGRESS, "old_value": BACKLOG}, }) assert resp.status_code == 200 mock_enqueue.assert_called_once() # task_desc is the 3rd positional arg of enqueue_job(agent, repo, task_desc, ...) task_desc = mock_enqueue.call_args.args[2] assert "Description:" in task_desc # the actual description body (not just the Title) is in the file assert "REAL BUSINESS REQUEST BODY" in task_desc assert "results map" in task_desc # --------------------------------------------------------------------------- # # BUG B: name fetched from Plane API when payload is empty -> slug not untitled # --------------------------------------------------------------------------- # @patch("src.webhooks.plane.enqueue_job", return_value=1) @patch("src.webhooks.plane._create_initial_docs", new_callable=AsyncMock) @patch("src.webhooks.plane._create_gitea_branch", new_callable=AsyncMock) @patch("src.plane_sync.fetch_issue_sequence_id", return_value=11) @patch("src.plane_sync.fetch_issue_fields", return_value=("GPX upload feature", "A sufficiently long description so QG-0 passes cleanly.")) def test_name_fetched_when_payload_empty( mock_fields, mock_seq, mock_branch, mock_docs, mock_enqueue ): resp = client.post("/webhook/plane", json={ "event": "issue", "action": "updated", "data": { "id": "taskB", # NO name, NO description in the payload (Plane status-change shape) "project": ENDURO_PLANE_ID, "state": {"id": IN_PROGRESS, "name": "In Progress", "group": "started"}, }, "activity": {"field": "state", "new_value": IN_PROGRESS, "old_value": BACKLOG}, }) assert resp.status_code == 200 mock_fields.assert_called_once() row = _task("taskB") assert row is not None branch = row["branch"] # slug derived from the fetched name -> "gpx-upload-feature", NOT untitled assert "untitled" not in branch assert "gpx-upload-feature" in branch # Title in the analyst task file is the fetched name, not "untitled" task_desc = mock_enqueue.call_args.args[2] assert "Title: GPX upload feature" in task_desc