fix(ci): edit the mock-LLM E2E PR comment in place (#16591)

This commit is contained in:
Vasco Schiavo
2026-08-23 13:57:08 +00:00
committed by GitHub
parent 10d2285dd0
commit 19c6b40944
2 changed files with 89 additions and 13 deletions
+57 -1
View File
@@ -1,10 +1,11 @@
// @vitest-environment node // @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 { renderReport } from "../../tests/e2e/mock-llm/scripts/render-mock-llm-report.mjs";
import { import {
buildCommentBody, buildCommentBody,
findMatchingJobComments, findMatchingJobComments,
upsertJobComment,
} from "../../tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs"; } from "../../tests/e2e/mock-llm/scripts/upsert-pr-comment.mjs";
const MOCK_MARKER = "<!-- agent-canvas-mock-llm-e2e-report -->"; const MOCK_MARKER = "<!-- agent-canvas-mock-llm-e2e-report -->";
@@ -86,4 +87,59 @@ describe("mock-LLM E2E reporting", () => {
1, 2, 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();
});
}); });
@@ -201,7 +201,7 @@ export function findMatchingJobComments(comments, options) {
return comments.filter((comment) => isMatchingJobComment(comment, options)); return comments.filter((comment) => isMatchingJobComment(comment, options));
} }
export async function replaceJobComment({ export async function upsertJobComment({
repo, repo,
issueNumber, issueNumber,
token, token,
@@ -210,24 +210,41 @@ export async function replaceJobComment({
legacyTitle = "", legacyTitle = "",
}) { }) {
const comments = await listIssueComments(repo, issueNumber, token); 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( await githubRequest(
"DELETE", "DELETE",
`/repos/${repo}/issues/comments/${comment.id}`, `/repos/${repo}/issues/comments/${duplicate.id}`,
token, 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( const created = await githubRequest(
"POST", "POST",
`/repos/${repo}/issues/${issueNumber}/comments`, `/repos/${repo}/issues/${issueNumber}/comments`,
token, token,
{ body: buildCommentBody(body, marker) }, { body: commentBody },
); );
return { deleted: existing.length, created }; return { updated: false, deleted: duplicates.length, comment: created };
} }
async function main() { async function main() {
@@ -258,7 +275,7 @@ async function main() {
); );
const body = readFileSync(bodyFile, "utf8"); const body = readFileSync(bodyFile, "utf8");
const result = await replaceJobComment({ const result = await upsertJobComment({
repo, repo,
issueNumber: validatedIssueNumber, issueNumber: validatedIssueNumber,
token, token,
@@ -267,11 +284,14 @@ async function main() {
legacyTitle: args.legacy_title ?? "", legacyTitle: args.legacy_title ?? "",
}); });
console.log( const action = result.updated ? "Updated" : "Created";
`Deleted ${result.deleted} existing PR comment${ const cleanup =
result.deleted === 1 ? "" : "s" result.deleted === 0
}; created PR comment ${result.created.id}.`, ? ""
); : ` Deleted ${result.deleted} duplicate PR comment${
result.deleted === 1 ? "" : "s"
}.`;
console.log(`${action} PR comment ${result.comment.id}.${cleanup}`);
} }
if ( if (