Files
STCourses/packages/feature-import/test/video-folder-pdf.test.ts
T

76 lines
4.4 KiB
TypeScript

import { expect, test } from "bun:test";
import { readdir, readFile, stat } from "node:fs/promises";
import { join } from "node:path";
import { importPdfDeck } from "../src/jobs/pdf-deck";
import { importVideoFolder, lessonTitle } from "../src/jobs/video-folder";
import { makeEnv } from "./harness";
import { makePdf, makeVideo, tmp } from "./fixtures";
test("lessonTitle strips ordering prefixes", () => {
expect(lessonTitle("01 - Intro to Nix.mp4")).toBe("Intro to Nix");
expect(lessonTitle("2_setup_env.mkv")).toBe("setup env");
expect(lessonTitle("12.mp4")).toBe("12");
});
async function folderCourse() {
const env = await makeEnv();
const dir = await tmp();
const src = join(dir, "My Homelab Videos");
await makeVideo(join(src, "10 - Last.mp4"), 1);
await makeVideo(join(src, "2 - Second Clip.mp4"), 1);
await makeVideo(join(src, "1 - Ünïcode first.mp4"), 1);
const before = (await readdir(src)).sort();
const r = await importVideoFolder(env.deps, { kind: "video-folder", path: src }, env.jc);
return { env, src, before, r };
}
test("a folder of videos becomes one lesson per file in natural order", async () => {
const { env, src, before, r } = await folderCourse();
expect(r.courseId).toBe("my-homelab-videos");
const m = env.deps.ctx.courses.get(r.courseId)!;
expect(m.status).toBe("ok");
expect(m.manifest!.modules[0]!.lessons.map((l) => l.title)).toEqual(["Ünïcode first", "Second Clip", "Last"]);
expect(m.manifest!.visibility).toBe("restricted");
expect(m.manifest!.source?.kind).toBe("video-folder");
expect(env.deps.ctx.courses.resolvePath(r.courseId, "media/1 - Ünïcode first.mp4")).toBeTruthy();
expect((await readdir(src)).sort()).toEqual(before);
});
test("a deck attaches to a lesson, builds page images and text, and leaves the PDF original alone", async () => {
const { env, r } = await folderCourse();
const d = await tmp();
await makePdf(join(d, "Slides Deck.pdf"), ["Alpha slide", "Beta slide", "Gamma slide"]);
const before = await readFile(join(d, "Slides Deck.pdf"));
const res = await importPdfDeck(env.deps, { kind: "pdf-deck", path: join(d, "Slides Deck.pdf"), options: { courseId: r.courseId, attachTo: "l2", title: "My deck" } }, env.jc);
expect(res.warnings).toEqual([]);
const c = env.deps.ctx.courses.get(r.courseId)!;
expect(c.status).toBe("ok");
expect(c.manifest!.decks).toEqual([{ id: "d-slides-deck", file: "media/decks/Slides Deck.pdf", title: "My deck", attachTo: "l2" }]);
const cache = join(env.dataDir, "cache/decks", r.courseId, "d-slides-deck");
expect((await readdir(cache)).sort()).toEqual(["p-001.jpg", "p-002.jpg", "p-003.jpg", "text.json"]);
expect(JSON.parse(await readFile(join(cache, "text.json"), "utf8"))[2].text).toContain("Gamma slide");
expect((await stat(join(env.dataDir, "cache/covers", r.courseId, "deck-d-slides-deck.jpg"))).size).toBeGreaterThan(100);
expect(Buffer.compare(await readFile(join(d, "Slides Deck.pdf")), before)).toBe(0);
});
test("deck attach validates the target and refuses courses the importer did not create", async () => {
const { env, r } = await folderCourse();
const d = await tmp();
await makePdf(join(d, "a.pdf"));
await expect(importPdfDeck(env.deps, { path: join(d, "a.pdf"), options: { courseId: r.courseId, attachTo: "nope" } }, env.jc)).rejects.toThrow(/not a lesson or module/);
await expect(importPdfDeck(env.deps, { path: join(d, "a.pdf"), options: { courseId: r.courseId } }, env.jc)).rejects.toThrow(/attachTo is required/);
await expect(importPdfDeck(env.deps, { path: join(d, "a.pdf"), options: { courseId: "sample", attachTo: "x" } }, env.jc)).rejects.toThrow(/only edits courses it created/);
await expect(importPdfDeck(env.deps, { path: join(d, "a.pdf"), options: { courseId: "../x", attachTo: "x" } }, env.jc)).rejects.toThrow(/only edits courses it created/);
await expect(importPdfDeck(env.deps, { path: join(d, "missing.pdf"), options: {} }, env.jc)).rejects.toThrow(/not a PDF|outside the allowed/);
});
test("without PyMuPDF the deck is still attached, with a warning", async () => {
const { env, r } = await folderCourse();
env.deps.python = "/nonexistent/python";
const d = await tmp();
await makePdf(join(d, "a.pdf"));
const res = await importPdfDeck(env.deps, { path: join(d, "a.pdf"), options: { courseId: r.courseId, attachTo: "m1" } }, env.jc);
expect(res.warnings.join()).toContain("page images and text could not be built");
expect(env.deps.ctx.courses.get(r.courseId)!.manifest!.decks).toHaveLength(1);
});