test: Importpfad gegen nachgebildete TMDB-Antworten prüfen

npm test läuft ohne TMDB-Schlüssel gegen die Entwicklungsdatenbank und deckt
Kopfdaten, Staffeln, Episoden, Streamingdienste je Region, wiederholten
Import und die Auffrischlogik ab.

Dabei aufgefallen: importSeries gab den Titel zurück, bevor die Staffel- und
Episodenzähler geschrieben waren — die Zähler standen auf 0.
This commit is contained in:
Florian Wilde
2026-09-01 19:00:13 +02:00
parent e11cb6abef
commit 75128d1bf2
4 changed files with 470 additions and 2 deletions
+157
View File
@@ -0,0 +1,157 @@
/**
* 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");
});