repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/strata/__tests__/server.test.ts
Rawimport { request as httpRequest } from "node:http";
import { afterEach, describe, expect, it, vi } from "vitest";
import { startReviewServer } from "../server.js";
import type {
AskRequest,
Draft,
Review,
ReviewServer,
ServerOptions,
} from "../types.js";
function makeReview(id = "snapshot-1"): Review {
return {
snapshot: {
id,
repoRoot: "/safe/project",
source: { kind: "working" },
base: "base-id",
head: "head-id",
hunks: [
{
id: "hunk-a",
path: "src/a.ts",
header: "@@ -1,2 +1,2 @@",
lines: [
{ kind: "delete", text: "old", oldLine: 1 },
{ kind: "add", text: "new", newLine: 1 },
{ kind: "context", text: "same", oldLine: 2, newLine: 2 },
],
},
],
skipped: [{ path: "media.bin", reason: "binary" }],
},
plan: {
summary: "One focused change",
cohorts: [
{
title: "Core",
layers: [
{
id: "layer-a",
title: "Behavior",
summary: "Changes behavior",
hunks: [{ id: "hunk-a", summary: "Replace old with new" }],
flow: ["Input", "Output"],
},
],
},
],
},
draft: { reviewed: [], findings: [], notes: "" },
};
}
function options(overrides: Partial<ServerOptions> = {}): ServerOptions {
return {
review: makeReview(),
isCurrent: vi.fn(async () => true),
refresh: vi.fn(async () => makeReview("snapshot-2")),
ask: vi.fn(async (request) => `Answer: ${request.question}`),
save: vi.fn(),
submit: vi.fn(async () => {}),
...overrides,
};
}
type Session = {
server: ReviewServer;
origin: string;
cookie: string;
revision: number;
snapshotId: string;
};
const servers = new Set<ReviewServer>();
afterEach(async () => {
await Promise.all([...servers].map((server) => server.close()));
servers.clear();
});
async function openSession(serverOptions: ServerOptions): Promise<Session> {
const server = await startReviewServer(serverOptions);
servers.add(server);
return openExisting(server);
}
async function api(
session: Session,
path: string,
body?: unknown,
extraHeaders: Record<string, string> = {},
): Promise<Response> {
const response = await fetch(`${session.origin}${path}`, {
method: body === undefined ? "GET" : "POST",
headers: {
Cookie: session.cookie,
...(body === undefined
? {}
: {
Origin: session.origin,
"Content-Type": "application/json",
...(path === "/api/close"
? {}
: {
"If-Match": String(session.revision),
"X-Strata-Snapshot": session.snapshotId,
}),
}),
...extraHeaders,
},
body: body === undefined ? undefined : JSON.stringify(body),
});
const payload = await response
.clone()
.json()
.catch(() => undefined);
if (payload && Number.isSafeInteger(payload.revision)) {
session.revision = payload.revision;
session.snapshotId = payload.snapshotId;
}
return response;
}
describe("review server HTTP boundary", () => {
it("exchanges its one-time URL token and authenticates pages and assets", async () => {
const server = await startReviewServer(options());
servers.add(server);
const origin = new URL(server.url).origin;
const unauthenticated = await fetch(origin);
expect(unauthenticated.status).toBe(401);
expect(unauthenticated.headers.get("cache-control")).toBe("no-store");
const session = await openExisting(server);
const page = await api(session, "/");
expect(page.status).toBe(200);
expect(page.headers.get("content-security-policy")).toContain(
"script-src 'self'",
);
expect(await page.text()).toContain("STRATA");
const script = await api(session, "/assets/app.js");
expect(script.status).toBe(200);
expect(script.headers.get("content-type")).toContain("text/javascript");
const unauthenticatedAsset = await fetch(`${origin}/assets/style.css`);
expect(unauthenticatedAsset.status).toBe(401);
});
it("rejects forged hosts, origins, and oversized requests", async () => {
const session = await openSession(options());
const forgedHost = await requestStatus(session, "/api/state", {
Host: "localhost.invalid",
Cookie: session.cookie,
});
expect(forgedHost).toBe(400);
const forgedOrigin = await api(
session,
"/api/draft",
{ reviewed: [], findings: [], notes: "" },
{ Origin: "http://localhost.invalid" },
);
expect(forgedOrigin.status).toBe(403);
const oversized = await fetch(`${session.origin}/api/draft`, {
method: "POST",
headers: {
Cookie: session.cookie,
Origin: session.origin,
"Content-Type": "application/json",
},
body: JSON.stringify({ notes: "x".repeat(70_000) }),
});
expect(oversized.status).toBe(413);
});
it("serves state and saves only valid complete drafts", async () => {
const save = vi.fn();
const session = await openSession(options({ save }));
const state = await api(session, "/api/state");
expect(await state.json()).toMatchObject({
revision: 0,
snapshotId: "snapshot-1",
review: { snapshot: { id: "snapshot-1" } },
threads: [],
});
const draft: Draft = {
reviewed: ["hunk-a"],
findings: [
{
id: "finding-a",
hunkId: "hunk-a",
side: "new",
line: 1,
severity: "major",
text: "Please verify this.",
},
],
notes: "Review note",
};
const saved = await api(session, "/api/draft", draft);
expect(saved.status).toBe(200);
expect(await saved.json()).toMatchObject({
revision: 1,
snapshotId: "snapshot-1",
draft,
});
expect(save).toHaveBeenCalledWith(draft);
const invalid = await api(session, "/api/draft", {
...draft,
findings: [{ ...draft.findings[0], line: 99 }],
});
expect(invalid.status).toBe(400);
expect(save).toHaveBeenCalledTimes(1);
const extra = await api(session, "/api/draft", { ...draft, extra: true });
expect(extra.status).toBe(400);
});
it("rejects missing or stale optimistic bindings without overwriting another tab", async () => {
const save = vi.fn();
const session = await openSession(options({ save }));
const secondTab = await openExisting(session.server);
const missing = await fetch(`${session.origin}/api/draft`, {
method: "POST",
headers: {
Cookie: session.cookie,
Origin: session.origin,
"Content-Type": "application/json",
},
body: JSON.stringify({ reviewed: [], findings: [], notes: "missing" }),
});
expect(missing.status).toBe(409);
expect((await missing.json()).code).toBe("stale_client");
const first = await api(session, "/api/draft", {
reviewed: [],
findings: [],
notes: "first tab",
});
expect(first.status).toBe(200);
expect(await first.json()).toMatchObject({
revision: 1,
snapshotId: "snapshot-1",
});
const stale = await api(secondTab, "/api/draft", {
reviewed: [],
findings: [],
notes: "second tab",
});
expect(stale.status).toBe(409);
expect((await stale.json()).code).toBe("stale_client");
expect(save).toHaveBeenCalledTimes(1);
});
it("asks only known layers and returns plain bounded answers", async () => {
const ask = vi.fn(
async (request: AskRequest) => `<b>${request.question}</b>`,
);
const session = await openSession(options({ ask }));
const response = await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: "hunk-a",
question: "Why?",
});
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
answer: "<b>Why?</b>",
revision: 1,
snapshotId: "snapshot-1",
threads: [
{
layerId: "layer-a",
messages: [
{
question: "Why?",
answer: "<b>Why?</b>",
hunkId: "hunk-a",
},
],
},
],
});
expect(ask).toHaveBeenCalledWith(
{
layerId: "layer-a",
hunkId: "hunk-a",
question: "Why?",
history: [],
},
expect.any(AbortSignal),
);
const unknown = await api(session, "/api/ask", {
layerId: "missing",
hunkId: null,
question: "Why?",
});
expect(unknown.status).toBe(400);
const browserHistory = await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: null,
question: "Again?",
history: [],
});
expect(browserHistory.status).toBe(400);
const whitespace = await api(session, "/api/ask", {
layerId: null,
hunkId: null,
question: " \n\t ",
});
expect(whitespace.status).toBe(400);
const oversizedUtf8 = await api(session, "/api/ask", {
layerId: null,
hunkId: null,
question: "é".repeat(3_000),
});
expect(oversizedUtf8.status).toBe(400);
});
it("owns separate layer and All changes histories and rejects invalid focus", async () => {
const review = makeReview();
review.snapshot.hunks.push({
id: "hunk-b",
path: "src/b.ts",
header: "@@ -1 +1 @@",
lines: [{ kind: "add", text: "b", newLine: 1 }],
});
review.plan.cohorts[0]?.layers.push({
id: "layer-b",
title: "Other",
summary: "Other behavior",
hunks: [{ id: "hunk-b", summary: "Adds b" }],
});
const requests: AskRequest[] = [];
const session = await openSession(
options({
review,
ask: async (request) => {
requests.push(request);
return `Answer ${requests.length}`;
},
}),
);
expect(
(
await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: "hunk-b",
question: "Wrong focus",
})
).status,
).toBe(400);
await api(session, "/api/ask", {
layerId: null,
hunkId: "hunk-b",
question: "Whole review",
});
await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: "hunk-a",
question: "Layer question",
});
await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: null,
question: "Follow-up",
});
expect(requests[0]).toMatchObject({ layerId: null, history: [] });
expect(requests[1]).toMatchObject({ layerId: "layer-a", history: [] });
expect(requests[2]?.history).toEqual([
{
question: "Layer question",
answer: "Answer 2",
hunkId: "hunk-a",
},
]);
const state = await (await api(session, "/api/state")).json();
expect(state.threads).toHaveLength(2);
expect(state.threads[0].layerId).toBeNull();
});
it("checks freshness before Ask and again before storing the answer", async () => {
const ask = vi.fn(async () => "Answer");
const staleBefore = await openSession(
options({ isCurrent: vi.fn(async () => false), ask }),
);
const refused = await api(staleBefore, "/api/ask", {
layerId: null,
hunkId: null,
question: "Question",
});
expect(refused.status).toBe(409);
expect(ask).not.toHaveBeenCalled();
const freshness = vi
.fn<() => Promise<boolean>>()
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false);
const staleAfter = await openSession(
options({ isCurrent: freshness, ask }),
);
const discarded = await api(staleAfter, "/api/ask", {
layerId: null,
hunkId: null,
question: "Question",
});
expect(discarded.status).toBe(409);
expect(ask).toHaveBeenCalledOnce();
const state = await (await api(staleAfter, "/api/state")).json();
expect(state).toMatchObject({ revision: 0, threads: [] });
});
it("rejects Ask history growth at 128 completed exchanges without truncation", async () => {
const session = await openSession(options({ ask: async () => "Answer" }));
for (let index = 0; index < 128; index++) {
const response = await api(session, "/api/ask", {
layerId: null,
hunkId: null,
question: `Question ${index}`,
});
expect(response.status).toBe(200);
}
const refused = await api(session, "/api/ask", {
layerId: "layer-a",
hunkId: null,
question: "One too many",
});
expect(refused.status).toBe(409);
expect((await refused.json()).code).toBe("history_limit");
const state = await (await api(session, "/api/state")).json();
expect(state.revision).toBe(128);
expect(state.threads[0].messages).toHaveLength(128);
});
it("busy-rejects overlapping callbacks and aborts outstanding work on close", async () => {
let started!: () => void;
const didStart = new Promise<void>((resolve) => {
started = resolve;
});
let aborted = false;
const ask = vi.fn(
async (_request: AskRequest, signal: AbortSignal) =>
new Promise<string>((_resolve, reject) => {
started();
signal.addEventListener("abort", () => {
aborted = true;
reject(new Error("aborted"));
});
}),
);
const session = await openSession(options({ ask }));
const asking = api(session, "/api/ask", {
layerId: "layer-a",
hunkId: null,
question: "Wait",
}).catch(() => undefined);
await didStart;
const busy = await api(session, "/api/draft", {
reviewed: [],
findings: [],
notes: "queued",
});
expect(busy.status).toBe(409);
await session.server.close();
await asking;
expect(aborted).toBe(true);
});
it("awaits request cleanup before close settles", async () => {
const started = Promise.withResolvers<void>();
const release = Promise.withResolvers<string>();
let signal: AbortSignal | undefined;
let cleaned = false;
const session = await openSession(
options({
ask: async (_request, requestSignal) => {
signal = requestSignal;
started.resolve();
try {
return await release.promise;
} finally {
cleaned = true;
}
},
}),
);
const asking = api(session, "/api/ask", {
layerId: "layer-a",
hunkId: null,
question: "Wait",
}).catch(() => undefined);
try {
await started.promise;
let settled = false;
const closing = session.server.close();
void closing.then(() => {
settled = true;
});
expect(session.server.close()).toBe(closing);
expect(signal?.aborted).toBe(true);
await Promise.resolve();
expect(settled).toBe(false);
expect(cleaned).toBe(false);
release.resolve("late answer");
await closing;
expect(cleaned).toBe(true);
} finally {
release.resolve("cleanup");
await session.server.close();
await asking;
}
});
it("refuses stale submission, preserves its draft, then clears after success", async () => {
const save = vi.fn();
const submit = vi.fn(async () => {});
const isCurrent = vi
.fn<() => Promise<boolean>>()
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);
const session = await openSession(options({ isCurrent, save, submit }));
const draft: Draft = {
reviewed: ["hunk-a"],
findings: [],
notes: "Keep me",
};
const stale = await api(session, "/api/submit", { draft });
expect(stale.status).toBe(409);
expect((await stale.json()).code).toBe("stale");
expect(submit).not.toHaveBeenCalled();
const state = await api(session, "/api/state");
expect((await state.json()).review.draft).toEqual(draft);
const sent = await api(session, "/api/submit", { draft });
expect(sent.status).toBe(200);
expect(submit).toHaveBeenCalledWith({ ...draft, snapshotId: "snapshot-1" });
expect(save).toHaveBeenLastCalledWith({
reviewed: [],
findings: [],
notes: "",
});
});
it("requires draft confirmation, checks freshness, and safely replaces review", async () => {
const isCurrent = vi.fn(async () => false);
const refresh = vi.fn(async () => makeReview("snapshot-2"));
const session = await openSession(
options({
review: {
...makeReview(),
draft: { reviewed: [], findings: [], notes: "draft" },
},
isCurrent,
refresh,
}),
);
const refused = await api(session, "/api/refresh", { confirm: false });
expect(refused.status).toBe(409);
expect((await refused.json()).code).toBe("confirm_required");
expect(isCurrent).toHaveBeenCalledTimes(1);
expect(refresh).not.toHaveBeenCalled();
const accepted = await api(session, "/api/refresh", { confirm: true });
expect(accepted.status).toBe(200);
const payload = await accepted.json();
expect(payload.wasCurrent).toBe(false);
expect(payload.review.snapshot.id).toBe("snapshot-2");
expect(refresh).toHaveBeenCalledWith(expect.any(AbortSignal));
});
it("clears server-owned threads on refresh", async () => {
const session = await openSession(options());
await api(session, "/api/ask", {
layerId: null,
hunkId: "hunk-a",
question: "Before refresh",
});
const refreshed = await api(session, "/api/refresh", { confirm: true });
expect(refreshed.status).toBe(200);
expect((await refreshed.json()).threads).toEqual([]);
expect((await (await api(session, "/api/state")).json()).threads).toEqual(
[],
);
});
it("closes explicitly through the authenticated endpoint", async () => {
const session = await openSession(options());
const response = await api(session, "/api/close", {});
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
closed: true,
revision: 0,
snapshotId: "snapshot-1",
});
});
});
function requestStatus(
session: Session,
path: string,
headers: Record<string, string>,
): Promise<number | undefined> {
return new Promise((resolve, reject) => {
const request = httpRequest(
`${session.origin}${path}`,
{ headers },
(response) => {
response.resume();
response.on("end", () => resolve(response.statusCode));
},
);
request.on("error", reject);
request.end();
});
}
async function openExisting(server: ReviewServer): Promise<Session> {
const exchange = await fetch(server.url, { redirect: "manual" });
const cookie = exchange.headers.get("set-cookie")?.split(";", 1)[0];
if (!cookie) throw new Error("server did not set an authentication cookie");
const origin = new URL(server.url).origin;
const state = await fetch(`${origin}/api/state`, {
headers: { Cookie: cookie },
}).then((response) => response.json());
return {
server,
origin,
cookie,
revision: state.revision,
snapshotId: state.snapshotId,
};
}