Files
sql-server-query-ai/tests-e2e/edge.spec.ts
T
ahmed mohammedandClaude Opus 4.7 2ebfcfb7a6 fix: session cookie over plain http, console onboarding gate, webkit qa coverage
- the session cookie was Secure whenever NODE_ENV=production, so a production build on http://localhost got no cookie back in Safari: every page after signup looked signed-out. It is now Secure only when X-Forwarded-Proto says https, or when COOKIE_SECURE=1.
- /app and /app/[id] now require an analyst and a Jev key, the same rule /api/analyze enforces with a 412. Closing the guide mid-way used to leave a console that could not run anything.
- Playwright runs the production server (pree2e builds first) and gains a webkit project for session survival; chromium-only QA could never see a Secure-cookie rejection.
- tests-e2e/session.spec.ts pins both; .env.example gains COOKIE_SECURE and stops shipping a blank APP_SECRET.
- e2e: 21 passed (setup, chromium, webkit, mobile).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-22 10:45:12 +03:00

41 lines
2.3 KiB
TypeScript

import { test, expect } from "@playwright/test";
import { DatabaseSync } from "node:sqlite";
import { randomUUID } from "node:crypto";
test.use({ storageState: ".auth/user.json" });
const DB = ".playwright-data/qai.db";
// The app opens this file too (WAL), so writes here are just another connection.
function withDb<T>(fn: (d: DatabaseSync) => T): T {
const d = new DatabaseSync(DB);
try { return fn(d); } finally { d.close(); }
}
test("a row left running renders what exists instead of redirecting forever", async ({ page }) => {
const id = withDb((d) => {
// The setup project's account, not "the newest user": the webkit specs sign up their own accounts and
// would otherwise put this row under an account the stored session cannot open (404, not the note).
const userId = d.prepare("SELECT id FROM users WHERE email LIKE 'qa+%@qai.test' ORDER BY rowid DESC LIMIT 1").get() as { id: string };
const aid = randomUUID();
// Backdated so it never becomes the rail's first row: history.spec.ts depends on that row being a real run.
d.prepare("INSERT INTO analyses (id, user_id, title, xml, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?)")
.run(aid, userId.id, "Orphaned run", "<ShowPlanXML/>", "running", Date.now() - 3_600_000, Date.now() - 3_600_000);
return aid;
});
await page.goto(`/app/${id}`);
await expect(page.getByText(/This run was interrupted before it finished/)).toBeVisible({ timeout: 15_000 });
expect(page.url()).toContain(`/app/${id}`); // the old redirect sent this to itself
await expect(page.getByRole("button", { name: "Analyse" })).toHaveCount(0);
});
test("an email another account already has is refused inline", async ({ page }) => {
withDb((d) => d.prepare("INSERT INTO users (id, email, pass, created_at) VALUES (?,?,?,?)")
.run(randomUUID(), "taken@qai.test", "salt:hash", Date.now()));
await page.goto("/settings");
await page.fill("#email", "taken@qai.test");
await page.getByRole("button", { name: "Save" }).click();
await expect(page.locator("form p[role=alert]")).toContainText("That email already has an account", { timeout: 15_000 });
const still = withDb((d) => (d.prepare("SELECT email FROM users WHERE email = ?").get("taken@qai.test") as { email: string }).email);
expect(still).toBe("taken@qai.test");
});