test: Universen absichern
12 Tests für Reihenfolgen, Fortschritt über Filme und Serien hinweg, Freigaben mit Lese- und Schreibrecht sowie die Rechteprüfung gegenüber Fremden.
This commit is contained in:
@@ -0,0 +1,282 @@
|
|||||||
|
import "./setup";
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test, { after, before, beforeEach } from "node:test";
|
||||||
|
|
||||||
|
import { installTmdbStub, SERIES_ID, MOVIE_ID, MOVIE_2_ID, COLLECTION_ID } from "./fixtures/tmdb";
|
||||||
|
import { prisma } from "../src/lib/db";
|
||||||
|
import { importCollection, importSeries } from "../src/lib/catalog";
|
||||||
|
import { addToLibrary, markEpisodesWatched, markMovieWatched } from "../src/lib/library";
|
||||||
|
import {
|
||||||
|
addCollection,
|
||||||
|
addTitle,
|
||||||
|
createOrder,
|
||||||
|
createUniverse,
|
||||||
|
getAccess,
|
||||||
|
getUniverse,
|
||||||
|
listUniverses,
|
||||||
|
setOrderPositions,
|
||||||
|
shareUniverse,
|
||||||
|
trashUniverse,
|
||||||
|
} from "../src/lib/universes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft Universen: Reihenfolgen, Fortschritt aus Sicht des Betrachters,
|
||||||
|
* Teilen und die Rechteprüfung.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const stub = installTmdbStub();
|
||||||
|
const OWNER_EMAIL = "test-uni-owner@flimmer.invalid";
|
||||||
|
const GUEST_EMAIL = "test-uni-guest@flimmer.invalid";
|
||||||
|
|
||||||
|
let ownerId: string;
|
||||||
|
let guestId: string;
|
||||||
|
let seriesTitleId: string;
|
||||||
|
let movieTitleId: string;
|
||||||
|
let movie2TitleId: string;
|
||||||
|
|
||||||
|
async function cleanup() {
|
||||||
|
await prisma.user.deleteMany({ where: { email: { in: [OWNER_EMAIL, GUEST_EMAIL] } } });
|
||||||
|
await prisma.title.deleteMany({ where: { tmdbId: { gte: 999000, lt: 1000000 } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await cleanup();
|
||||||
|
ownerId = (
|
||||||
|
await prisma.user.create({ data: { email: OWNER_EMAIL, name: "Besitzerin" }, select: { id: true } })
|
||||||
|
).id;
|
||||||
|
guestId = (
|
||||||
|
await prisma.user.create({ data: { email: GUEST_EMAIL, name: "Gast" }, select: { id: true } })
|
||||||
|
).id;
|
||||||
|
|
||||||
|
seriesTitleId = (await importSeries(SERIES_ID, { region: "DE" })).id;
|
||||||
|
const { titles } = await importCollection(COLLECTION_ID, "DE");
|
||||||
|
movieTitleId = titles.find((title) => title.tmdbId === MOVIE_ID)!.id;
|
||||||
|
movie2TitleId = titles.find((title) => title.tmdbId === MOVIE_2_ID)!.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await prisma.universe.deleteMany({ where: { ownerId } });
|
||||||
|
await prisma.watch.deleteMany({ where: { userId: { in: [ownerId, guestId] } } });
|
||||||
|
await prisma.libraryEntry.deleteMany({ where: { userId: { in: [ownerId, guestId] } } });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await cleanup();
|
||||||
|
stub.restore();
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ein neues Universum bringt die Erscheinungsreihenfolge mit", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Vorführer-Universum", "Zwei Filme, eine Rolle.");
|
||||||
|
|
||||||
|
const view = await getUniverse(ownerId, universe.id);
|
||||||
|
assert.ok(view);
|
||||||
|
assert.equal(view.orders.length, 1);
|
||||||
|
assert.equal(view.orders[0]?.kind, "RELEASE");
|
||||||
|
assert.equal(view.orders[0]?.isDefault, true);
|
||||||
|
assert.equal(view.entries.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Slugs bleiben je Besitzer eindeutig", async () => {
|
||||||
|
const first = await createUniverse(ownerId, "Sternenpfad");
|
||||||
|
const second = await createUniverse(ownerId, "Sternenpfad");
|
||||||
|
|
||||||
|
assert.equal(first.slug, "sternenpfad");
|
||||||
|
assert.equal(second.slug, "sternenpfad-2", "die Dublette wird durchgezählt");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Filme und Serien landen gemeinsam in einem Universum", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Gemischt");
|
||||||
|
await addTitle(ownerId, universe.id, seriesTitleId);
|
||||||
|
await addTitle(ownerId, universe.id, movieTitleId);
|
||||||
|
|
||||||
|
const view = await getUniverse(ownerId, universe.id);
|
||||||
|
assert.equal(view?.entries.length, 2);
|
||||||
|
assert.deepEqual(
|
||||||
|
view?.entries.map((entry) => entry.type).sort(),
|
||||||
|
["MOVIE", "SERIES"],
|
||||||
|
"Serien und Filme stehen gleichberechtigt nebeneinander",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Zweimal derselbe Titel legt keinen zweiten Eintrag an.
|
||||||
|
await addTitle(ownerId, universe.id, movieTitleId);
|
||||||
|
const again = await getUniverse(ownerId, universe.id);
|
||||||
|
assert.equal(again?.entries.length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("TMDB-Collection lässt sich als Startpunkt übernehmen", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Aus Collection");
|
||||||
|
const added = await addCollection(ownerId, universe.id, COLLECTION_ID);
|
||||||
|
|
||||||
|
assert.equal(added, 2);
|
||||||
|
const view = await getUniverse(ownerId, universe.id);
|
||||||
|
assert.deepEqual(
|
||||||
|
view?.entries.map((entry) => entry.title),
|
||||||
|
[
|
||||||
|
"Die Stille zwischen den Bildern",
|
||||||
|
"Die Stille zwischen den Bildern II",
|
||||||
|
],
|
||||||
|
"Erscheinungsreihenfolge ergibt sich aus den Daten",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Erneutes Übernehmen fügt nichts doppelt hinzu.
|
||||||
|
assert.equal(await addCollection(ownerId, universe.id, COLLECTION_ID), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("eigene Reihenfolge lässt sich frei sortieren", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Sortierbar");
|
||||||
|
await addCollection(ownerId, universe.id, COLLECTION_ID);
|
||||||
|
await addTitle(ownerId, universe.id, seriesTitleId);
|
||||||
|
|
||||||
|
const order = await createOrder(ownerId, universe.id, "Chronologisch");
|
||||||
|
const before = await getUniverse(ownerId, universe.id, order.id);
|
||||||
|
assert.equal(before?.entries.length, 3);
|
||||||
|
|
||||||
|
// Umdrehen
|
||||||
|
const reversed = [...before!.entries].reverse().map((entry) => entry.itemId);
|
||||||
|
await setOrderPositions(ownerId, order.id, reversed);
|
||||||
|
|
||||||
|
const after = await getUniverse(ownerId, universe.id, order.id);
|
||||||
|
assert.deepEqual(
|
||||||
|
after?.entries.map((entry) => entry.itemId),
|
||||||
|
reversed,
|
||||||
|
"die eigene Reihenfolge steht so, wie sie gesetzt wurde",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Die Erscheinungsreihenfolge bleibt davon unberührt.
|
||||||
|
const release = await getUniverse(ownerId, universe.id, before!.orders.find((o) => o.kind === "RELEASE")!.id);
|
||||||
|
assert.notDeepEqual(release?.entries.map((entry) => entry.itemId), reversed);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("die Erscheinungsreihenfolge lässt sich nicht umsortieren", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Fest");
|
||||||
|
await addTitle(ownerId, universe.id, movieTitleId);
|
||||||
|
const view = await getUniverse(ownerId, universe.id);
|
||||||
|
const releaseOrder = view!.orders.find((order) => order.kind === "RELEASE")!;
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => setOrderPositions(ownerId, releaseOrder.id, [view!.entries[0]!.itemId]),
|
||||||
|
/ergibt sich aus den Daten/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Fortschritt und Restlaufzeit rechnen über Filme und Serien", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Fortschritt");
|
||||||
|
await addTitle(ownerId, universe.id, movieTitleId);
|
||||||
|
await addTitle(ownerId, universe.id, seriesTitleId);
|
||||||
|
|
||||||
|
const fresh = await getUniverse(ownerId, universe.id);
|
||||||
|
// Film 118 min + Serie 5 Episoden (52+47+48+50+50 = 247 min)
|
||||||
|
assert.equal(fresh?.progress.total, 2);
|
||||||
|
assert.equal(fresh?.progress.completed, 0);
|
||||||
|
assert.equal(fresh?.progress.totalMinutes, 118 + 247);
|
||||||
|
assert.equal(fresh?.progress.remainingMinutes, 118 + 247);
|
||||||
|
// Erscheinungsreihenfolge: die Serie (2023) läuft vor dem Film (2024).
|
||||||
|
assert.equal(fresh?.nextUp?.title, "Nordlicht");
|
||||||
|
|
||||||
|
// Film sehen
|
||||||
|
const movieEntry = await addToLibrary(ownerId, movieTitleId);
|
||||||
|
await markMovieWatched(ownerId, movieEntry.id);
|
||||||
|
|
||||||
|
const afterMovie = await getUniverse(ownerId, universe.id);
|
||||||
|
assert.equal(afterMovie?.progress.completed, 1);
|
||||||
|
assert.equal(afterMovie?.progress.percent, 50);
|
||||||
|
assert.equal(afterMovie?.progress.watchedMinutes, 118);
|
||||||
|
assert.equal(afterMovie?.progress.remainingMinutes, 247);
|
||||||
|
assert.equal(
|
||||||
|
afterMovie?.nextUp?.type,
|
||||||
|
"SERIES",
|
||||||
|
"der Film ist durch, offen bleibt die Serie",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Zwei Episoden der Serie
|
||||||
|
const seriesEntry = await addToLibrary(ownerId, seriesTitleId);
|
||||||
|
const episodes = await prisma.episode.findMany({
|
||||||
|
where: { titleId: seriesTitleId, seasonNumber: { gt: 0 } },
|
||||||
|
orderBy: [{ seasonNumber: "asc" }, { episodeNumber: "asc" }],
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
await markEpisodesWatched(ownerId, seriesEntry.id, [episodes[0]!.id, episodes[1]!.id]);
|
||||||
|
|
||||||
|
const afterEpisodes = await getUniverse(ownerId, universe.id);
|
||||||
|
const series = afterEpisodes!.entries.find((entry) => entry.type === "SERIES")!;
|
||||||
|
assert.equal(series.watchedEpisodes, 2);
|
||||||
|
assert.equal(series.totalEpisodes, 5);
|
||||||
|
assert.equal(series.completed, false);
|
||||||
|
assert.ok(
|
||||||
|
series.remainingMinutes < 247 && series.remainingMinutes > 0,
|
||||||
|
"die Restlaufzeit schrumpft anteilig",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("geteilte Universen zeigen jedem den eigenen Fortschritt", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Geteilt");
|
||||||
|
await addTitle(ownerId, universe.id, movieTitleId);
|
||||||
|
|
||||||
|
const entry = await addToLibrary(ownerId, movieTitleId);
|
||||||
|
await markMovieWatched(ownerId, entry.id);
|
||||||
|
|
||||||
|
await shareUniverse(ownerId, universe.id, GUEST_EMAIL, "READ");
|
||||||
|
|
||||||
|
const ownerView = await getUniverse(ownerId, universe.id);
|
||||||
|
const guestView = await getUniverse(guestId, universe.id);
|
||||||
|
|
||||||
|
assert.equal(ownerView?.progress.completed, 1);
|
||||||
|
assert.equal(guestView?.progress.completed, 0, "der Gast hat den Film noch nicht gesehen");
|
||||||
|
assert.equal(guestView?.canWrite, false, "Leserecht bleibt Leserecht");
|
||||||
|
assert.equal(guestView?.isOwner, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Schreibrecht lässt sich vergeben, Löschen bleibt beim Besitzer", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Gemeinsam");
|
||||||
|
await shareUniverse(ownerId, universe.id, GUEST_EMAIL, "WRITE");
|
||||||
|
|
||||||
|
const access = await getAccess(guestId, universe.id);
|
||||||
|
assert.equal(access.canWrite, true);
|
||||||
|
assert.equal(access.isOwner, false);
|
||||||
|
|
||||||
|
// Mit Schreibrecht darf der Gast Titel aufnehmen …
|
||||||
|
await addTitle(guestId, universe.id, movieTitleId);
|
||||||
|
const view = await getUniverse(guestId, universe.id);
|
||||||
|
assert.equal(view?.entries.length, 1);
|
||||||
|
|
||||||
|
// … aber nicht löschen.
|
||||||
|
await assert.rejects(() => trashUniverse(guestId, universe.id), /Nur wer das Universum angelegt hat/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Fremde sehen ein Universum gar nicht", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Privat");
|
||||||
|
const access = await getAccess(guestId, universe.id);
|
||||||
|
|
||||||
|
assert.equal(access.canRead, false);
|
||||||
|
assert.equal(await getUniverse(guestId, universe.id), null);
|
||||||
|
await assert.rejects(() => addTitle(guestId, universe.id, movieTitleId), /gibt es nicht/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("die Übersicht listet eigene und geteilte Universen", async () => {
|
||||||
|
const own = await createUniverse(ownerId, "Meins");
|
||||||
|
await addTitle(ownerId, own.id, movieTitleId);
|
||||||
|
await addTitle(ownerId, own.id, movie2TitleId);
|
||||||
|
await shareUniverse(ownerId, own.id, GUEST_EMAIL, "READ");
|
||||||
|
|
||||||
|
const entry = await addToLibrary(guestId, movieTitleId);
|
||||||
|
await markMovieWatched(guestId, entry.id);
|
||||||
|
|
||||||
|
const guestList = await listUniverses(guestId);
|
||||||
|
assert.equal(guestList.length, 1);
|
||||||
|
assert.equal(guestList[0]?.isOwner, false);
|
||||||
|
assert.equal(guestList[0]?.ownerName, "Besitzerin");
|
||||||
|
assert.equal(guestList[0]?.itemCount, 2);
|
||||||
|
assert.equal(guestList[0]?.completed, 1, "der Gast hat einen der beiden Filme gesehen");
|
||||||
|
assert.equal(guestList[0]?.percent, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("gelöschte Universen verschwinden aus der Übersicht", async () => {
|
||||||
|
const universe = await createUniverse(ownerId, "Weg damit");
|
||||||
|
await trashUniverse(ownerId, universe.id);
|
||||||
|
|
||||||
|
assert.equal(await getUniverse(ownerId, universe.id), null);
|
||||||
|
const list = await listUniverses(ownerId);
|
||||||
|
assert.equal(list.find((item) => item.id === universe.id), undefined);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user