diff --git a/apps/app/src/app/api/pick/cache-regression.test.ts b/apps/app/src/app/api/pick/cache-regression.test.ts new file mode 100644 index 0000000..ba52cd2 --- /dev/null +++ b/apps/app/src/app/api/pick/cache-regression.test.ts @@ -0,0 +1,11 @@ +// @vitest-environment node +import { it, expect, vi } from "vitest"; +import { NextRequest } from "next/server"; +vi.mock("./store", () => ({getSession:vi.fn(),setSession:vi.fn()})); +import { getSession } from "./store"; +import { GET } from "./route"; +it.each([undefined,{status:"scanned"},{status:"picked",prompt:"Private prompt"}] as const)("never caches QR polling", async session => { + vi.mocked(getSession).mockReturnValue(session); + const r = await GET(new NextRequest("http://localhost/api/pick?sessionId=valid")); + expect(r.headers.get("cache-control")).toBe("no-store"); +}); diff --git a/apps/app/src/app/api/pick/route.ts b/apps/app/src/app/api/pick/route.ts index 8b9e543..a17976a 100644 --- a/apps/app/src/app/api/pick/route.ts +++ b/apps/app/src/app/api/pick/route.ts @@ -1,44 +1,46 @@ import { NextRequest, NextResponse } from "next/server"; import { getSession, setSession } from "./store"; +const reply = (body: object, status = 200) => NextResponse.json(body, { status, headers: { "Cache-Control": "no-store" } }); + /** GET — poll session status (desktop polls this) */ export async function GET(req: NextRequest) { const sessionId = req.nextUrl.searchParams.get("sessionId"); - if (!sessionId) return NextResponse.json({ error: "missing sessionId" }, { status: 400 }); + if (!sessionId) return reply({ error: "missing sessionId" }, 400); const session = getSession(sessionId); - if (!session) return NextResponse.json({ status: "waiting" }); + if (!session) return reply({ status: "waiting" }); // Consume the prompt on first read so duplicate polls don't re-trigger if (session.status === "picked" && session.prompt) { const prompt = session.prompt; setSession(sessionId, { status: "picked" }); - return NextResponse.json({ status: "picked", prompt }); + return reply({ status: "picked", prompt }); } - return NextResponse.json(session); + return reply(session); } /** PUT — mark session as scanned (mobile calls this on page load) */ export async function PUT(req: NextRequest) { const { sessionId } = await req.json(); - if (!sessionId) return NextResponse.json({ error: "missing sessionId" }, { status: 400 }); + if (!sessionId) return reply({ error: "missing sessionId" }, 400); const existing = getSession(sessionId); if (!existing || existing.status === "waiting") { setSession(sessionId, { status: "scanned" }); } - return NextResponse.json({ ok: true }); + return reply({ ok: true }); } /** POST — submit picked prompt (mobile calls this when user picks an option) */ export async function POST(req: NextRequest) { const { sessionId, prompt } = await req.json(); if (!sessionId || !prompt) { - return NextResponse.json({ error: "missing sessionId or prompt" }, { status: 400 }); + return reply({ error: "missing sessionId or prompt" }, 400); } setSession(sessionId, { status: "picked", prompt }); - return NextResponse.json({ ok: true }); + return reply({ ok: true }); }