import { NextResponse } from "next/server"; import { workerEndpointEnabled } from "yt-dlp-transcript-common/lib/workerToken"; import { testRouteDenied } from "../_guard"; export const dynamic = "force-dynamic"; // E2E test harness only. Unsets and restores `WORKER_TOKEN` inside the running // server. // // WHY A ROUTE AND NOT A SPEC FIXTURE. The 503 branch is the one that decides // whether an instance is an open transcription server — unset means OFF, you // opt in — and no spec could observe it: the test server boots with // WORKER_TOKEN=test-worker-token (editor/package.json, dev:test) and ONE server // serves the whole suite, so the only way to see the endpoint disabled is to // turn it off in the process that is answering. A suite cannot restart the dev // server mid-run, which is the same reason /api/test/resume-lane exists. // // IT WORKS BECAUSE `getWorkerToken()` READS process.env PER CALL, never at // import (common/lib/workerToken.test.ts pins that). A module-level cache would // make this route a silent no-op and the spec below would pass by proving // nothing. // // ⚠️ THE SPEC MUST RESTORE IN A `finally`. The variable is process-wide and the // server outlives the spec, so a run that unsets it and throws leaves every // later /api/ops and /api/worker spec answering 503 — a whole suite red from // one failure. `?set=` with no value is the restore, and it is idempotent. // // GUARDED BY `E2E_TEST_ROUTES`, and it is the route that made the guard // necessary: an unauthenticated GET that SETS a credential is a CSRF-able // token grant, and "the editor is a localhost admin tool" does not help — the // operator's browser is inside the loopback. See _guard.ts. export async function GET(req: Request) { // FIRST LINE, BEFORE THE QUERY IS EVEN PARSED. This route sets a credential; // off a harness-started server it does not exist. See _guard.ts. const denied = testRouteDenied(); if (denied) return denied; const url = new URL(req.url); // `?set=` restores (or changes) it; `?unset=1` removes it entirely. // Exactly one of the two, so a typo cannot silently do nothing. const set = url.searchParams.get("set"); const unset = url.searchParams.get("unset"); if ((set === null) === (unset === null)) { return NextResponse.json( { ok: false, error: "pass exactly one of ?set= or ?unset=1" }, { status: 400 }, ); } const before = workerEndpointEnabled(); if (unset !== null) delete process.env.WORKER_TOKEN; else process.env.WORKER_TOKEN = set as string; return NextResponse.json({ ok: true, was: before, // Never the token itself: this response goes into a test log. enabled: workerEndpointEnabled(), }); }