import { mkdir, writeFile } from "node:fs/promises"; import { test, expect } from "@playwright/test"; import { channelStage, generateReport, pathExists, readJson, resetData, resolvePath, } from "./helpers"; type AvailabilityFile = { availability: string; checkedAt: string; webpageUrl?: string; }; const CHANNEL = "test-youtube"; async function seedVideo( id: string, metadata: Record, ): Promise { const dir = resolvePath( `test-transcripts/channels/${CHANNEL}/data/${id}`, ); await mkdir(dir, { recursive: true }); await writeFile( resolvePath( `test-transcripts/channels/${CHANNEL}/data/${id}/metadata.info.json`, ), JSON.stringify({ id, webpage_url: `https://www.youtube.com/watch?v=${id}`, ...metadata, }), ); } test("download backfills availability.json from metadata", async ({ page }) => { await resetData("one-youtube-channel"); // Two videos: one with availability=private in metadata, one without // availability set (yt-dlp default = treated as public). Neither has an // availability.json yet. await seedVideo("seededPrivate1", { availability: "private" }); await seedVideo("seededPlain1", {}); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "playlist")); // Sync pages the flat-playlist (fake emits fake0000000{1..5}) and downloads // each via the managed per-URL path. After it finishes, runYtdlp calls // backfillAvailabilityFromMetadata, which sweeps ALL existing data dirs. await page.getByRole("button", { name: "Sync" }).click(); await expect(page.getByLabel("Sync output")).toContainText("backfill", { timeout: 30_000, }); // Seeded videos should now have availability.json written from metadata. expect( await pathExists( `test-transcripts/channels/${CHANNEL}/data/seededPrivate1/availability.json`, ), ).toBe(true); const priv = await readJson( `test-transcripts/channels/${CHANNEL}/data/seededPrivate1/availability.json`, ); expect(priv.availability).toBe("private"); expect(priv.webpageUrl).toBe( "https://www.youtube.com/watch?v=seededPrivate1", ); const plain = await readJson( `test-transcripts/channels/${CHANNEL}/data/seededPlain1/availability.json`, ); expect(plain.availability).toBe("public"); // A newly-fetched video also gets a sidecar from its metadata. expect( await pathExists( `test-transcripts/channels/${CHANNEL}/data/fake00000001/availability.json`, ), ).toBe(true); }); test("backfill skips videos that already have an availability sidecar", async ({ page, }) => { await resetData("one-youtube-channel"); await seedVideo("seededPrivate1", { availability: "private" }); // Pre-existing sidecar marked as "deleted" — must not be overwritten by // backfill (which would otherwise infer "private" from metadata). await writeFile( resolvePath( `test-transcripts/channels/${CHANNEL}/data/seededPrivate1/availability.json`, ), JSON.stringify({ availability: "deleted", checkedAt: "2099-01-01T00:00:00.000Z", }), ); await generateReport(page, CHANNEL); await page.goto(channelStage(CHANNEL, "playlist")); await page.getByRole("button", { name: "Sync" }).click(); await expect(page.getByLabel("Sync output")).toContainText("backfill", { timeout: 20_000, }); const after = await readJson( `test-transcripts/channels/${CHANNEL}/data/seededPrivate1/availability.json`, ); expect(after.availability).toBe("deleted"); expect(after.checkedAt).toBe("2099-01-01T00:00:00.000Z"); });