import { mkdir, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { test, expect } from "@playwright/test"; import { channelStage, generateReport, pathExists, resetData, resolvePath, } from "./helpers"; const CHANNEL = "availability-test"; const FIXTURE = "availability-baseline"; type AvailabilityStatus = | "public" | "unlisted" | "private" | "members_only" | "needs_auth" | "deleted" | "error"; async function writeAvailability( id: string, status: AvailabilityStatus, opts: { error?: string } = {}, ) { const path = resolvePath( `test-transcripts/channels/${CHANNEL}/data/${id}/availability.json`, ); await mkdir(dirname(path), { recursive: true }); const record = { checkedAt: new Date().toISOString(), availability: status, webpageUrl: `https://www.youtube.com/watch?v=${id}`, ...(opts.error ? { error: opts.error } : {}), }; await writeFile(path, JSON.stringify(record)); } test("retry control renders for needs_auth and is absent on excluded buckets", async ({ page, }) => { await resetData(FIXTURE); // Populate buckets directly via sidecars so the test doesn't depend on // running availability checks first. await writeAvailability("vidneedsauth1", "needs_auth"); await writeAvailability("viddeleted1", "deleted"); await writeAvailability("vidprivate1", "private"); await writeAvailability("vidmembers1", "members_only"); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "diagnostics")); const needsAuth = page.getByLabel("retry needs auth bucket"); await expect(needsAuth).toBeVisible(); await expect( needsAuth.getByRole("button", { name: /^Retry \(1\)$/ }), ).toBeVisible(); // Excluded-from-download statuses must NOT show a retry control even // though their bucket cards render. await expect(page.getByLabel("availability deleted list")).toBeVisible(); await expect(page.getByLabel("retry deleted bucket")).toHaveCount(0); await expect(page.getByLabel("retry private bucket")).toHaveCount(0); await expect(page.getByLabel("retry members-only bucket")).toHaveCount(0); }); test("retry control renders for the error bucket", async ({ page }) => { await resetData(FIXTURE); // Reuse an existing fixture video for the synthetic error state — the // test only cares about UI wiring, not the cause of the error. await writeAvailability("vidneedsauth1", "error", { error: "transient" }); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "diagnostics")); const errorBucket = page.getByLabel("retry error bucket"); await expect(errorBucket).toBeVisible(); await expect( errorBucket.getByRole("button", { name: /^Retry \(1\)$/ }), ).toBeVisible(); }); test("clicking retry on needs_auth re-downloads the listed video", async ({ page, }) => { test.setTimeout(60_000); await resetData(FIXTURE); await writeAvailability("vidneedsauth1", "needs_auth"); // retry-bucket reads the channel playlist file to know which URL to fetch. await writeFile( resolvePath(`test-transcripts/channels/${CHANNEL}/playlist`), "https://www.youtube.com/watch?v=vidneedsauth1\n", ); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "diagnostics")); await page .getByLabel("retry needs auth bucket") .getByRole("button", { name: /^Retry \(1\)$/ }) .click(); const log = page.getByLabel("Retry needs auth output"); await expect(log).toContainText("Retry allowlist: kept 1 of 1", { timeout: 30_000, }); await expect(log).toContainText("Managed download complete", { timeout: 30_000, }); expect( await pathExists( `test-transcripts/channels/${CHANNEL}/data/vidneedsauth1/transcript.en.vtt`, ), ).toBe(true); });