repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/strata/__e2e__/forge.spec.ts
Rawimport { expect, test } from "@playwright/test";
import { startReviewServer } from "../server.js";
import type { Feedback, ForgeReview, Review, ReviewServer } from "../types.js";
const mergeBaseSha = "0123456789abcdef0123456789abcdef01234567";
const baseTipSha = "1111111111111111111111111111111111111111";
const headSha = "2222222222222222222222222222222222222222";
const ancestorSha = "4444444444444444444444444444444444444444";
function makeForge(): ForgeReview {
return {
provider: "github",
providerLabel: "GitHub",
repository: "acme/strata",
number: 42,
url: "https://github.com/acme/strata/pull/42",
title: "Reject anonymous access at the request boundary",
body: "Authentication now fails closed before a request reaches the account service.\n\n<img src=x onerror=alert('description')>",
author: "octo-reviewer",
baseRef: "main",
baseSha: baseTipSha,
headRef: "auth-boundary",
headSha,
checkoutBranch: "strata/pr-42-auth-boundary",
comments: [
{
id: "discussion-hostile",
url: "javascript:alert('discussion')",
author: "maintainer",
body: "General discussion: <img src=x onerror=alert('comment')>",
kind: "discussion",
outdated: false,
},
{
id: "review-summary",
url: "http://github.example/review/7",
author: "security-reviewer",
body: "Review summary: keep the rejection before account lookup.",
kind: "review",
outdated: false,
},
{
id: "inline-current-new",
url: "https://github.com/acme/strata/pull/42#discussion_r100",
author: "security-reviewer",
body: "This exception keeps missing credentials out of the service layer.",
kind: "inline",
path: "src/authenticate.ts",
side: "new",
line: 14,
commit: headSha,
outdated: false,
},
{
id: "inline-current-old",
url: "https://github.com/acme/strata/pull/42#discussion_r101",
author: "maintainer",
body: "The guest fallback was the original privilege leak.",
kind: "inline",
path: "src/authenticate.ts",
side: "old",
line: 14,
commit: headSha,
outdated: false,
replyTo: "discussion_r88",
},
{
id: "inline-outdated-missing",
url: "https://github.com/acme/strata/pull/42#discussion_r102",
author: "former-reviewer",
body: "This referred to a line removed by the latest push.",
kind: "inline",
path: "src/removed.ts",
side: "new",
line: 900,
commit: "3333333333333333333333333333333333333333",
outdated: true,
},
{
id: "inline-ancestor-current",
url: "https://github.com/acme/strata/pull/42#discussion_r103",
author: "former-reviewer",
body: "This was authored earlier and remains mapped to the current line.",
kind: "inline",
path: "src/authenticate.ts",
side: "new",
line: 14,
commit: ancestorSha,
outdated: false,
},
{
id: "inline-outdated-current-line",
url: "https://github.com/acme/strata/pull/42#discussion_r104",
author: "former-reviewer",
body: "The adapter marks this otherwise valid coordinate as outdated.",
kind: "inline",
path: "src/authenticate.ts",
side: "new",
line: 14,
commit: ancestorSha,
outdated: true,
},
{
id: "inline-missing-line",
url: "file:///tmp/not-a-source-link",
author: "maintainer",
body: "The head is current, but this anchor is absent from the captured diff.",
kind: "inline",
path: "src/authenticate.ts",
side: "new",
line: 999,
commit: headSha,
outdated: false,
},
],
};
}
function makeReview(forge: ForgeReview | null = makeForge()): Review {
return {
snapshot: {
id: "same-code-snapshot",
repoRoot: "/safe/forge-fixture",
source: { kind: "base", ref: baseTipSha },
base: mergeBaseSha,
head: headSha,
hunks: [
{
id: "auth-boundary",
path: "src/authenticate.ts",
header: "@@ -11,8 +11,9 @@ export async function authenticate",
lines: [
{
kind: "context",
text: "export async function authenticate(request: Request) {",
oldLine: 11,
newLine: 11,
},
{
kind: "context",
text: ' const token = request.headers.get("authorization");',
oldLine: 12,
newLine: 12,
},
{ kind: "context", text: "", oldLine: 13, newLine: 13 },
{
kind: "delete",
text: " if (!token) return { role: 'guest' };",
oldLine: 14,
},
{
kind: "add",
text: " if (!token) throw new AuthenticationError();",
newLine: 14,
},
{
kind: "add",
text: "",
newLine: 15,
},
{
kind: "context",
text: " const account = await accounts.findByToken(token);",
oldLine: 15,
newLine: 16,
},
{
kind: "context",
text: " return { role: account.role, accountId: account.id };",
oldLine: 16,
newLine: 17,
},
{ kind: "context", text: "}", oldLine: 17, newLine: 18 },
],
},
{
id: "auth-test",
path: "test/authenticate.test.ts",
header: "@@ -22,2 +22,7 @@ test missing credentials",
lines: [
{
kind: "context",
text: 'test("rejects requests without credentials", async () => {',
oldLine: 22,
newLine: 22,
},
{
kind: "add",
text: " await expect(authenticate(requestWithoutToken))",
newLine: 23,
},
{
kind: "add",
text: " .rejects.toBeInstanceOf(AuthenticationError);",
newLine: 24,
},
{
kind: "add",
text: " expect(accounts.findByToken).not.toHaveBeenCalled();",
newLine: 25,
},
{ kind: "context", text: "});", oldLine: 23, newLine: 26 },
],
},
],
skipped: [],
},
plan: {
summary:
"Reject missing credentials before account lookup and prove the boundary.",
cohorts: [
{
title: "Authentication",
layers: [
{
id: "request-boundary",
title: "Request boundary",
summary:
"Review the rejection path and its regression test together.",
hunks: [
{
id: "auth-boundary",
summary: "Missing credentials now fail closed.",
},
{
id: "auth-test",
summary:
"The service lookup remains untouched after rejection.",
},
],
},
],
},
],
},
draft: { reviewed: [], findings: [], notes: "" },
...(forge ? { forge } : {}),
};
}
let server: ReviewServer | undefined;
test.afterEach(() => {
server?.close();
server = undefined;
});
test("renders current forge anchors beside code and keeps external discussion separate", async ({
page,
}, testInfo) => {
const submitted: Feedback[] = [];
const externalRequests: string[] = [];
page.on("request", (request) => {
if (!request.url().startsWith("http://127.0.0.1:")) {
externalRequests.push(request.url());
}
});
server = await startReviewServer({
review: makeReview(),
isCurrent: async () => true,
refresh: async () => makeReview(),
ask: async () => "answer",
save: () => {},
submit: async (feedback) => {
submitted.push(structuredClone(feedback));
},
});
await page.setViewportSize({ width: 1440, height: 900 });
await page.emulateMedia({ colorScheme: "light" });
await page.goto(server.url);
await expect(page.locator("#forge-region")).toBeVisible();
await expect(page.locator("#forge-source")).toHaveText("GitHub");
await expect(page.locator("#forge-source svg")).toHaveCount(1);
await expect(page.locator("#forge-heading")).toHaveText(
"Reject anonymous access at the request boundary",
);
await expect(page.locator("#forge-refs")).toContainText("Base tip");
await expect(page.locator("#forge-refs")).toContainText(baseTipSha);
await expect(page.locator("#forge-refs")).toContainText("Merge base");
await expect(page.locator("#forge-refs")).toContainText(mergeBaseSha);
await expect(page.locator("#forge-refs")).toContainText(headSha);
await expect(page.locator("#forge-checkout")).toContainText(
"Snapshot is bound to the exact PR base tip and head commit",
);
await expect(page.locator("#forge-link")).toHaveAttribute(
"href",
"https://github.com/acme/strata/pull/42",
);
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-current-new"]',
),
).toContainText("src/authenticate.ts · new:14");
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-current-old"]',
),
).toBeVisible();
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-ancestor-current"]',
),
).toContainText("authored earlier");
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-outdated-current-line"]',
),
).toHaveCount(0);
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-outdated-missing"]',
),
).toHaveCount(0);
await page.keyboard.press("g");
await page.keyboard.press("d");
await expect(page.getByRole("tab", { name: "Details" })).toBeFocused();
await page.keyboard.press("Tab");
await expect(page.locator("#forge-link")).toBeFocused();
await page.locator("#forge-discussions").evaluate((element) => {
(element as HTMLDetailsElement).open = true;
});
await expect(
page.locator(
'[data-forge-comment-id="inline-outdated-missing"] .forge-comment-state',
),
).toHaveText("Outdated · Unmapped");
await expect(
page.locator(
'#forge-comments [data-forge-comment-id="inline-ancestor-current"] .forge-comment-state',
),
).toHaveText("Current anchor");
await expect(
page.locator(
'[data-forge-comment-id="inline-outdated-current-line"] .forge-comment-state',
),
).toHaveText("Outdated");
await expect(
page.locator(
'[data-forge-comment-id="inline-missing-line"] .forge-comment-state',
),
).toHaveText("Unmapped");
await expect(page.locator("#forge-comments")).toContainText(
"<img src=x onerror=alert('comment')>",
);
await expect(page.locator("img")).toHaveCount(0);
await expect(
page.locator('[data-forge-comment-id="discussion-hostile"] a'),
).toHaveCount(0);
await expect(
page.locator('[data-forge-comment-id="review-summary"] a'),
).toHaveCount(0);
await expect(
page.locator('[data-forge-comment-id="inline-missing-line"] a'),
).toHaveCount(0);
for (const href of await page
.locator("a[href]")
.evaluateAll((links) =>
links.map((link) => (link as HTMLAnchorElement).href),
)) {
expect(href.startsWith("https://")).toBe(true);
}
await page.locator("#forge-discussions").evaluate((element) => {
(element as HTMLDetailsElement).open = false;
});
await page.screenshot({
path: testInfo.outputPath("forge-review-desktop-light.png"),
});
await page.emulateMedia({ colorScheme: "dark" });
await page.screenshot({
path: testInfo.outputPath("forge-review-desktop-dark.png"),
});
await page.getByRole("tab", { name: "Feedback" }).click();
await expect(page.locator("#findings")).toHaveText("No line comments.");
await page
.getByRole("button", {
name: "Comment on new line 14 in src/authenticate.ts",
})
.click();
await page.locator("#comment-text").fill("Keep this rejection covered.");
await page.getByRole("button", { name: "Add comment" }).click();
await expect(page.locator(".finding")).toHaveCount(1);
await expect(page.locator(".finding")).toContainText(
"Keep this rejection covered.",
);
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Send feedback to Pi" }).click();
await expect(page.locator("#status")).toHaveText("Feedback sent to Pi");
expect(submitted).toHaveLength(1);
expect(submitted[0].findings).toHaveLength(1);
expect(submitted[0].findings[0].text).toBe("Keep this rejection covered.");
expect(JSON.stringify(submitted[0])).not.toContain("privilege leak");
expect(externalRequests).toEqual([]);
});
for (const mismatch of [
{
name: "non-base snapshot source",
apply(review: Review) {
review.snapshot.source = { kind: "working" };
},
},
{
name: "wrong base tip ref",
apply(review: Review) {
review.snapshot.source.ref = "5555555555555555555555555555555555555555";
},
},
{
name: "wrong head commit",
apply(review: Review) {
review.snapshot.head = "6666666666666666666666666666666666666666";
},
},
]) {
test(`does not map forge anchors for ${mismatch.name}`, async ({ page }) => {
const review = makeReview();
mismatch.apply(review);
server = await startReviewServer({
review,
isCurrent: async () => true,
refresh: async () => review,
ask: async () => "answer",
save: () => {},
submit: async () => {},
});
await page.goto(server.url);
await expect(page.locator("#forge-checkout")).toContainText(
"Snapshot is not bound to the exact PR base tip and head commit",
);
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-current-new"]',
),
).toHaveCount(0);
await expect(
page.locator(
'.imported-comments [data-forge-comment-id="inline-ancestor-current"]',
),
).toHaveCount(0);
await expect(
page.locator(
'[data-forge-comment-id="inline-current-new"] .forge-comment-state',
),
).toHaveText("Outdated");
});
}
test("clears forge source and commit details when refresh removes forge context", async ({
page,
}) => {
server = await startReviewServer({
review: makeReview(),
isCurrent: async () => true,
refresh: async () => makeReview(null),
ask: async () => "answer",
save: () => {},
submit: async () => {},
});
await page.goto(server.url);
await expect(page.locator("#forge-refs .forge-ref")).toHaveCount(3);
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Refresh" }).click();
await expect(page.locator("#status")).toHaveText("Review refreshed");
await expect(page.locator("#forge-region")).toBeHidden();
await expect(page.locator("#forge-source")).toBeEmpty();
await expect(page.locator("#forge-link")).toBeEmpty();
await expect(page.locator("#forge-link")).not.toHaveAttribute("href", /./);
await expect(page.locator("#forge-refs")).toBeEmpty();
await expect(page.locator("#forge-checkout")).toBeEmpty();
await expect(page.locator("#forge-comments")).toBeEmpty();
});
test("keeps PR details usable on mobile and falls back for another provider", async ({
page,
}, testInfo) => {
const forge = makeForge();
forge.provider = "smallforge";
forge.providerLabel = "Small Forge";
forge.url = "ftp://forge.example/acme/strata/reviews/42";
server = await startReviewServer({
review: makeReview(forge),
isCurrent: async () => true,
refresh: async () => makeReview(forge),
ask: async () => "answer",
save: () => {},
submit: async () => {},
});
await page.setViewportSize({ width: 390, height: 780 });
await page.emulateMedia({ colorScheme: "dark" });
await page.goto(server.url);
await expect(page.locator("#detail-region")).toBeHidden();
await page.getByRole("button", { name: "Show review drawer" }).click();
await expect(page.locator("#forge-source")).toHaveText("Small Forge");
await expect(page.locator("#forge-source svg")).toHaveCount(0);
await expect(page.locator("#forge-link")).toBeHidden();
await expect(page.locator("#forge-region")).toBeInViewport();
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
),
).toBe(true);
await page.screenshot({
path: testInfo.outputPath("forge-review-mobile-dark.png"),
});
});
test("preserves the ordinary browser review when forge metadata is absent", async ({
page,
}) => {
server = await startReviewServer({
review: makeReview(null),
isCurrent: async () => true,
refresh: async () => makeReview(null),
ask: async () => "answer",
save: () => {},
submit: async () => {},
});
await page.goto(server.url);
await expect(page.locator("#forge-region")).toBeHidden();
await expect(page.locator(".imported-comments-row")).toHaveCount(0);
await expect(page.locator("#diff-heading")).toHaveText("All changes");
await expect(page.locator(".hunk")).toHaveCount(2);
await expect(page.locator("#status")).toHaveText("Review ready");
});