/** * Nachgebildete TMDB-Antworten. * * Damit lässt sich der komplette Importpfad (Kopfdaten, Staffeln, Episoden, * Streamingdienste, Collections) prüfen, ohne einen echten Schlüssel zu * brauchen — und ohne bei jedem Lauf die TMDB-API zu belasten. Der Aufbau * entspricht dem, was TMDB tatsächlich liefert; gekürzt auf die Felder, die * Flimmer verwendet. */ export const SERIES_ID = 999001; export const MOVIE_ID = 999002; export const MOVIE_2_ID = 999003; export const COLLECTION_ID = 999100; export const seriesDetails = { id: SERIES_ID, name: "Nordlicht", original_name: "Nordlys", overview: "Eine Meteorologin kehrt in ihr Heimatdorf am Polarkreis zurück und stößt auf ein Wetterphänomen, das es nicht geben dürfte.", tagline: "Manche Lichter wollen nicht gesehen werden.", poster_path: "/abcdef1234567890.jpg", backdrop_path: "/bcdefa1234567890.jpg", first_air_date: "2023-03-14", last_air_date: "2025-05-02", episode_run_time: [48], status: "Returning Series", in_production: true, genres: [ { id: 18, name: "Drama" }, { id: 9648, name: "Mystery" }, ], original_language: "no", homepage: "https://example.org/nordlicht", vote_average: 8.1, number_of_seasons: 2, number_of_episodes: 5, external_ids: { imdb_id: "tt9990010" }, seasons: [ { id: 500000, season_number: 0, name: "Specials", overview: "Zusätzliche Folgen außerhalb der Handlung.", poster_path: null, air_date: "2023-12-24", episode_count: 1, }, { id: 500001, season_number: 1, name: "Staffel 1", overview: "Die Rückkehr.", poster_path: "/cdefab1234567890.jpg", air_date: "2023-03-14", episode_count: 3, }, { id: 500002, season_number: 2, name: "Staffel 2", overview: "Der lange Winter.", poster_path: "/defabc1234567890.jpg", air_date: "2025-04-11", episode_count: 2, }, ], "watch/providers": { results: { DE: { link: "https://www.themoviedb.org/tv/999001/watch?locale=DE", flatrate: [ { provider_id: 8, provider_name: "Netflix", logo_path: "/netflix12345678.jpg" }, { provider_id: 30, provider_name: "WOW", logo_path: "/wowlogo123456789.jpg" }, ], buy: [ { provider_id: 3, provider_name: "Google Play Movies", logo_path: "/googleplay123456.jpg", }, ], }, AT: { flatrate: [{ provider_id: 8, provider_name: "Netflix", logo_path: "/netflix12345678.jpg" }], }, }, }, }; export const seasons: Record = { 0: { id: 500000, season_number: 0, name: "Specials", air_date: "2023-12-24", episodes: [ { id: 600000, season_number: 0, episode_number: 1, name: "Weihnachten am Fjord", overview: "Eine Folge außer der Reihe.", still_path: "/still000000000001.jpg", air_date: "2023-12-24", runtime: 30, }, ], }, 1: { id: 500001, season_number: 1, name: "Staffel 1", air_date: "2023-03-14", episodes: [ { id: 600001, season_number: 1, episode_number: 1, name: "Ankunft", overview: "Ida steigt in Bodø aus dem Zug.", still_path: "/still000000000002.jpg", air_date: "2023-03-14", runtime: 52, }, { id: 600002, season_number: 1, episode_number: 2, name: "Der Messwert", overview: "Ein Gerät zeigt etwas Unmögliches an.", still_path: null, air_date: "2023-03-21", runtime: 47, }, { id: 600003, season_number: 1, episode_number: 3, name: "Polarnacht", overview: "Die Sonne bleibt weg — länger als sie darf.", still_path: null, air_date: "2023-03-28", // Ohne Laufzeit: muss auf episode_run_time zurückfallen. runtime: null, }, ], }, 2: { id: 500002, season_number: 2, name: "Staffel 2", air_date: "2025-04-11", episodes: [ { id: 600004, season_number: 2, episode_number: 1, name: "Rückkehr des Lichts", overview: null, still_path: null, air_date: "2025-04-11", runtime: 50, }, { id: 600005, season_number: 2, episode_number: 2, name: "Der zweite Winter", overview: null, still_path: null, // Noch nicht ausgestrahlt — kommt in Etappe 5 im Kalender vor. air_date: "2099-01-08", runtime: 50, }, ], }, }; export const movieDetails = { id: MOVIE_ID, imdb_id: "tt9990020", title: "Die Stille zwischen den Bildern", original_title: "The Silence Between Frames", overview: "Ein Filmvorführer entdeckt in einer alten Rolle Bilder, die dort nicht sein können.", tagline: "24 Bilder pro Sekunde. Eins davon lügt.", poster_path: "/movie0001234567890.jpg", backdrop_path: "/movieback01234567.jpg", release_date: "2024-10-17", runtime: 118, status: "Released", genres: [ { id: 27, name: "Horror" }, { id: 53, name: "Thriller" }, ], original_language: "en", homepage: null, vote_average: 7.4, belongs_to_collection: { id: COLLECTION_ID, name: "Vorführer-Reihe", poster_path: "/collection00123456.jpg", }, "watch/providers": { results: { DE: { flatrate: [ { provider_id: 337, provider_name: "Disney Plus", logo_path: "/disney1234567890.jpg" }, ], rent: [ { provider_id: 3, provider_name: "Google Play Movies", logo_path: "/googleplay123456.jpg" }, ], }, }, }, }; export const movie2Details = { ...movieDetails, id: MOVIE_2_ID, imdb_id: "tt9990030", title: "Die Stille zwischen den Bildern II", original_title: "The Silence Between Frames II", release_date: "2026-02-06", runtime: 126, poster_path: "/movie0002234567890.jpg", "watch/providers": { results: {} }, }; export const collectionDetails = { id: COLLECTION_ID, name: "Vorführer-Reihe", overview: "Zwei Filme über alte Projektoren und das, was sie zeigen.", poster_path: "/collection00123456.jpg", backdrop_path: null, parts: [ // Absichtlich in falscher Reihenfolge — der Import muss nach Datum sortieren. { id: MOVIE_2_ID, title: movie2Details.title, release_date: "2026-02-06" }, { id: MOVIE_ID, title: movieDetails.title, release_date: "2024-10-17" }, ], }; /** * Ersetzt `fetch` durch eine Fassung, die aus den Fixtures antwortet. * Liefert eine Funktion zum Zurücksetzen sowie die Liste der Aufrufe. */ export function installTmdbStub() { const original = globalThis.fetch; const calls: string[] = []; globalThis.fetch = (async (input: string | URL | Request) => { const url = new URL(typeof input === "string" ? input : input.toString()); calls.push(url.pathname); const json = (body: unknown) => new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" }, }); const path = url.pathname; if (path === `/3/tv/${SERIES_ID}`) return json(seriesDetails); const seasonMatch = path.match(new RegExp(`^/3/tv/${SERIES_ID}/season/(\\d+)$`)); if (seasonMatch?.[1] !== undefined) { const number = Number(seasonMatch[1]); const season = seasons[number]; return season ? json(season) : new Response(JSON.stringify({ status_message: "Not found" }), { status: 404 }); } if (path === `/3/movie/${MOVIE_ID}`) return json(movieDetails); if (path === `/3/movie/${MOVIE_2_ID}`) return json(movie2Details); if (path === `/3/collection/${COLLECTION_ID}`) return json(collectionDetails); return new Response(JSON.stringify({ status_message: `Kein Fixture für ${path}` }), { status: 404, headers: { "content-type": "application/json" }, }); }) as typeof fetch; return { calls, restore() { globalThis.fetch = original; }, }; }