diff --git a/__tests__/e2e/mock-llm-reporting.test.ts b/__tests__/e2e/mock-llm-reporting.test.ts index 4fc9466745..04a4b4adbc 100644 --- a/__tests__/e2e/mock-llm-reporting.test.ts +++ b/__tests__/e2e/mock-llm-reporting.test.ts @@ -1,10 +1,11 @@ // @vitest-environment node -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { renderReport } from "../../tests/e2e/mock-llm/scripts/render-mock-llm-report.mjs"; import { buildCommentBody, findMatchingJobComments, + upsertJobComment, } from "../../tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs"; const MOCK_MARKER = ""; @@ -86,4 +87,59 @@ describe("mock-LLM E2E reporting", () => { 1, 2, ]); }); + + it("edits the existing report in place instead of reposting it", async () => { + const requests: { method: string; path: string }[] = []; + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockImplementation(async (url, init) => { + const method = init?.method ?? "GET"; + const path = + new URL(String(url)).pathname + new URL(String(url)).search; + requests.push({ method, path }); + + if (method === "GET") { + return new Response( + JSON.stringify( + path.includes("page=1") + ? [ + { id: 11, body: "unrelated", user: { type: "User" } }, + { + id: 22, + body: `${MOCK_MARKER}\n## ✅ Mock-LLM E2E Tests`, + user: { login: "github-actions[bot]", type: "Bot" }, + }, + ] + : [], + ), + { status: 200 }, + ); + } + return new Response(JSON.stringify({ id: 22 }), { status: 200 }); + }); + + const result = await upsertJobComment({ + repo: "OpenHands/OpenHands", + issueNumber: "16521", + token: "t", + body: "## ✅ Mock-LLM E2E Tests\n\n**62/62 passed**", + marker: MOCK_MARKER, + legacyTitle: "Mock-LLM E2E Tests", + }); + + expect(result).toMatchObject({ updated: true, deleted: 0 }); + expect(requests.filter((r) => r.method === "PATCH")).toEqual([ + { + method: "PATCH", + path: "/repos/OpenHands/OpenHands/issues/comments/22", + }, + ]); + expect(requests.some((r) => r.method === "POST")).toBe(false); + expect(requests.some((r) => r.method === "DELETE")).toBe(false); + fetchMock.mockRestore(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); }); diff --git a/tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs b/tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs index 77ee321ee3..aa690e0ce5 100644 --- a/tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs +++ b/tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs @@ -201,7 +201,7 @@ export function findMatchingJobComments(comments, options) { return comments.filter((comment) => isMatchingJobComment(comment, options)); } -export async function replaceJobComment({ +export async function upsertJobComment({ repo, issueNumber, token, @@ -210,24 +210,41 @@ export async function replaceJobComment({ legacyTitle = "", }) { const comments = await listIssueComments(repo, issueNumber, token); - const existing = findMatchingJobComments(comments, { marker, legacyTitle }); + const [existing, ...duplicates] = findMatchingJobComments(comments, { + marker, + legacyTitle, + }); + const commentBody = buildCommentBody(body, marker); - for (const comment of existing) { + // Edit the oldest report in place so the comment keeps its position in the + // thread and does not notify subscribers on every run. Anything beyond it is + // a leftover duplicate from an earlier run. + for (const duplicate of duplicates) { await githubRequest( "DELETE", - `/repos/${repo}/issues/comments/${comment.id}`, + `/repos/${repo}/issues/comments/${duplicate.id}`, token, ); } + if (existing) { + const updated = await githubRequest( + "PATCH", + `/repos/${repo}/issues/comments/${existing.id}`, + token, + { body: commentBody }, + ); + return { updated: true, deleted: duplicates.length, comment: updated }; + } + const created = await githubRequest( "POST", `/repos/${repo}/issues/${issueNumber}/comments`, token, - { body: buildCommentBody(body, marker) }, + { body: commentBody }, ); - return { deleted: existing.length, created }; + return { updated: false, deleted: duplicates.length, comment: created }; } async function main() { @@ -258,7 +275,7 @@ async function main() { ); const body = readFileSync(bodyFile, "utf8"); - const result = await replaceJobComment({ + const result = await upsertJobComment({ repo, issueNumber: validatedIssueNumber, token, @@ -267,11 +284,14 @@ async function main() { legacyTitle: args.legacy_title ?? "", }); - console.log( - `Deleted ${result.deleted} existing PR comment${ - result.deleted === 1 ? "" : "s" - }; created PR comment ${result.created.id}.`, - ); + const action = result.updated ? "Updated" : "Created"; + const cleanup = + result.deleted === 0 + ? "" + : ` Deleted ${result.deleted} duplicate PR comment${ + result.deleted === 1 ? "" : "s" + }.`; + console.log(`${action} PR comment ${result.comment.id}.${cleanup}`); } if (