feat(plane): unified status-comment format with duration line (ORCH-016) (#34)
This commit was merged in pull request #34.
This commit is contained in:
135
tests/test_analyst_status_only_regression.py
Normal file
135
tests/test_analyst_status_only_regression.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""ORCH-016 / TC-16 + AC-6: analyst status-only regression.
|
||||
|
||||
Status-only verdict model (PR #12 / #13):
|
||||
- analyst finishes its run -> Plane state becomes In Review,
|
||||
- ONE status comment is posted asking the stakeholder to flip the status to
|
||||
Approved (or write a reason and switch to Rejected),
|
||||
- NO auto-advance happens — the next stage waits for human approval.
|
||||
|
||||
The ORCH-016 PR refactors the comment text into the unified status-comment
|
||||
helper. This regression test guards against:
|
||||
(a) the analyst path silently auto-advancing,
|
||||
(b) the analyst comment losing the «Approved» / «Rejected» instruction text,
|
||||
(c) the comment switching authorship away from the analyst bot.
|
||||
|
||||
We exercise `_handle_analysis_approved_flow` directly (the launcher path).
|
||||
"""
|
||||
|
||||
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_analyst_so.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
|
||||
|
||||
|
||||
REPO = "enduro-trails"
|
||||
BRANCH = "feature/ET-016-x"
|
||||
WID = "ET-016"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_db(monkeypatch, tmp_path):
|
||||
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, ?, ?, 'analysis', ?)",
|
||||
(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):
|
||||
base = tmp_path / "wt"
|
||||
docs = base / "docs" / "work-items" / WID
|
||||
docs.mkdir(parents=True)
|
||||
# All analyst artifacts present -> "files_check" returns True.
|
||||
for f in ("01-brd.md", "02-trz.md", "03-acceptance-criteria.md",
|
||||
"04-test-plan.yaml"):
|
||||
(docs / f).write_text("x")
|
||||
monkeypatch.setattr("src.git_worktree.get_worktree_path", lambda r, b: str(base))
|
||||
monkeypatch.setattr("src.stage_engine.get_worktree_path", lambda r, b: str(base))
|
||||
monkeypatch.setattr("src.qg.checks.get_worktree_path", lambda r, b: str(base))
|
||||
return base
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def collect_calls(monkeypatch):
|
||||
calls = {"in_review": 0, "advance": 0, "comments": [], "enqueued": []}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.stage_engine.set_issue_in_review",
|
||||
lambda wid: calls.__setitem__("in_review", calls["in_review"] + 1),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.stage_engine.notify_approve_requested", lambda tid: None
|
||||
)
|
||||
|
||||
def _add_comment(wid, body, author=None, **kw):
|
||||
calls["comments"].append({"wid": wid, "body": body, "author": author})
|
||||
|
||||
monkeypatch.setattr("src.stage_engine.plane_add_comment", _add_comment)
|
||||
|
||||
# advance_stage isn't directly hit; if anything calls update_task_stage to
|
||||
# 'architecture', we'd see it here.
|
||||
def _update_task_stage(task_id, stage):
|
||||
calls["advance"] += 1
|
||||
|
||||
monkeypatch.setattr("src.stage_engine.update_task_stage", _update_task_stage)
|
||||
|
||||
def _enqueue(*a, **k):
|
||||
calls["enqueued"].append((a, k))
|
||||
return 1
|
||||
|
||||
monkeypatch.setattr("src.stage_engine.enqueue_job", _enqueue)
|
||||
return calls
|
||||
|
||||
|
||||
def test_tc16_analyst_goes_to_in_review_no_advance(fake_worktree, collect_calls):
|
||||
"""When the analyst finishes with complete artifacts, the task goes to In
|
||||
Review and NO advance/enqueue happens — the human approves via Plane status.
|
||||
"""
|
||||
from src.stage_engine import _handle_analysis_approved_flow, AdvanceResult
|
||||
|
||||
result = AdvanceResult(from_stage="analysis")
|
||||
_handle_analysis_approved_flow(
|
||||
task_id=1, current_stage="analysis", repo=REPO, work_item_id=WID,
|
||||
branch=BRANCH, agent="analyst", result=result,
|
||||
)
|
||||
|
||||
# In Review state requested in Plane.
|
||||
assert collect_calls["in_review"] == 1, collect_calls
|
||||
# NO stage-machine advance.
|
||||
assert collect_calls["advance"] == 0, collect_calls
|
||||
# NO new job enqueued by the analyst path.
|
||||
assert collect_calls["enqueued"] == [], collect_calls
|
||||
|
||||
# Exactly one comment posted, authored by analyst, with required text bits.
|
||||
assert len(collect_calls["comments"]) == 1, collect_calls["comments"]
|
||||
c = collect_calls["comments"][0]
|
||||
assert c["wid"] == WID
|
||||
assert c["author"] == "analyst"
|
||||
body = c["body"]
|
||||
assert "Approved" in body
|
||||
assert "Rejected" in body
|
||||
assert ":approved:" not in body
|
||||
assert "In Progress" not in body
|
||||
# AC-6 +: the new unified format adds a Длительность line (DB fallback).
|
||||
# No agent_runs row exists in this test, so the line should be ABSENT.
|
||||
assert "Длительность" not in body
|
||||
Reference in New Issue
Block a user