76 lines
4.4 KiB
TypeScript
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/);
|
|
});
|
|
|
|
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);
|
|
});
|