229 lines
7.1 KiB
Python
229 lines
7.1 KiB
Python
"""Coverage for the directive prompt assembly."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from robomp import persona
|
|
from robomp.worker import DirectiveInfo, ThreadMessage
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class _Repo:
|
|
full_name: str = "octo/widget"
|
|
default_branch: str = "main"
|
|
clone_url: str = ""
|
|
private: bool = False
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class _Issue:
|
|
repo: str = "octo/widget"
|
|
number: int = 1080
|
|
title: str = "broken thing"
|
|
body: str = "the body text"
|
|
state: str = "open"
|
|
author: str = "alice"
|
|
labels: tuple[str, ...] = ()
|
|
is_pull_request: bool = False
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class _Workspace:
|
|
branch: str = "farm/abc/test"
|
|
session_dir: str = "/tmp/session"
|
|
context_dir: str = "/tmp/ctx"
|
|
repo_dir: str = "/tmp/repo"
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class _Pr:
|
|
number: int = 99
|
|
author: str = "alice"
|
|
head_ref: str = "fix-crash"
|
|
base_ref: str = "main"
|
|
head_repo: str = "alice/widget"
|
|
html_url: str = "https://github.com/octo/widget/pull/99"
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class _Comment:
|
|
id: int = 1
|
|
author: str = "can1357"
|
|
body: str = "@roboomp please fix"
|
|
created_at: str = "2026-05-14T20:00:00Z"
|
|
|
|
|
|
def test_render_thread_empty_yields_placeholder() -> None:
|
|
assert persona._render_thread(()).startswith("(no prior")
|
|
|
|
|
|
def test_render_thread_orders_kinds_with_appropriate_headers() -> None:
|
|
thread = (
|
|
ThreadMessage(kind="issue_body", author="alice", body="orig report", created_at=""),
|
|
ThreadMessage(kind="comment", author="bob", body="me too", created_at="2026-05-01T10:00:00Z"),
|
|
ThreadMessage(
|
|
kind="review_comment",
|
|
author="codex",
|
|
body="leak here",
|
|
created_at="2026-05-02T10:00:00Z",
|
|
path="src/foo.py",
|
|
line=42,
|
|
),
|
|
ThreadMessage(
|
|
kind="review",
|
|
author="codex",
|
|
body="two issues",
|
|
created_at="2026-05-02T10:01:00Z",
|
|
state="CHANGES_REQUESTED",
|
|
),
|
|
)
|
|
out = persona._render_thread(thread)
|
|
# Issue body header (no timestamp).
|
|
assert "### @alice — issue body" in out
|
|
assert "orig report" in out
|
|
# Comment header with timestamp.
|
|
assert "### @bob — comment *(2026-05-01T10:00:00Z)*" in out
|
|
assert "me too" in out
|
|
# Review comment with file:line anchor.
|
|
assert "### @codex — review comment on `src/foo.py`:L42" in out
|
|
assert "leak here" in out
|
|
# Review with state badge.
|
|
assert "### @codex — review (CHANGES_REQUESTED)" in out
|
|
assert "two issues" in out
|
|
|
|
|
|
def test_directive_prompt_embeds_thread_and_directive_body() -> None:
|
|
thread = (
|
|
ThreadMessage(kind="comment", author="alice", body="follow up please", created_at="2026-05-01T10:00:00Z"),
|
|
)
|
|
out = persona.directive(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
comment=_Comment(),
|
|
workspace=_Workspace(),
|
|
directive=DirectiveInfo(body="apply fix Y", author="can1357", thread=thread),
|
|
pr_status="PR #1080 is open",
|
|
)
|
|
assert "Directive on octo/widget#1080" in out
|
|
assert "@can1357" in out
|
|
assert "apply fix Y" in out
|
|
assert "follow up please" in out
|
|
assert "PR #1080 is open" in out
|
|
|
|
|
|
def test_followup_comment_prompt_embeds_thread_context() -> None:
|
|
thread = (
|
|
ThreadMessage(kind="pr_body", author="roboomp", body="PR body", created_at=""),
|
|
ThreadMessage(kind="comment", author="can1357", body="prior request", created_at="2026-05-01T10:00:00Z"),
|
|
)
|
|
out = persona.followup_comment(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
comment=_Comment(body="current request"),
|
|
workspace=_Workspace(),
|
|
pr_status="PR #1080 is open",
|
|
pr_number=1080,
|
|
thread=thread,
|
|
)
|
|
|
|
assert "Prior conversation" in out
|
|
assert "PR body" in out
|
|
assert "prior request" in out
|
|
assert "current request" in out
|
|
|
|
|
|
def test_kickoff_directive_prompt_embeds_thread_and_classify_instruction() -> None:
|
|
thread = (ThreadMessage(kind="issue_body", author="alice", body="failing on macos", created_at=""),)
|
|
out = persona.kickoff_directive(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
directive=DirectiveInfo(body="reproduce + fix", author="can1357", thread=thread),
|
|
)
|
|
assert "Maintainer directive on octo/widget#1080" in out
|
|
assert "failing on macos" in out
|
|
assert "reproduce + fix" in out
|
|
# The kickoff variant must still tell the agent to classify first.
|
|
assert "Classify first" in out
|
|
|
|
|
|
def test_resume_triage_renders_branch_and_issue() -> None:
|
|
out = persona.resume_triage(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
)
|
|
# Working branch surfaces literally so the agent sees what it's on.
|
|
assert "farm/abc/test" in out
|
|
# Issue identity surfaces with the title.
|
|
assert "octo/widget#1080" in out
|
|
assert "broken thing" in out
|
|
# The prompt instructs the agent to reconcile drift via fetch_issue_thread.
|
|
assert "fetch_issue_thread" in out
|
|
|
|
|
|
def test_kickoff_pr_review_formats_head_repo_and_origin_base() -> None:
|
|
out = persona.kickoff_pr_review(
|
|
repo=_Repo(),
|
|
pr=_Pr(),
|
|
workspace=_Workspace(),
|
|
)
|
|
assert "`fix-crash` from `alice/widget`" in out
|
|
assert "git diff origin/main...HEAD" in out
|
|
|
|
|
|
def test_review_completion_reminder_mentions_submit_only() -> None:
|
|
out = persona.review_completion_reminder(
|
|
repo=_Repo(),
|
|
issue=_Issue(number=99, title="Fix parser"),
|
|
workspace=_Workspace(branch="review/pr-99"),
|
|
)
|
|
assert "submit_pr_review" in out
|
|
assert "gh_open_pr" not in out
|
|
|
|
|
|
def test_completion_reminder_limits_mark_unable_to_reporter_details() -> None:
|
|
out = persona.completion_reminder(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
)
|
|
assert "reporter-provided reproduction details" in out
|
|
assert "maintainer input" not in out
|
|
|
|
|
|
def test_system_append_renders_configured_bot_login() -> None:
|
|
out = persona.system_append(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
bot_login="Svitter",
|
|
)
|
|
# The hardcoded persona name MUST be replaced by the configured login so
|
|
# the agent self-mentions the account that actually receives webhooks.
|
|
assert "You are **@Svitter**" in out
|
|
assert "**robomp**" not in out
|
|
|
|
|
|
def test_system_append_routes_push_refusal_to_maintainer_comment_only() -> None:
|
|
out = persona.system_append(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
bot_login="Svitter",
|
|
)
|
|
assert "Push refused for reasons you cannot resolve? Ask the maintainer via `gh_post_comment`." in out
|
|
assert "or use `mark_unable_to_reproduce`" not in out
|
|
|
|
|
|
def test_system_append_pr_review_renders_configured_bot_login() -> None:
|
|
out = persona.system_append_pr_review(
|
|
repo=_Repo(),
|
|
issue=_Issue(),
|
|
workspace=_Workspace(),
|
|
bot_login="Svitter",
|
|
)
|
|
assert "You are **@Svitter**" in out
|
|
assert "**robomp**" not in out
|