import { NextResponse } from "next/server"; import { getAutoRunnerStatus, startAutoRunnersIfEnabled, stopAutoRunner, } from "yt-dlp-transcript-common/controller/autoRunner"; import { LANES } from "yt-dlp-transcript-common/lib/autoQueueTypes"; import type { AutoQueueKind } from "yt-dlp-transcript-common/jobs/autoQueueState"; import { testRouteDenied } from "../_guard"; export const dynamic = "force-dynamic"; // E2E test harness only. Simulates a SERVER RESTART for one lane's runner, // which is the one property of an enabled lane that cannot otherwise be tested: // the runner is process state, a restart destroys it, and // instrumentation.ts's boot hook is what brings it back. An e2e suite cannot // restart the dev server mid-run, so it does the two halves a restart does — // drop the in-process runner, then call the hook — and asserts one comes back. // // It replaced /api/test/resume-backfill-sweep, which did this for the sweep; // that route retired with the sweep in slice 1.3. // // GUARDED BY `E2E_TEST_ROUTES`, like every other /api/test route. It was // mounted unconditionally on the reasoning that the editor is a localhost admin // tool — but the operator's browser is inside the loopback, so an // unauthenticated GET that stops and restarts a lane runner is CSRF-able. // See _guard.ts. export async function GET(req: Request) { const denied = testRouteDenied(); if (denied) return denied; const lane = new URL(req.url).searchParams.get("lane") ?? ""; if (!LANES.includes(lane as AutoQueueKind)) { return NextResponse.json( { ok: false, error: `lane must be one of ${LANES.join(", ")}` }, { status: 400 }, ); } const kind = lane as AutoQueueKind; // Half one: lose the live runner, the way a restart does. The job's own // `finally` clears the singleton, so this leaves exactly the state a fresh // process starts in — the persisted policy and nothing running. const before = getAutoRunnerStatus(kind).jobId; stopAutoRunner(kind); // The cancel is asynchronous at the loop's end, and startAutoRunner treats a // still-running record as "already up" and returns it. So WAIT for the old // one to leave, or the resume is a no-op that silently proves nothing. const deadline = Date.now() + 15_000; while (getAutoRunnerStatus(kind).running && Date.now() < deadline) { await new Promise((r) => setTimeout(r, 50)); } // Half two: the boot hook. Reads autoQueue[lane].enabled and its persisted // tree, and re-launches from those alone. await startAutoRunnersIfEnabled(); return NextResponse.json({ ok: true, cancelled: before, resumed: getAutoRunnerStatus(kind).jobId, }); }