"""ORCH-016 / TC-13..TC-15: _post_usage_comments integration tests. End-to-end (DB + filesystem worktree, no network) verification that AgentLauncher._post_usage_comments: - resolves the task by (repo, branch), - threads the explicit duration_s into build_status_comment, - posts exactly ONE status comment authored by the finishing agent, - for deployer: ALSO posts the per-task usage summary (deployer authorship). The actual Plane HTTP call (plane_sync.add_comment) is patched out; we only check the (work_item_id, body, author) tuples the launcher passes to it. """ 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_post_usage.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 setup_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_worktree(monkeypatch, tmp_path): """Stub get_worktree_path inside the launcher module to a tmp_path location.""" wt = tmp_path / "wt" (wt / "docs" / "work-items" / WID).mkdir(parents=True) def _get_wt(repo, branch): return str(wt) # The launcher imports get_worktree_path lazily inside the function body # (`from ..git_worktree import get_worktree_path`); patch the source module. monkeypatch.setattr("src.git_worktree.get_worktree_path", _get_wt) monkeypatch.setattr("src.usage._input_total", lambda u: 0) # quiet tail return wt @pytest.fixture def capture_comments(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.fixture def public_url(monkeypatch): from src.config import settings monkeypatch.setattr( settings, "gitea_public_url", "https://git.mva154.duckdns.org", raising=False ) monkeypatch.setattr(settings, "gitea_url", "http://localhost:3000", raising=False) monkeypatch.setattr(settings, "gitea_owner", "admin", raising=False) # --------------------------------------------------------------------------- # TC-13: reviewer comment. # --------------------------------------------------------------------------- def test_tc13_reviewer_posts_one_status_comment( setup_db, fake_worktree, capture_comments, public_url ): (fake_worktree / "docs" / "work-items" / WID / "12-review.md").write_text( "---\nverdict: APPROVE\n---\nReviewed.", ) AgentLauncher()._post_usage_comments( run_id=99, agent="reviewer", repo=REPO, branch=BRANCH, usage={"input_tokens": 1, "output_tokens": 1, "cost_usd": 0.01}, duration_s=180, ) assert len(capture_comments) == 1 post = capture_comments[0] assert post["wid"] == WID assert post["author"] == "reviewer" body = post["body"] assert "\U0001f50e Reviewer" in body assert "Verdict: APPROVE" in body assert "Длительность: 3m 00s" in body assert "12-review.md" in body # --------------------------------------------------------------------------- # TC-14: tester comment. # --------------------------------------------------------------------------- def test_tc14_tester_posts_one_status_comment( setup_db, fake_worktree, capture_comments, public_url ): (fake_worktree / "docs" / "work-items" / WID / "13-test-report.md").write_text( "---\nverdict: PASS\n---\n", ) AgentLauncher()._post_usage_comments( run_id=100, agent="tester", repo=REPO, branch=BRANCH, usage=None, duration_s=42, ) assert len(capture_comments) == 1 post = capture_comments[0] assert post["author"] == "tester" body = post["body"] assert "\U0001f9ea Tester" in body assert "Verdict: PASS" in body assert "Длительность: 42s" in body # --------------------------------------------------------------------------- # TC-15: deployer comment + per-task summary (two comments, both from deployer). # --------------------------------------------------------------------------- def test_tc15_deployer_posts_status_then_summary( setup_db, fake_worktree, capture_comments, public_url ): # Task stage = 'deploy' so build_status_comment uses 14-deploy-log.md. conn = get_db() conn.execute("UPDATE tasks SET stage='deploy' WHERE id=1") conn.commit() conn.close() (fake_worktree / "docs" / "work-items" / WID / "14-deploy-log.md").write_text( "---\ndeploy_status: SUCCESS\n---\nDeployed.", ) AgentLauncher()._post_usage_comments( run_id=101, agent="deployer", repo=REPO, branch=BRANCH, usage={"input_tokens": 1, "output_tokens": 1, "cost_usd": 0.01}, duration_s=300, ) # 2 comments: status + per-task summary. assert len(capture_comments) == 2 status, summary = capture_comments assert status["author"] == "deployer" assert "Status: SUCCESS" in status["body"] assert "Длительность: 5m 00s" in status["body"] assert "14-deploy-log.md" in status["body"] assert summary["author"] == "deployer" # task_summary_comment header (Russian "Итого по задаче"). assert "\U0001f4ca" in summary["body"] assert "Итого" in summary["body"] def test_deployer_staging_picks_15_log( setup_db, fake_worktree, capture_comments, public_url ): conn = get_db() conn.execute("UPDATE tasks SET stage='deploy-staging' WHERE id=1") conn.commit() conn.close() (fake_worktree / "docs" / "work-items" / WID / "15-staging-log.md").write_text( "---\nstaging_status: SUCCESS\n---\n", ) AgentLauncher()._post_usage_comments( run_id=102, agent="deployer", repo=REPO, branch=BRANCH, usage=None, duration_s=10, ) # deployer always also posts the summary; check the FIRST comment is status. assert len(capture_comments) == 2 status = capture_comments[0] assert "Status: SUCCESS" in status["body"] assert "15-staging-log.md" in status["body"] assert "14-deploy-log.md" not in status["body"] assert "staging-деплой" in status["body"]