Skip to content

Commit 1000002

Browse files
B2JK-Industryclaude
andcommitted
fix(test): admin shortcut to flip teacher verified for class-mode e2e
CI v6 cleared the email-required + birth-year-select issues but exposed the next G-14 layer: golden-paths #5 (class mode) creates a teacher then immediately POSTs /api/nauczyciel/class. New teachers default to verified=false (G-14 spec), so the gate I added returns 403 with `teacher-not-verified`. Production fix is the email-link flow at /verify?token=…, but the token isn't returned in the signup response (security: tokens go out via the mailer, not the HTTP body). Tests need a way to flip the flag without intercepting email. Solution mirrors the existing admin-endpoint pattern in `app/api/admin/seed-demo-school` and `purge-e2e-accounts`: - `app/api/admin/teacher-verify/route.ts` (NEW). POST { username } with `Authorization: Bearer $ADMIN_SECRET`. Calls `markTeacherVerified` directly. Local dev without the secret is allowed (same pattern as seed-demo-school) so dev workflow isn't blocked. - golden-paths #5 calls the admin endpoint with the same E2E_ADMIN_SECRET fallback string `production-ready.spec.ts` already uses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 654f163 commit 1000002

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { NextRequest } from "next/server";
2+
import { z } from "zod";
3+
import { markTeacherVerified } from "@/lib/class";
4+
5+
/* PR-P G-14 follow-up — admin shortcut to flip a teacher's `verified`
6+
* flag without going through the email link. Used by the e2e suite
7+
* (golden-paths #5 class mode) to bypass the verify gate. ADMIN_SECRET
8+
* is the primary guard; local dev without the secret is allowed
9+
* (matches the pattern in `seed-demo-school` and other admin
10+
* endpoints).
11+
*
12+
* POST { username } with `Authorization: Bearer $ADMIN_SECRET`.
13+
*/
14+
15+
const BodySchema = z.object({
16+
username: z.string().min(1).max(64),
17+
});
18+
19+
function unauthorized() {
20+
return Response.json({ ok: false, error: "unauthorized" }, { status: 401 });
21+
}
22+
23+
function checkSecret(req: NextRequest): boolean {
24+
const expected = process.env.ADMIN_SECRET;
25+
if (!expected) return true;
26+
const authz = req.headers.get("authorization") ?? "";
27+
return authz === `Bearer ${expected}`;
28+
}
29+
30+
export async function POST(req: NextRequest) {
31+
if (!checkSecret(req)) return unauthorized();
32+
let parsed;
33+
try {
34+
parsed = BodySchema.parse(await req.json());
35+
} catch (e) {
36+
return Response.json(
37+
{ ok: false, error: `bad-body: ${(e as Error).message}` },
38+
{ status: 400 },
39+
);
40+
}
41+
await markTeacherVerified(parsed.username);
42+
return Response.json({ ok: true, verified: true });
43+
}

e2e/golden-paths.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,15 @@ test.describe("golden paths", () => {
230230
schoolName: "Playwright School",
231231
});
232232
expect(tSignup.status, `teacher signup: ${JSON.stringify(tSignup)}`).toBeLessThan(400);
233+
// G-14 — new teachers default to verified=false; class POST is
234+
// 403 until /verify?token=… is consumed. Tests bypass via the
235+
// admin shortcut endpoint (see app/api/admin/teacher-verify).
236+
const ADMIN_SECRET =
237+
process.env.E2E_ADMIN_SECRET ?? "e2e-admin-secret-not-for-production";
238+
await t.request.post("/api/admin/teacher-verify", {
239+
headers: { authorization: `Bearer ${ADMIN_SECRET}` },
240+
data: { username: teacherUser },
241+
});
233242

234243
// Class POST requires { name, grade, subject? } — see
235244
// app/api/nauczyciel/class/route.ts.

0 commit comments

Comments
 (0)