136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""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
|