agent: implement issue #7 - Fix: 修复 comment 的 bug
This commit is contained in:
@@ -174,6 +174,7 @@ class FakeWorkspaceManager:
|
||||
self.diff = diff
|
||||
self.pushed: list[str] = []
|
||||
self.resumed: list[tuple[str, Path | None]] = []
|
||||
self.cleaned: list[Path] = []
|
||||
|
||||
def prepare(self, repo, issue, branch_name):
|
||||
path = self.root / branch_name.replace("/", "_")
|
||||
@@ -189,6 +190,9 @@ class FakeWorkspaceManager:
|
||||
def has_diff(self, workspace, base_ref="origin/HEAD"):
|
||||
return self.diff
|
||||
|
||||
def has_uncommitted_changes(self, workspace):
|
||||
return self.diff
|
||||
|
||||
def push_branch(self, workspace, branch_name):
|
||||
self.pushed.append(branch_name)
|
||||
|
||||
@@ -197,7 +201,7 @@ class FakeWorkspaceManager:
|
||||
return "abc1234"
|
||||
|
||||
def cleanup(self, workspace):
|
||||
pass
|
||||
self.cleaned.append(Path(workspace))
|
||||
|
||||
|
||||
class FakeRunner:
|
||||
@@ -349,6 +353,55 @@ def test_scan_pull_request_feedback_advances_cursor_without_human_comments(db):
|
||||
assert db.get_task(task.id).state == TaskState.HUMAN_REVIEW_READY # type: ignore[union-attr]
|
||||
|
||||
|
||||
def test_scan_pull_request_feedback_queues_task_for_inline_review_comment(db):
|
||||
task = seed_task(db)
|
||||
transition_to_human_review_ready(db, task.id, pr_number=5, branch_name="agent/issue-1-ready-issue")
|
||||
db.clear_pr_feedback_pending(task.id, last_seen_comment_id=2)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "agent-bot"})
|
||||
if request.url.path == "/api/v1/repos/acme/service/pulls/5":
|
||||
return httpx.Response(200, json={"number": 5, "state": "open", "merged": False})
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments":
|
||||
return httpx.Response(200, json=[])
|
||||
if request.url.path == "/api/v1/repos/acme/service/pulls/5/reviews":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{
|
||||
"id": 9,
|
||||
"body": "",
|
||||
"state": "REQUEST_CHANGES",
|
||||
"user": {"login": "alice"},
|
||||
}
|
||||
],
|
||||
)
|
||||
if request.url.path == "/api/v1/repos/acme/service/pulls/5/reviews/9/comments":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{
|
||||
"id": 31,
|
||||
"body": "This branch misses the cleanup case.",
|
||||
"path": "src/service.py",
|
||||
"position": 42,
|
||||
"user": {"login": "alice"},
|
||||
}
|
||||
],
|
||||
)
|
||||
return httpx.Response(404)
|
||||
|
||||
queued = scan_pull_request_feedback(db, make_client(handler))
|
||||
cursors = db.get_pr_feedback_cursors(task.id)
|
||||
|
||||
assert queued == 1
|
||||
assert db.has_pending_pr_feedback(task.id)
|
||||
assert cursors.last_seen_comment_id == 2
|
||||
assert cursors.last_seen_review_id == 0
|
||||
assert cursors.last_seen_review_comment_id == 0
|
||||
|
||||
|
||||
def test_run_task_with_pending_pr_feedback_updates_existing_pr(db, tmp_path):
|
||||
config = make_config(tmp_path)
|
||||
task = seed_task(db)
|
||||
@@ -360,7 +413,7 @@ def test_run_task_with_pending_pr_feedback_updates_existing_pr(db, tmp_path):
|
||||
db.conn.execute("UPDATE tasks SET workspace_path = ? WHERE id = ?", (str(workspace_path), task.id))
|
||||
db.conn.commit()
|
||||
requests: list[tuple[str, str, dict]] = []
|
||||
next_comment_id = 3
|
||||
next_comment_id = 4
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
nonlocal next_comment_id
|
||||
@@ -400,9 +453,127 @@ def test_run_task_with_pending_pr_feedback_updates_existing_pr(db, tmp_path):
|
||||
assert workspace_manager.commit_message == "agent: address PR #5 feedback for issue #1"
|
||||
assert not any(path == "/api/v1/repos/acme/service/pulls" for _, path, _ in requests)
|
||||
assert [run["role"] for run in db.list_agent_runs(task.id)] == ["implementer_pr_feedback", "reviewer"]
|
||||
assert db.get_pr_feedback_cursor(task.id) == 6
|
||||
assert db.get_pr_feedback_cursor(task.id) == 3
|
||||
assert not db.has_pending_pr_feedback(task.id)
|
||||
|
||||
def rescan_handler(request: httpx.Request) -> httpx.Response:
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "agent-bot"})
|
||||
if request.url.path == "/api/v1/repos/acme/service/pulls/5":
|
||||
return httpx.Response(200, json={"number": 5, "state": "open", "merged": False})
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{"id": 3, "body": "Please add a regression test.", "user": {"login": "alice"}},
|
||||
{"id": 4, "body": "Also cover the cleanup case.", "user": {"login": "alice"}},
|
||||
{"id": 5, "body": "processed", "user": {"login": "agent-bot"}},
|
||||
{"id": 6, "body": "review report", "user": {"login": "agent-bot"}},
|
||||
{"id": 7, "body": "summary", "user": {"login": "agent-bot"}},
|
||||
],
|
||||
)
|
||||
return httpx.Response(404)
|
||||
|
||||
queued = scan_pull_request_feedback(db, make_client(rescan_handler))
|
||||
|
||||
assert queued == 1
|
||||
assert db.has_pending_pr_feedback(task.id)
|
||||
assert db.get_pr_feedback_cursor(task.id) == 3
|
||||
|
||||
|
||||
def test_run_task_with_pending_pr_feedback_allows_no_code_changes(db, tmp_path):
|
||||
config = make_config(tmp_path)
|
||||
task = seed_task(db)
|
||||
workspace_path = tmp_path / "work" / "existing"
|
||||
transition_to_human_review_ready(db, task.id, pr_number=5, branch_name="agent/issue-1-ready-issue")
|
||||
db.transition(task.id, TaskState.DISCOVERED, clear_lease=True)
|
||||
db.clear_pr_feedback_pending(task.id, last_seen_comment_id=2)
|
||||
db.mark_pr_feedback_pending(task.id)
|
||||
db.conn.execute("UPDATE tasks SET workspace_path = ? WHERE id = ?", (str(workspace_path), task.id))
|
||||
db.conn.commit()
|
||||
requests: list[tuple[str, str, dict]] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
payload = json.loads(request.content.decode() or "{}")
|
||||
requests.append((request.method, request.url.path, payload))
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "agent-bot"})
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments" and request.method == "GET":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{"id": 1, "body": "review report", "user": {"login": "agent-bot"}},
|
||||
{"id": 2, "body": "summary", "user": {"login": "agent-bot"}},
|
||||
{"id": 3, "body": "Can you confirm why this is enough?", "user": {"login": "alice"}},
|
||||
],
|
||||
)
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments" and request.method == "POST":
|
||||
return httpx.Response(201, json={"id": 4, "body": payload["body"], "user": {"login": "agent-bot"}})
|
||||
return httpx.Response(404)
|
||||
|
||||
workspace_manager = FakeWorkspaceManager(tmp_path / "work", diff=False)
|
||||
finished = TaskRunner(
|
||||
db=db,
|
||||
config=config,
|
||||
gitea=make_client(handler),
|
||||
workspace_manager=workspace_manager,
|
||||
command_runner=FakeRunner(),
|
||||
worker_id="worker",
|
||||
).run_once()
|
||||
|
||||
assert finished is not None
|
||||
assert finished.state == TaskState.HUMAN_REVIEW_READY
|
||||
assert workspace_manager.pushed == []
|
||||
assert not hasattr(workspace_manager, "commit_message")
|
||||
assert [run["role"] for run in db.list_agent_runs(task.id)] == ["implementer_pr_feedback"]
|
||||
assert db.get_pr_feedback_cursor(task.id) == 3
|
||||
assert not db.has_pending_pr_feedback(task.id)
|
||||
|
||||
|
||||
def test_success_cleanup_does_not_block_later_pr_feedback(db, tmp_path):
|
||||
config = make_config(
|
||||
tmp_path,
|
||||
workspace={"root": tmp_path / "workspaces", "cleanup_on_success": True},
|
||||
)
|
||||
task = seed_task(db)
|
||||
workspace_path = tmp_path / "work" / "existing"
|
||||
transition_to_human_review_ready(db, task.id, pr_number=5, branch_name="agent/issue-1-ready-issue")
|
||||
db.transition(task.id, TaskState.DISCOVERED, clear_lease=True)
|
||||
db.clear_pr_feedback_pending(task.id, last_seen_comment_id=2)
|
||||
db.mark_pr_feedback_pending(task.id)
|
||||
db.conn.execute("UPDATE tasks SET workspace_path = ? WHERE id = ?", (str(workspace_path), task.id))
|
||||
db.conn.commit()
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
payload = json.loads(request.content.decode() or "{}")
|
||||
if request.url.path == "/api/v1/user":
|
||||
return httpx.Response(200, json={"login": "agent-bot"})
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments" and request.method == "GET":
|
||||
return httpx.Response(
|
||||
200,
|
||||
json=[
|
||||
{"id": 3, "body": "Please address the review.", "user": {"login": "alice"}},
|
||||
],
|
||||
)
|
||||
if request.url.path == "/api/v1/repos/acme/service/issues/5/comments" and request.method == "POST":
|
||||
return httpx.Response(201, json={"id": 4, "body": payload["body"], "user": {"login": "agent-bot"}})
|
||||
return httpx.Response(404)
|
||||
|
||||
workspace_manager = FakeWorkspaceManager(tmp_path / "work")
|
||||
finished = TaskRunner(
|
||||
db=db,
|
||||
config=config,
|
||||
gitea=make_client(handler),
|
||||
workspace_manager=workspace_manager,
|
||||
command_runner=FakeRunner(),
|
||||
worker_id="worker",
|
||||
).run_once()
|
||||
|
||||
assert finished is not None
|
||||
assert finished.state == TaskState.HUMAN_REVIEW_READY
|
||||
assert workspace_manager.resumed == [("agent/issue-1-ready-issue", workspace_path)]
|
||||
assert workspace_manager.cleaned == [workspace_path]
|
||||
|
||||
|
||||
def test_close_issues_for_merged_pull_requests_closes_linked_issue(db):
|
||||
repo = db.upsert_repository(
|
||||
|
||||
Reference in New Issue
Block a user