import { expect, test, type Page, type Route } from "@playwright/test"; const workspace = { id: "ws-1", name: "Launch Workspace", description: "Release notes", members: [{ userId: "user-1", role: "owner" }], updatedAt: "2026-05-05T00:00:00.000Z", updatedBy: "user-1", noteCount: 2, }; const relatedNote = { id: "note-2", workspaceId: "ws-1", title: "Related rollout note", body: "
Related body
", status: "active", tags: ["release"], links: [], updatedAt: "2026-05-04T00:00:00.000Z", updatedBy: "user-1", createdBy: "user-1", createdAt: "2026-05-04T00:00:00.000Z", sourceType: "manual", }; function accessToken() { const payload = Buffer.from(JSON.stringify({ exp: 4102444800 })).toString("base64url"); return `test.${payload}.token`; } async function seedAuth(page: Page) { await page.addInitScript((token) => { localStorage.setItem("notelett_auth_user", JSON.stringify({ id: "user-1", email: "user@example.com", name: "Release Tester" })); localStorage.setItem("notelett_access_token", token); localStorage.setItem("notelett_refresh_token", "refresh-token"); }, accessToken()); } async function mockReleaseApis(page: Page) { let note = { id: "note-1", workspaceId: "ws-1", title: "Launch readiness note", body: "Initial release body
", status: "active", tags: ["launch"], links: [], updatedAt: "2026-05-05T00:00:00.000Z", updatedBy: "user-1", createdBy: "user-1", createdAt: "2026-05-05T00:00:00.000Z", sourceType: "manual", }; let reviewItems = [ actionDoc("act-1", "Approve release summary", "proposed"), actionDoc("act-2", "Reject stale task", "proposed"), ]; const calls = { createdNote: false, editedNote: false, archived: false, restored: false, linked: false, promptRun: false, intake: false, approved: false, rejected: false, }; (page as Page & { releaseCalls?: typeof calls }).releaseCalls = calls; await page.route("**/api/auth/refresh", (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ accessToken: accessToken(), refreshToken: "refresh-token" }), }), ); await page.route("**/api/kill-switch**", (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ disabled: false, message: null }) }), ); await page.route("**/api/**", async (route) => { const request = route.request(); const url = new URL(request.url()); const path = url.pathname.replace(/^\/api/, ""); const method = request.method(); if (path === "/workspaces/summaries") { return json(route, { items: [workspace], total: 1 }); } if (path === "/workspaces") { return json(route, { items: [workspace], total: 1 }); } if (path === "/notes" && method === "POST") { calls.createdNote = true; const input = request.postDataJSON() as { id: string; title: string; body: string; workspaceId: string; tags?: string[] }; note = { ...note, ...input, status: "draft", updatedAt: "2026-05-05T01:00:00.000Z" }; return json(route, note, 201); } if (path === "/notes" && method === "GET") { const search = url.searchParams.get("search"); if (search === "note-1") return json(route, { items: [note], total: 1 }); if (search === "Related") return json(route, { items: [relatedNote], total: 1 }); return json(route, { items: [note, relatedNote], total: 2 }); } if (path === "/notes/search" && method === "POST") { return json(route, { items: [], total: 0, mode: "lexical" }); } if (path === "/notes/note-1" && method === "GET") { return json(route, note); } if (path === "/notes/note-1" && method === "PATCH") { calls.editedNote = true; const input = request.postDataJSON() as { title?: string; body?: string }; note = { ...note, ...input, updatedAt: "2026-05-05T02:00:00.000Z" }; return json(route, note); } if (path === "/notes/note-1/archive") { calls.archived = true; note = { ...note, status: "archived" }; return json(route, note); } if (path === "/notes/note-1/restore") { calls.restored = true; note = { ...note, status: "active" }; return json(route, note); } if (path === "/notes/note-1/versions") { return json(route, { items: [], total: 0 }); } if (path === "/note-tasks") return json(route, { items: [], total: 0 }); if (path === "/note-artifacts") return json(route, { items: [], total: 0 }); if (path === "/note-relationships" && method === "POST") { calls.linked = true; return json(route, { id: "rel-1" }, 201); } if (path === "/note-relationships") return json(route, { items: [], total: 0 }); if (path === "/note-agent-actions/pending" && method === "GET") { return json(route, { items: reviewItems, total: reviewItems.length }); } if (path === "/note-agent-actions" && method === "GET") { return json(route, { items: [], total: 0 }); } if (path.startsWith("/note-agent-actions/") && method === "PATCH") { const id = path.split("/").pop(); const body = request.postDataJSON() as { state: "approved" | "rejected" }; calls.approved ||= body.state === "approved"; calls.rejected ||= body.state === "rejected"; reviewItems = reviewItems.filter((item) => item.id !== id); return json(route, actionDoc(id ?? "act", body.state === "approved" ? "Approved" : "Rejected", body.state)); } if (path === "/note-prompts") { return json(route, { items: [{ id: "tmpl-1", slug: "summarize", name: "Summarize", description: "Summarize the current note", category: "transform", inputType: "text", outputType: "text", isBuiltin: true, }], total: 1, }); } if (path === "/notes/note-1/reading-time") return json(route, { wordCount: 120, readingTimeMinutes: 1 }); if (path === "/note-prompts/run") { calls.promptRun = true; return json(route, { content: "Prompt summary result", model: "mock", usage: { totalTokens: 12 } }); } if (path === "/intake") { calls.intake = true; return json(route, { jobId: "job-1", noteId: "note-1", contentType: "article", ruleMatched: null, templateSlug: "summarize", status: "queued" }, 201); } if (path === "/public/note-shares/public-token") { return route.fulfill({ status: 200, contentType: "application/json", headers: { "Cache-Control": "no-store", "X-Robots-Tag": "noindex, nofollow" }, body: JSON.stringify({ product: "NoteLett", noteId: "note-public", workspaceId: "ws-1", title: "Public release note", body: "Read-only public body
", updatedAt: "2026-05-05T00:00:00.000Z", expiresAt: "2026-06-05T00:00:00.000Z", }), }); } if (path === "/broadcasts/active") return json(route, { items: [], total: 0 }); if (path === "/surveys/active") return json(route, { items: [], total: 0 }); if (path === "/saved-views") return json(route, { items: [], total: 0 }); return json(route, {}); }); } test.beforeEach(async ({ page }) => { await seedAuth(page); await mockReleaseApis(page); }); test("create note and intake URL from dashboard", async ({ page }) => { await page.goto("/dashboard"); await page.getByLabel("URL to process").fill("https://example.com/release-plan"); await page.getByRole("button", { name: "Process URL" }).click(); await expect.poll(() => (page as Page & { releaseCalls: Record