rewrite phase 1
This commit is contained in:
+193
-6
@@ -1,8 +1,11 @@
|
||||
from agentci.adapters.gitea_models import CommentInfo, IssueInfo
|
||||
from agentci.workflows.context import ContextBuilder
|
||||
from typing import cast
|
||||
|
||||
from agentci.engine.repository import Repository
|
||||
from agentci.gitea import CommentInfo, Gitea, IssueInfo, PullRequestInfo
|
||||
from agentci.workflows.context import build_issue_context, build_pull_request_context
|
||||
|
||||
|
||||
class FakeGitea:
|
||||
class FakeIssueGitea:
|
||||
async def issue(self, *_args):
|
||||
return IssueInfo(number=2, title="Broken widget", body="It fails.", state="open")
|
||||
|
||||
@@ -13,15 +16,199 @@ class FakeGitea:
|
||||
]
|
||||
|
||||
|
||||
class FakeStorage:
|
||||
class FakeRepository:
|
||||
async def operational_comment_ids(self, *_args):
|
||||
return {2}
|
||||
|
||||
|
||||
class FakePullRequestGitea:
|
||||
async def pull_request(self, *_args):
|
||||
return PullRequestInfo(
|
||||
number=3,
|
||||
title="Fix widget",
|
||||
body="Fixes the failure.",
|
||||
state="open",
|
||||
merged=False,
|
||||
base_branch="main",
|
||||
head_branch="fix-widget",
|
||||
head_sha="abcdef1234567890",
|
||||
head_owner="alice",
|
||||
head_repo="repo",
|
||||
)
|
||||
|
||||
async def issue_comments(self, *_args):
|
||||
return [CommentInfo(3, "bob", "Please add a test.", "2026-01-03")]
|
||||
|
||||
async def pull_reviews(self, *_args):
|
||||
return [
|
||||
{
|
||||
"id": 4,
|
||||
"user": {"login": "carol"},
|
||||
"state": "REQUEST_CHANGES",
|
||||
"body": "One issue remains.",
|
||||
}
|
||||
]
|
||||
|
||||
async def review_comments(self, *_args):
|
||||
return [
|
||||
{
|
||||
"path": "src/widget.py",
|
||||
"new_position": 12,
|
||||
"body": "Handle the empty value.",
|
||||
}
|
||||
]
|
||||
|
||||
async def pull_commits(self, *_args):
|
||||
return [{"sha": "abcdef1234567890", "commit": {"message": "Fix widget"}}]
|
||||
|
||||
|
||||
class EmptyIssueGitea:
|
||||
async def issue(self, *_args):
|
||||
return IssueInfo(number=5, title="Empty issue", body="", state="open")
|
||||
|
||||
async def issue_comments(self, *_args):
|
||||
return []
|
||||
|
||||
|
||||
class EmptyRepository:
|
||||
async def operational_comment_ids(self, *_args):
|
||||
return set()
|
||||
|
||||
|
||||
class EmptyPullRequestGitea:
|
||||
async def pull_request(self, *_args):
|
||||
return PullRequestInfo(
|
||||
number=6,
|
||||
title="Empty pull request",
|
||||
body="",
|
||||
state="open",
|
||||
merged=False,
|
||||
base_branch="main",
|
||||
head_branch="empty",
|
||||
head_sha="1234567890abcdef",
|
||||
head_owner="alice",
|
||||
head_repo="repo",
|
||||
)
|
||||
|
||||
async def issue_comments(self, *_args):
|
||||
return []
|
||||
|
||||
async def pull_reviews(self, *_args):
|
||||
return []
|
||||
|
||||
async def pull_commits(self, *_args):
|
||||
return []
|
||||
|
||||
async def review_comments(self, *_args):
|
||||
raise AssertionError("review comments should not be requested without reviews")
|
||||
|
||||
|
||||
class OrderedPullRequestGitea(FakePullRequestGitea):
|
||||
async def issue_comments(self, *_args):
|
||||
return [
|
||||
CommentInfo(1, "alice", "First timeline comment", "2026-01-01"),
|
||||
CommentInfo(2, "bob", "Second timeline comment", "2026-01-02"),
|
||||
]
|
||||
|
||||
async def pull_reviews(self, *_args):
|
||||
return [
|
||||
{
|
||||
"id": 10,
|
||||
"user": {"login": "carol"},
|
||||
"state": "REQUEST_CHANGES",
|
||||
"body": "First formal review",
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"user": {"login": "dave"},
|
||||
"state": "APPROVED",
|
||||
"body": "Second formal review",
|
||||
},
|
||||
]
|
||||
|
||||
async def review_comments(self, *_args):
|
||||
review_id = _args[-1]
|
||||
if review_id == 10:
|
||||
return [
|
||||
{
|
||||
"path": "src/old.py",
|
||||
"new_position": None,
|
||||
"old_position": 21,
|
||||
"body": "Old-side position",
|
||||
}
|
||||
]
|
||||
return [
|
||||
{
|
||||
"path": "src/new.py",
|
||||
"new_position": 34,
|
||||
"body": "New-side position",
|
||||
}
|
||||
]
|
||||
|
||||
async def pull_commits(self, *_args):
|
||||
return [
|
||||
{"sha": "111111111111aaaa", "commit": {"message": "First commit"}},
|
||||
{"sha": "222222222222bbbb", "commit": {"message": "Second commit"}},
|
||||
]
|
||||
|
||||
|
||||
async def test_issue_context_excludes_operational_comments() -> None:
|
||||
builder = ContextBuilder(FakeGitea(), FakeStorage()) # type: ignore[arg-type]
|
||||
context = await builder.issue_context("org", "repo", 2)
|
||||
context = await build_issue_context(
|
||||
cast(Gitea, FakeIssueGitea()),
|
||||
cast(Repository, FakeRepository()),
|
||||
"org",
|
||||
"repo",
|
||||
2,
|
||||
)
|
||||
|
||||
assert "Broken widget" in context
|
||||
assert "Details" in context
|
||||
assert "Agent job queued" not in context
|
||||
|
||||
|
||||
async def test_pull_request_context_includes_feedback_and_commits() -> None:
|
||||
pull, context = await build_pull_request_context(
|
||||
cast(Gitea, FakePullRequestGitea()), "org", "repo", 3
|
||||
)
|
||||
|
||||
assert pull.number == 3
|
||||
assert "Please add a test." in context
|
||||
assert "One issue remains." in context
|
||||
assert "`src/widget.py:12`: Handle the empty value." in context
|
||||
assert "abcdef123456 Fix widget" in context
|
||||
|
||||
|
||||
async def test_issue_context_labels_empty_body_and_discussion() -> None:
|
||||
context = await build_issue_context(
|
||||
cast(Gitea, EmptyIssueGitea()),
|
||||
cast(Repository, EmptyRepository()),
|
||||
"org",
|
||||
"repo",
|
||||
5,
|
||||
)
|
||||
|
||||
assert "## Issue body\n(empty)" in context
|
||||
assert "## Discussion\n(none)" in context
|
||||
|
||||
|
||||
async def test_pull_request_context_labels_empty_sections() -> None:
|
||||
_, context = await build_pull_request_context(
|
||||
cast(Gitea, EmptyPullRequestGitea()), "org", "repo", 6
|
||||
)
|
||||
|
||||
assert "## Pull request body\n(empty)" in context
|
||||
assert "## Commits\n(none)" in context
|
||||
assert "## Timeline discussion\n(none)" in context
|
||||
assert "## Formal and inline reviews\n(none)" in context
|
||||
|
||||
|
||||
async def test_pull_request_context_preserves_source_order_and_positions() -> None:
|
||||
_, context = await build_pull_request_context(
|
||||
cast(Gitea, OrderedPullRequestGitea()), "org", "repo", 3
|
||||
)
|
||||
|
||||
assert context.index("First timeline comment") < context.index("Second timeline comment")
|
||||
assert context.index("111111111111 First commit") < context.index("222222222222 Second commit")
|
||||
assert context.index("Review 10 by carol") < context.index("Review 11 by dave")
|
||||
assert "`src/old.py:21`: Old-side position" in context
|
||||
assert "`src/new.py:34`: New-side position" in context
|
||||
|
||||
Reference in New Issue
Block a user