// A synthetic imported course, laid out as the importer leaves it in /courses/. Nothing here is real material. import { mkdirSync, symlinkSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { LARGE_FILE_BYTES } from "../src/server/media-refs"; import { tempDir } from "./helpers"; export const VIDEO = "media/01 Intro – Übersicht.mp4"; export const VIDEO2 = "media/[2] Next step.mp4"; export const BIG_PDF = "decks/big deck.pdf"; /** Fake MP4 bytes: the server only streams them, nothing decodes them. */ export const videoBytes = (n = 4096, seed = 7) => new Uint8Array(n).map((_, i) => (i * seed) % 256); export interface ImportFixtureOpts { id?: string; kind?: string; manifestId?: string; root?: string; visibility?: string; /** Extra hostile bits: a symlink to outside, a .git dir, a nested .gitignore that hides a lesson. */ hostile?: boolean; } function put(dir: string, rel: string, content: string | Uint8Array) { mkdirSync(dirname(join(dir, rel)), { recursive: true }); writeFileSync(join(dir, rel), content); } export function manifestYaml(o: ImportFixtureOpts = {}): string { const id = o.manifestId ?? o.id ?? "imp"; return [ "schema: 1", `id: ${id}`, "title: Imported Course", `visibility: ${o.visibility ?? "restricted"}`, ...(o.root ? [`root: ${o.root}`] : []), "cover: media/cover.jpg", "source:", ` kind: ${o.kind ?? "video-folder"}`, " origin: /import/synthetic", "decks:", " - id: d1", ` file: ${BIG_PDF}`, " attachTo: l1", "modules:", " - id: m1", " title: Videos", " lessons:", " - id: l1", " title: Intro", " type: video", ` file: "${VIDEO}"`, " transcript: transcripts/l1.md", " - id: l2", " title: Next step", " type: video", ` file: "${VIDEO2}"`, " - id: notes", " title: Reading", " type: text", " file: code/S01 - Basics/README.md", "", ].join("\n"); } /** Writes the course into `/courses/` and returns that folder. */ export function makeImportedCourse(dataDir: string, o: ImportFixtureOpts = {}): string { const id = o.id ?? "imp"; const dir = join(dataDir, "courses", id); mkdirSync(dir, { recursive: true }); put(dir, "stcourse.yaml", manifestYaml(o)); put(dir, VIDEO, videoBytes(4096, 7)); put(dir, VIDEO2, videoBytes(2048, 11)); put(dir, "media/cover.jpg", new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0, 0x10, 0x4a, 0x46, 0x49, 0x46, 0, 1, 0xff, 0xd9])); put(dir, BIG_PDF, new Uint8Array(LARGE_FILE_BYTES + 100).fill(0x25)); put(dir, "transcripts/l1.md", "# Intro\n\n[0:00] Hello.\n"); put(dir, "code/S01 - Basics/README.md", "# Basics\n\nRead me.\n"); if (o.hostile) { // the companion code repo's own .gitignore hides Markdown: the lesson must still be committed put(dir, "code/S01 - Basics/.gitignore", "*.md\n"); put(dir, "code/.git/config", "[core]\n"); const outside = join(tempDir(), "secret.txt"); writeFileSync(outside, "outside\n"); symlinkSync(outside, join(dir, "code/leak.txt")); } return dir; }