- package.json - package-lock.json - .gitignore - playwright.config.ts - e2e/fixtures.ts - e2e/smoke.spec.ts - tests/svg-image.test.tsx GSD context: - Milestone: M001 - Merge upstream v0.4.0 into the Gitea fork - Slice: S02 - Verify combined application behavior - Task: T01 - Added sandboxed, isolated Chromium first-run coverage with stateful synthetic API fixtures and fail-closed network boundaries. GSD-Task: S02/T01
75 lines
3.8 KiB
TypeScript
75 lines
3.8 KiB
TypeScript
import { test, expect, syntheticModel } from "./fixtures";
|
|
|
|
test("first run configures all model slots and opens settings without sending the draft", async ({ page, harness }) => {
|
|
await page.goto("/");
|
|
const draft = page.getByRole("textbox", { name: "What do you want to learn?", exact: true });
|
|
await expect(page.getByTestId("setup-cta-keys")).toBeVisible();
|
|
await draft.fill("Synthetic learning topic — do not send");
|
|
await page.getByRole("button", { name: "Start session", exact: true }).click();
|
|
await expect(page.getByTestId("setup-stage-keys")).toBeVisible();
|
|
await expect(page.getByTestId("setup-stage-pickers")).toHaveCount(0);
|
|
await page.getByTestId("setup-key-provider").click();
|
|
await page.getByTestId("provider-option-openai").click();
|
|
await page.getByTestId("setup-key-input").fill("synthetic-smoke-key-not-a-credential");
|
|
await page.getByTestId("setup-key-save").click();
|
|
await expect(page.getByTestId("setup-stage-pickers")).toBeVisible();
|
|
await expect(page.getByTestId("setup-save")).toBeDisabled();
|
|
for (const slot of ["strong", "cheap", "vision"]) {
|
|
await page.getByTestId(`setup-trigger-${slot}`).click();
|
|
await page.getByTestId(`model-option-${syntheticModel.id}`).click();
|
|
}
|
|
await expect(page.getByTestId("setup-save")).toBeEnabled();
|
|
await page.getByTestId("setup-save").click();
|
|
await expect(page.getByTestId("setup-dialog")).toHaveCount(0);
|
|
await expect(page.getByTestId("setup-cta")).toHaveCount(0);
|
|
await expect(draft).toHaveText("Synthetic learning topic — do not send");
|
|
const pin = { model: { provider: "openai", id: syntheticModel.id }, thinkingLevel: "off" };
|
|
expect(harness.writes).toEqual([
|
|
{ path: "/api/keys/OPENAI_API_KEY", body: { value: "synthetic-smoke-key-not-a-credential" } },
|
|
{ path: "/api/settings/models", body: { strong: pin, cheap: pin, vision: pin } },
|
|
]);
|
|
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
|
await expect(page.getByTestId("settings-dialog")).toBeVisible();
|
|
await expect(page.getByTestId("settings-nav-keys")).toBeVisible();
|
|
await page.getByTestId("settings-close").click();
|
|
await expect(draft).toHaveText("Synthetic learning topic — do not send");
|
|
expect(harness.generation).toEqual([]);
|
|
await expect(page).toHaveURL("http://127.0.0.1:3005/");
|
|
});
|
|
|
|
test("catalog failure is visible and cannot save incomplete slots", async ({ page, harness }) => {
|
|
harness.catalogFailure = true;
|
|
await page.goto("/");
|
|
await page.getByTestId("setup-cta").click();
|
|
await expect(page.getByTestId("setup-catalog-error")).toContainText("Model catalog could not be loaded. Check the connection and retry.");
|
|
await expect(page.getByTestId("setup-stage-keys")).toHaveCount(0);
|
|
await expect(page.getByTestId("setup-save")).toHaveCount(0);
|
|
harness.catalogFailure = false;
|
|
await page.getByTestId("setup-retry").click();
|
|
await expect(page.getByTestId("setup-stage-keys")).toBeVisible();
|
|
await expect(page.getByTestId("setup-catalog-error")).toHaveCount(0);
|
|
expect(harness.writes).toEqual([]);
|
|
});
|
|
|
|
test("network boundary rejects external requests and unknown API writes", async ({ page, harness }) => {
|
|
await page.goto("/");
|
|
await page.evaluate(async () => {
|
|
await Promise.all([
|
|
fetch("https://example.invalid/must-not-leave-host").catch(() => undefined),
|
|
fetch("/api/unexpected-write", { method: "POST", body: "synthetic" }).catch(() => undefined),
|
|
new Promise<void>((resolve) => {
|
|
const socket = new WebSocket("wss://example.invalid/must-not-connect");
|
|
socket.onclose = () => resolve();
|
|
socket.onerror = () => resolve();
|
|
}),
|
|
]);
|
|
});
|
|
expect(harness.blocked.sort()).toEqual([
|
|
"GET https://example.invalid/must-not-leave-host",
|
|
"POST http://127.0.0.1:3005/api/unexpected-write",
|
|
"WS wss://example.invalid/must-not-connect",
|
|
]);
|
|
// Only these deliberately asserted denials are consumed; every other denial fails teardown.
|
|
harness.blocked.length = 0;
|
|
});
|