| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- # -*- coding: utf-8 -*-
- """
- Tests fuer den EPGCache mit JSON-Persistenz und In-Memory-Indizes.
- Testet Laden/Speichern, Programm-Updates, Pruning, Abfragen nach
- Sender/Titel/Tag/Genre sowie Enrichment-Daten.
- """
- from __future__ import annotations
- import json
- from datetime import date, datetime, timedelta, timezone
- from pathlib import Path
- import pytest
- from plugins.tv_program.epg.cache import EPGCache
- # ============================================================================
- # Hilfsfunktionen fuer Testdaten
- # ============================================================================
- def _jetzt() -> datetime:
- """Gibt die aktuelle Zeit mit Zeitzone zurueck."""
- return datetime.now(tz=timezone.utc)
- def _iso(dt: datetime) -> str:
- """Konvertiert ein datetime-Objekt in ISO-Format."""
- return dt.isoformat()
- def _programm(
- channel: str,
- title: str,
- start: datetime,
- dauer_min: int = 60,
- genre: list[str] | None = None,
- ) -> dict:
- """Erstellt ein Test-Programm-Dict."""
- end = start + timedelta(minutes=dauer_min)
- prog: dict = {
- "ch": channel,
- "t": title,
- "s": _iso(start),
- "e": _iso(end),
- }
- if genre:
- prog["g"] = genre
- return prog
- def _programm_liste_heute(channel: str = "ard") -> list[dict]:
- """Erstellt eine Tages-Programmliste mit mehreren Sendungen."""
- heute = _jetzt().replace(hour=0, minute=0, second=0, microsecond=0)
- return [
- _programm(channel, "Morgenmagazin", heute.replace(hour=6), 120),
- _programm(channel, "Tagesschau", heute.replace(hour=12), 30),
- _programm(channel, "Mittagsmagazin", heute.replace(hour=12, minute=30), 60),
- _programm(channel, "Tagesschau", heute.replace(hour=20), 15),
- _programm(
- channel,
- "Tatort",
- heute.replace(hour=20, minute=15),
- 90,
- genre=["Krimi", "Spielfilm"],
- ),
- _programm(channel, "Tagesthemen", heute.replace(hour=21, minute=45), 30),
- ]
- # ============================================================================
- # EPGCache - JSON I/O
- # ============================================================================
- class TestEPGCacheLadenSpeichern:
- """Tests fuer die JSON-Persistenz des Caches."""
- def test_load_fehlende_datei_gibt_false(self, tmp_path: Path) -> None:
- """Laden ohne vorhandene Datei gibt False zurueck."""
- cache = EPGCache(tmp_path)
- assert cache.load() is False
- def test_load_fehlende_datei_leere_daten(self, tmp_path: Path) -> None:
- """Nach fehlgeschlagenem Laden sind die Daten leer."""
- cache = EPGCache(tmp_path)
- cache.load()
- stats = cache.get_stats()
- assert stats["program_count"] == 0
- def test_save_und_load_roundtrip(self, tmp_path: Path) -> None:
- """Gespeicherte Daten koennen wieder geladen werden."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- cache.save()
- # Neuen Cache aus gleicher Datei laden
- cache2 = EPGCache(tmp_path)
- assert cache2.load() is True
- stats = cache2.get_stats()
- assert stats["program_count"] == len(programme)
- def test_save_erzeugt_json_datei(self, tmp_path: Path) -> None:
- """Nach dem Speichern existiert die JSON-Datei."""
- cache = EPGCache(tmp_path)
- cache.save()
- assert (tmp_path / EPGCache.FILE_NAME).exists()
- def test_load_korrupte_datei_gibt_false(self, tmp_path: Path) -> None:
- """Beschaedigte JSON-Datei wird toleriert und gibt False zurueck."""
- datei = tmp_path / EPGCache.FILE_NAME
- datei.write_text("{kaputt: invalid json!!!", encoding="utf-8")
- cache = EPGCache(tmp_path)
- assert cache.load() is False
- def test_load_korrupte_datei_leere_daten(self, tmp_path: Path) -> None:
- """Nach Laden einer korrupten Datei sind die Daten leer."""
- datei = tmp_path / EPGCache.FILE_NAME
- datei.write_text("nicht json", encoding="utf-8")
- cache = EPGCache(tmp_path)
- cache.load()
- assert cache.get_stats()["program_count"] == 0
- # ============================================================================
- # EPGCache - update_programs()
- # ============================================================================
- class TestEPGCacheUpdatePrograms:
- """Tests fuer das Hinzufuegen und Aktualisieren von Programmen."""
- def test_update_fuegt_neue_programme_hinzu(self, tmp_path: Path) -> None:
- """Neue Programme werden korrekt hinzugefuegt."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- count = cache.update_programs(programme)
- assert count == len(programme)
- def test_update_gibt_neue_anzahl_zurueck(self, tmp_path: Path) -> None:
- """Rueckgabewert ist die Anzahl der neuen Programme."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- count = cache.update_programs(programme)
- assert count == 6
- def test_update_deduplizierung_gleiche_id(self, tmp_path: Path) -> None:
- """Bereits vorhandene Programme (gleiche ID) werden nicht dupliziert."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- # Erstes Einfuegen
- cache.update_programs(programme)
- # Gleiches nochmal einfuegen
- count = cache.update_programs(programme)
- assert count == 0 # Keine neuen
- def test_update_stats_korrekt_nach_einfuegen(self, tmp_path: Path) -> None:
- """Statistiken stimmen nach dem Einfuegen."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- stats = cache.get_stats()
- assert stats["program_count"] == len(programme)
- assert stats["channel_count"] >= 1
- def test_update_leere_liste(self, tmp_path: Path) -> None:
- """Leere Programmliste verursacht keinen Fehler."""
- cache = EPGCache(tmp_path)
- count = cache.update_programs([])
- assert count == 0
- # ============================================================================
- # EPGCache - prune_old()
- # ============================================================================
- class TestEPGCachePruneOld:
- """Tests fuer das Entfernen alter Sendungen."""
- def test_prune_entfernt_alte_sendungen(self, tmp_path: Path) -> None:
- """Sendungen aelter als retention_days werden entfernt."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- alte_sendung = _programm("ard", "Alte Show", jetzt - timedelta(days=30))
- cache.update_programs([alte_sendung])
- removed = cache.prune_old(retention_days=14)
- assert removed == 1
- assert cache.get_stats()["program_count"] == 0
- def test_prune_behaelt_aktuelle_sendungen(self, tmp_path: Path) -> None:
- """Aktuelle Sendungen bleiben erhalten."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- aktuelle_sendung = _programm("ard", "Aktuelle Show", jetzt)
- cache.update_programs([aktuelle_sendung])
- removed = cache.prune_old(retention_days=14)
- assert removed == 0
- assert cache.get_stats()["program_count"] == 1
- def test_prune_gemischt_alt_und_neu(self, tmp_path: Path) -> None:
- """Nur alte Sendungen werden entfernt, neue bleiben."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Alte Show", jetzt - timedelta(days=30)),
- _programm("ard", "Neue Show", jetzt + timedelta(hours=1)),
- ]
- cache.update_programs(programme)
- removed = cache.prune_old(retention_days=14)
- assert removed == 1
- assert cache.get_stats()["program_count"] == 1
- # ============================================================================
- # EPGCache - get_program_at()
- # ============================================================================
- class TestEPGCacheGetProgramAt:
- """Tests fuer die zeitpunktbasierte Sendungsabfrage."""
- def test_programm_zur_genauen_startzeit(self, tmp_path: Path) -> None:
- """Sendung wird zur exakten Startzeit gefunden."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- start = jetzt.replace(hour=20, minute=15, second=0, microsecond=0)
- sendung = _programm("ard", "Tatort", start, 90)
- cache.update_programs([sendung])
- ergebnis = cache.get_program_at("ard", start)
- assert ergebnis is not None
- assert ergebnis["t"] == "Tatort"
- def test_programm_waehrend_ausstrahlung(self, tmp_path: Path) -> None:
- """Sendung wird waehrend der Ausstrahlung gefunden."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- start = jetzt.replace(hour=20, minute=15, second=0, microsecond=0)
- sendung = _programm("ard", "Tatort", start, 90)
- cache.update_programs([sendung])
- # 30 Minuten nach Start
- abfrage_zeit = start + timedelta(minutes=30)
- ergebnis = cache.get_program_at("ard", abfrage_zeit)
- assert ergebnis is not None
- assert ergebnis["t"] == "Tatort"
- def test_programm_nach_ende_none(self, tmp_path: Path) -> None:
- """Nach Sendungsende wird None zurueckgegeben."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- start = jetzt.replace(hour=20, minute=15, second=0, microsecond=0)
- sendung = _programm("ard", "Tatort", start, 90)
- cache.update_programs([sendung])
- # 2 Stunden nach Start (Sendung ist 90 Min)
- abfrage_zeit = start + timedelta(hours=2)
- ergebnis = cache.get_program_at("ard", abfrage_zeit)
- assert ergebnis is None
- def test_programm_falscher_sender_none(self, tmp_path: Path) -> None:
- """Falscher Sender gibt None zurueck."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- start = jetzt.replace(hour=20, minute=15, second=0, microsecond=0)
- sendung = _programm("ard", "Tatort", start, 90)
- cache.update_programs([sendung])
- ergebnis = cache.get_program_at("zdf", start)
- assert ergebnis is None
- def test_programm_leerer_sender_none(self, tmp_path: Path) -> None:
- """Sender ohne Programm gibt None zurueck."""
- cache = EPGCache(tmp_path)
- ergebnis = cache.get_program_at("ard", _jetzt())
- assert ergebnis is None
- # ============================================================================
- # EPGCache - get_primetime()
- # ============================================================================
- class TestEPGCacheGetPrimetime:
- """Tests fuer die Primetime-Abfrage (20:15)."""
- def test_primetime_findet_2015_sendung(self, tmp_path: Path) -> None:
- """Sendung um 20:15 wird als Primetime erkannt."""
- cache = EPGCache(tmp_path)
- heute = date.today()
- start = datetime(heute.year, heute.month, heute.day, 20, 15,
- tzinfo=timezone.utc)
- sendung = _programm("ard", "Tatort", start, 90)
- cache.update_programs([sendung])
- ergebnis = cache.get_primetime("ard", heute)
- assert ergebnis is not None
- assert ergebnis["t"] == "Tatort"
- def test_primetime_kein_programm_none(self, tmp_path: Path) -> None:
- """Ohne Programm um 20:15 wird None zurueckgegeben."""
- cache = EPGCache(tmp_path)
- heute = date.today()
- # Sendung um 21:00, nicht 20:15
- start = datetime(heute.year, heute.month, heute.day, 21, 0,
- tzinfo=timezone.utc)
- sendung = _programm("ard", "Spaetsendung", start, 60)
- cache.update_programs([sendung])
- ergebnis = cache.get_primetime("ard", heute)
- assert ergebnis is None
- # ============================================================================
- # EPGCache - search_title()
- # ============================================================================
- class TestEPGCacheSearchTitle:
- """Tests fuer die Titelsuche."""
- def test_suche_exakter_titel(self, tmp_path: Path) -> None:
- """Exakter Titel wird gefunden."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- ergebnisse = cache.search_title("Tatort")
- assert len(ergebnisse) >= 1
- assert any(p["t"] == "Tatort" for p in ergebnisse)
- def test_suche_teilstring(self, tmp_path: Path) -> None:
- """Teilstring-Suche findet passende Titel."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- ergebnisse = cache.search_title("Tagesschau")
- assert len(ergebnisse) >= 1
- def test_suche_kein_treffer_leere_liste(self, tmp_path: Path) -> None:
- """Ohne Treffer wird eine leere Liste zurueckgegeben."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- ergebnisse = cache.search_title("Nichtexistierender Titel XYZ")
- assert ergebnisse == []
- def test_suche_leerer_query_leere_liste(self, tmp_path: Path) -> None:
- """Leerer Suchbegriff gibt leere Liste zurueck."""
- cache = EPGCache(tmp_path)
- ergebnisse = cache.search_title("")
- assert ergebnisse == []
- def test_suche_zukunft_zuerst(self, tmp_path: Path) -> None:
- """Zukuenftige Sendungen werden vor vergangenen sortiert."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Tagesschau", jetzt - timedelta(hours=5)),
- _programm("ard", "Tagesschau", jetzt + timedelta(hours=5)),
- ]
- cache.update_programs(programme)
- ergebnisse = cache.search_title("Tagesschau")
- assert len(ergebnisse) == 2
- # Erstes Ergebnis soll in der Zukunft liegen
- assert ergebnisse[0]["s"] > _iso(jetzt)
- def test_suche_limit_begrenzt_ergebnisse(self, tmp_path: Path) -> None:
- """Der Limit-Parameter begrenzt die Ergebnis-Anzahl."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Tagesschau", jetzt + timedelta(hours=i))
- for i in range(20)
- ]
- cache.update_programs(programme)
- ergebnisse = cache.search_title("Tagesschau", limit=5)
- assert len(ergebnisse) <= 5
- # ============================================================================
- # EPGCache - search_by_tag()
- # ============================================================================
- class TestEPGCacheSearchByTag:
- """Tests fuer die Tag-basierte Suche ueber Enrichment-Daten."""
- def test_suche_nach_tag_findet_sendungen(self, tmp_path: Path) -> None:
- """Sendungen werden ueber Enrichment-Tags gefunden."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- sendung = _programm("ard", "Tatort", jetzt, 90)
- cache.update_programs([sendung])
- # Enrichment mit Tags hinzufuegen
- series_id = cache._generate_series_id("Tatort")
- cache.update_enrichment(series_id, {"tags": ["Krimi", "Spannung"]})
- ergebnisse = cache.search_by_tag("Krimi")
- assert len(ergebnisse) >= 1
- def test_suche_nach_tag_gross_kleinschreibung(self, tmp_path: Path) -> None:
- """Tag-Suche ist case-insensitive."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- sendung = _programm("ard", "Tatort", jetzt, 90)
- cache.update_programs([sendung])
- series_id = cache._generate_series_id("Tatort")
- cache.update_enrichment(series_id, {"tags": ["Krimi"]})
- ergebnisse = cache.search_by_tag("krimi")
- assert len(ergebnisse) >= 1
- def test_suche_nach_tag_kein_treffer(self, tmp_path: Path) -> None:
- """Ohne passenden Tag wird eine leere Liste zurueckgegeben."""
- cache = EPGCache(tmp_path)
- ergebnisse = cache.search_by_tag("NichtExistierenderTag")
- assert ergebnisse == []
- # ============================================================================
- # EPGCache - get_next_airing()
- # ============================================================================
- class TestEPGCacheGetNextAiring:
- """Tests fuer die naechste Ausstrahlung einer Serie."""
- def test_naechste_ausstrahlung_in_zukunft(self, tmp_path: Path) -> None:
- """Findet die naechste zukuenftige Ausstrahlung."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Tatort", jetzt - timedelta(days=7)),
- _programm("ard", "Tatort", jetzt + timedelta(days=7)),
- ]
- cache.update_programs(programme)
- series_id = cache._generate_series_id("Tatort")
- ergebnis = cache.get_next_airing(series_id)
- assert ergebnis is not None
- assert ergebnis["s"] > _iso(jetzt)
- def test_naechste_ausstrahlung_keine_zukunft_none(self, tmp_path: Path) -> None:
- """Ohne zukuenftige Ausstrahlung wird None zurueckgegeben."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Tatort", jetzt - timedelta(days=14)),
- _programm("ard", "Tatort", jetzt - timedelta(days=7)),
- ]
- cache.update_programs(programme)
- series_id = cache._generate_series_id("Tatort")
- ergebnis = cache.get_next_airing(series_id)
- assert ergebnis is None
- def test_naechste_ausstrahlung_unbekannte_serie_none(self, tmp_path: Path) -> None:
- """Unbekannte Serien-ID gibt None zurueck."""
- cache = EPGCache(tmp_path)
- ergebnis = cache.get_next_airing("unbekannte-serie")
- assert ergebnis is None
- # ============================================================================
- # EPGCache - Streaming und Enrichment
- # ============================================================================
- class TestEPGCacheStreamingUndEnrichment:
- """Tests fuer Streaming-Info und Enrichment-Abfragen."""
- def test_streaming_info_aus_enrichment(self, tmp_path: Path) -> None:
- """Streaming-Anbieter werden aus Enrichment-Daten zurueckgegeben."""
- cache = EPGCache(tmp_path)
- cache.update_enrichment("tatort", {
- "streaming": ["ARD Mediathek", "Netflix"],
- "tags": [],
- })
- info = cache.get_streaming_info("tatort")
- assert "ARD Mediathek" in info
- assert "Netflix" in info
- def test_streaming_info_ohne_enrichment_leer(self, tmp_path: Path) -> None:
- """Ohne Enrichment-Daten wird eine leere Liste zurueckgegeben."""
- cache = EPGCache(tmp_path)
- info = cache.get_streaming_info("unbekannt")
- assert info == []
- def test_get_enrichment_vorhanden(self, tmp_path: Path) -> None:
- """Enrichment-Daten werden korrekt zurueckgegeben."""
- cache = EPGCache(tmp_path)
- daten = {"tags": ["Krimi"], "streaming": ["Netflix"], "beschreibung": "Test"}
- cache.update_enrichment("tatort", daten)
- ergebnis = cache.get_enrichment("tatort")
- assert ergebnis is not None
- assert ergebnis["tags"] == ["Krimi"]
- assert ergebnis["beschreibung"] == "Test"
- def test_get_enrichment_nicht_vorhanden_none(self, tmp_path: Path) -> None:
- """Fehlende Enrichment-Daten geben None zurueck."""
- cache = EPGCache(tmp_path)
- ergebnis = cache.get_enrichment("gibts_nicht")
- assert ergebnis is None
- def test_update_enrichment_speichert_und_liest(self, tmp_path: Path) -> None:
- """Enrichment-Daten werden gespeichert und koennen abgefragt werden."""
- cache = EPGCache(tmp_path)
- cache.update_enrichment("naruto", {"tags": ["Anime"], "streaming": ["Crunchyroll"]})
- ergebnis = cache.get_enrichment("naruto")
- assert ergebnis["tags"] == ["Anime"]
- assert ergebnis["streaming"] == ["Crunchyroll"]
- def test_enrichment_roundtrip_save_load(self, tmp_path: Path) -> None:
- """Enrichment-Daten ueberstehen save/load Roundtrip."""
- cache = EPGCache(tmp_path)
- cache.update_enrichment("tatort", {"tags": ["Krimi"], "streaming": ["ARD"]})
- cache.save()
- cache2 = EPGCache(tmp_path)
- cache2.load()
- assert cache2.get_enrichment("tatort")["tags"] == ["Krimi"]
- # ============================================================================
- # EPGCache - _generate_series_id()
- # ============================================================================
- class TestEPGCacheGenerateSeriesId:
- """Tests fuer die Serien-ID-Generierung."""
- def test_slug_naruto_shippuden(self) -> None:
- """'Naruto Shippuden' wird zu 'naruto-shippuden'."""
- assert EPGCache._generate_series_id("Naruto Shippuden") == "naruto-shippuden"
- def test_slug_sonderzeichen_entfernt(self) -> None:
- """Sonderzeichen werden durch Bindestriche ersetzt."""
- assert EPGCache._generate_series_id("SOKO Leipzig!") == "soko-leipzig"
- def test_slug_leer(self) -> None:
- """Leerer Titel ergibt leeren String."""
- assert EPGCache._generate_series_id("") == ""
- def test_slug_whitespace_gestrippt(self) -> None:
- """Fuehrende und abschliessende Leerzeichen werden entfernt."""
- ergebnis = EPGCache._generate_series_id(" Tatort ")
- assert ergebnis == "tatort"
- assert not ergebnis.startswith("-")
- assert not ergebnis.endswith("-")
- # ============================================================================
- # EPGCache - _normalize_title()
- # ============================================================================
- class TestEPGCacheNormalizeTitle:
- """Tests fuer die Titel-Normalisierung."""
- def test_normalize_lowercase(self) -> None:
- """Titel wird zu Kleinbuchstaben konvertiert."""
- assert EPGCache._normalize_title("TATORT") == "tatort"
- def test_normalize_artikel_entfernt(self) -> None:
- """Fuehrende Artikel (der, die, das) werden entfernt."""
- assert EPGCache._normalize_title("Der Tatort") == "tatort"
- assert EPGCache._normalize_title("Die Sendung") == "sendung"
- assert EPGCache._normalize_title("Das Erste") == "erste"
- def test_normalize_englische_artikel(self) -> None:
- """Englische Artikel (the, a, an) werden ebenfalls entfernt."""
- assert EPGCache._normalize_title("The Walking Dead") == "walking dead"
- # ============================================================================
- # EPGCache - get_stats()
- # ============================================================================
- class TestEPGCacheGetStats:
- """Tests fuer die Statistik-Abfrage."""
- def test_stats_leerer_cache(self, tmp_path: Path) -> None:
- """Leerer Cache hat ueberall Nullwerte."""
- cache = EPGCache(tmp_path)
- stats = cache.get_stats()
- assert stats["program_count"] == 0
- assert stats["channel_count"] == 0
- assert stats["enriched_count"] == 0
- def test_stats_korrekte_programmanzahl(self, tmp_path: Path) -> None:
- """Statistik zaehlt Programme korrekt."""
- cache = EPGCache(tmp_path)
- programme = _programm_liste_heute()
- cache.update_programs(programme)
- stats = cache.get_stats()
- assert stats["program_count"] == len(programme)
- def test_stats_korrekte_senderanzahl(self, tmp_path: Path) -> None:
- """Statistik zaehlt Sender korrekt."""
- cache = EPGCache(tmp_path)
- jetzt = _jetzt()
- programme = [
- _programm("ard", "Show A", jetzt),
- _programm("zdf", "Show B", jetzt + timedelta(hours=1)),
- ]
- cache.update_programs(programme)
- stats = cache.get_stats()
- assert stats["channel_count"] == 2
- def test_stats_enrichment_anzahl(self, tmp_path: Path) -> None:
- """Statistik zaehlt Enrichments korrekt."""
- cache = EPGCache(tmp_path)
- cache.update_enrichment("tatort", {"tags": ["Krimi"]})
- cache.update_enrichment("tagesschau", {"tags": ["Nachrichten"]})
- stats = cache.get_stats()
- assert stats["enriched_count"] == 2
- def test_stats_enthaelt_alle_felder(self, tmp_path: Path) -> None:
- """Stats-Dict enthaelt alle erwarteten Felder."""
- cache = EPGCache(tmp_path)
- stats = cache.get_stats()
- erwartete_felder = {
- "program_count", "channel_count", "enriched_count",
- "reminder_count", "title_count", "series_count", "tag_count",
- }
- assert erwartete_felder.issubset(stats.keys())
|