/** * Prüft den TMDB-Importpfad gegen nachgebildete Antworten. * * npm test * * Braucht eine erreichbare Entwicklungsdatenbank (docker-compose.dev.yml) und * keinen TMDB-Schlüssel. Angelegt werden ausschließlich Titel mit TMDB-IDs * ab 999000 — echte Daten bleiben unberührt und werden am Ende aufgeräumt. */ import "./setup"; import assert from "node:assert/strict"; import test, { after, before } from "node:test"; import { installTmdbStub, SERIES_ID, MOVIE_ID, MOVIE_2_ID, COLLECTION_ID } from "./fixtures/tmdb"; import { prisma } from "../src/lib/db"; import { clearCatalogCaches, ensureTitle, importCollection, importMovie, importSeries, } from "../src/lib/catalog"; const stub = installTmdbStub(); async function cleanup() { await prisma.title.deleteMany({ where: { tmdbId: { gte: 999000, lt: 1000000 } } }); await prisma.watchProvider.deleteMany({ where: { tmdbId: { in: [8, 30, 3, 337] } } }); } before(cleanup); after(async () => { await cleanup(); stub.restore(); await prisma.$disconnect(); }); test("Serie mit Staffeln und Episoden übernehmen", async () => { const title = await importSeries(SERIES_ID, { region: "DE" }); assert.equal(title.title, "Nordlicht"); assert.equal(title.type, "SERIES"); assert.equal(title.imdbId, "tt9990010"); assert.equal(title.inProduction, true); assert.deepEqual(title.genres, ["Drama", "Mystery"]); assert.equal(title.runtime, 48, "durchschnittliche Episodenlänge aus episode_run_time"); // Specials zählen nicht mit: 2 reguläre Staffeln, 5 reguläre Episoden. assert.equal(title.seasonCount, 2); assert.equal(title.episodeCount, 5); const seasons = await prisma.season.findMany({ where: { titleId: title.id }, orderBy: { seasonNumber: "asc" }, }); assert.deepEqual( seasons.map((s) => s.seasonNumber), [0, 1, 2], "Specials werden importiert, aber nicht mitgezählt", ); const episodes = await prisma.episode.findMany({ where: { titleId: title.id, seasonNumber: 1 }, orderBy: { episodeNumber: "asc" }, }); assert.equal(episodes.length, 3); assert.equal(episodes[0]?.name, "Ankunft"); assert.equal( episodes[2]?.runtime, 48, "Episode ohne eigene Laufzeit fällt auf die Serienlänge zurück", ); assert.equal(episodes[0]?.airDate?.toISOString().slice(0, 10), "2023-03-14"); }); test("Streamingdienste werden je Region übernommen", async () => { const title = await prisma.title.findUniqueOrThrow({ where: { type_tmdbId: { type: "SERIES", tmdbId: SERIES_ID } }, include: { providers: { include: { provider: true } } }, }); const de = title.providers.filter((p) => p.region === "DE"); assert.equal(de.length, 3, "zwei Abo-Dienste und ein Kauf-Dienst für DE"); const flatrate = de.filter((p) => p.kind === "FLATRATE").map((p) => p.provider.name).sort(); assert.deepEqual(flatrate, ["Netflix", "WOW"]); const netflix = de.find((p) => p.provider.name === "Netflix"); assert.equal(netflix?.provider.color, "#e50914", "bekannter Dienst bekommt seine Akzentfarbe"); const at = title.providers.filter((p) => p.region === "AT"); assert.equal(at.length, 0, "nur die angeforderte Region wird geschrieben"); }); test("erneuter Import legt nichts doppelt an", async () => { clearCatalogCaches(); const before = await prisma.episode.count({ where: { title: { tmdbId: SERIES_ID, type: "SERIES" } }, }); await importSeries(SERIES_ID, { region: "DE" }); const after = await prisma.episode.count({ where: { title: { tmdbId: SERIES_ID, type: "SERIES" } }, }); assert.equal(after, before, "Episoden werden aktualisiert, nicht dupliziert"); const titles = await prisma.title.count({ where: { tmdbId: SERIES_ID, type: "SERIES" } }); assert.equal(titles, 1); }); test("Film mit Collection übernehmen", async () => { const movie = await importMovie(MOVIE_ID, "DE"); assert.equal(movie.type, "MOVIE"); assert.equal(movie.runtime, 118); assert.equal(movie.tmdbCollectionId, COLLECTION_ID); assert.equal(movie.tmdbCollectionName, "Vorführer-Reihe"); assert.equal(movie.releaseDate?.toISOString().slice(0, 10), "2024-10-17"); }); test("Collection wird nach Erscheinungsdatum sortiert importiert", async () => { clearCatalogCaches(); const { collection, titles } = await importCollection(COLLECTION_ID, "DE"); assert.equal(collection.name, "Vorführer-Reihe"); assert.equal(titles.length, 2); assert.deepEqual( titles.map((t) => t.tmdbId), [MOVIE_ID, MOVIE_2_ID], "die Fixture liefert die Teile verdreht — der Import muss sortieren", ); }); test("ensureTitle greift auf den Katalog zurück, statt erneut abzurufen", async () => { clearCatalogCaches(); const countBefore = stub.calls.length; await ensureTitle("SERIES", SERIES_ID, { maxAgeHours: 24, region: "DE" }); assert.equal(stub.calls.length, countBefore, "frisch importierter Titel löst keinen Abruf aus"); }); test("ensureTitle frischt veraltete Einträge auf", async () => { clearCatalogCaches(); await prisma.title.update({ where: { type_tmdbId: { type: "SERIES", tmdbId: SERIES_ID } }, data: { syncedAt: new Date(Date.now() - 40 * 24 * 3_600_000) }, }); const countBefore = stub.calls.length; await ensureTitle("SERIES", SERIES_ID, { maxAgeHours: 24, region: "DE" }); assert.ok(stub.calls.length > countBefore, "veralteter Eintrag wird neu geholt"); });