"""ORCH-016 / TC-19 + AC-1..AC-5 authorship: status comments use per-agent bots. When a status comment is posted by AgentLauncher._post_usage_comments, the underlying plane_sync.add_comment must be invoked with ``author=`` so plane_sync._headers_for() picks the agent's bot token (PLANE_BOT_TOKENS[role]) — falling back to PLANE_HEADERS when the bot token is empty / role unknown. Comment FORMAT changes (ORCH-016) must not affect that authorship contract. """ import os import tempfile os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") _test_db = os.path.join(tempfile.gettempdir(), "test_orch016_authorship.db") os.environ["ORCH_DB_PATH"] = _test_db import pytest # noqa: E402 from src import db as db_module # noqa: E402 from src.db import init_db, get_db # noqa: E402 from src.agents.launcher import AgentLauncher # noqa: E402 REPO = "enduro-trails" BRANCH = "feature/ET-016-x" WID = "ET-016" @pytest.fixture def db(monkeypatch): monkeypatch.setattr(db_module.settings, "db_path", _test_db, raising=False) if os.path.exists(_test_db): os.unlink(_test_db) init_db() conn = get_db() conn.execute( "INSERT INTO tasks (id, repo, branch, stage, work_item_id) " "VALUES (1, ?, ?, 'review', ?)", (REPO, BRANCH, WID), ) conn.commit() conn.close() yield if os.path.exists(_test_db): os.unlink(_test_db) @pytest.fixture def fake_wt(monkeypatch, tmp_path): base = tmp_path / "wt" (base / "docs" / "work-items" / WID).mkdir(parents=True) monkeypatch.setattr("src.git_worktree.get_worktree_path", lambda r, b: str(base)) return base @pytest.fixture def capture(monkeypatch): posts = [] def _spy(work_item_id, body, author=None, **kwargs): posts.append({"wid": work_item_id, "body": body, "author": author}) monkeypatch.setattr("src.agents.launcher.plane_add_comment", _spy) return posts @pytest.mark.parametrize("agent", ["architect", "developer", "reviewer", "tester"]) def test_tc19_status_comment_carries_agent_author(agent, db, fake_wt, capture): """Each agent's status comment must be POST-ed under that agent's bot.""" AgentLauncher()._post_usage_comments( run_id=1, agent=agent, repo=REPO, branch=BRANCH, usage=None, duration_s=10, ) assert len(capture) >= 1 assert capture[0]["author"] == agent, ( f"Expected author={agent!r}, got {capture[0]['author']!r}" ) def test_tc19_deployer_status_and_summary_both_authored_by_deployer(db, fake_wt, capture): """Deployer posts TWO comments (status + per-task summary) — both ``author='deployer'``.""" conn = get_db() conn.execute("UPDATE tasks SET stage='deploy' WHERE id=1") conn.commit() conn.close() AgentLauncher()._post_usage_comments( run_id=2, agent="deployer", repo=REPO, branch=BRANCH, usage=None, duration_s=10, ) assert len(capture) == 2 assert {c["author"] for c in capture} == {"deployer"} def test_tc19_headers_for_unknown_role_falls_back(monkeypatch): """Ensure plane_sync._headers_for handles unknown agents (fallback contract).""" from src import plane_sync h = plane_sync._headers_for("unknown_role_xyz") # PLANE_HEADERS fallback uses settings.plane_api_token (set to 'test-token'). assert isinstance(h, dict) and "X-API-Key" in h def test_tc19_status_comment_format_preserves_author_contract(db, fake_wt, capture): """The ORCH-016 format change must not strip the author= kw from the call site.""" (fake_wt / "docs" / "work-items" / WID / "12-review.md").write_text( "---\nverdict: APPROVE\n---\n", ) AgentLauncher()._post_usage_comments( run_id=3, agent="reviewer", repo=REPO, branch=BRANCH, usage={"input_tokens": 0, "output_tokens": 0, "cost_usd": 0.0}, duration_s=180, ) assert len(capture) == 1 post = capture[0] assert post["author"] == "reviewer" # And the new format is present in the body (sanity). assert "\U0001f50e Reviewer" in post["body"] assert "Verdict: APPROVE" in post["body"] assert "Длительность: 3m 00s" in post["body"]