| … | … | @@ -158,3 +158,110 @@ def test_the_part_file_is_a_sibling_of_its_destination(): |
| 158 | 158 | dest = addon._library_path("series", "Show", "2020", "1", |
| 159 | 159 | season=1, episode=1, episode_title="T") |
| 160 | 160 | assert os.path.dirname(addon._part_path(dest)) == os.path.dirname(dest) |
| 161 | |
| 162 | |
| 163 | # --- metadata lookup -------------------------------------------------------- |
| 164 | # Naming is only safe if the identity behind it is. A title we cannot identify |
| 165 | # must NOT be ingested under a guessed name: the 2026-07-28 cleanup existed |
| 166 | # precisely because wrongly-named media had accumulated, and a bad file in the |
| 167 | # library costs more than a missing one. |
| 168 | |
| 169 | @pytest.fixture |
| 170 | def tmdb(monkeypatch): |
| 171 | """Stand in for TMDB. Tests must never reach the network.""" |
| 172 | calls = [] |
| 173 | responses = {} |
| 174 | |
| 175 | def fake(path, params=None): |
| 176 | calls.append(path) |
| 177 | if path not in responses: |
| 178 | raise RuntimeError(f"unexpected TMDB call: {path}") |
| 179 | return responses[path] |
| 180 | |
| 181 | monkeypatch.setattr(addon, "_tmdb_get", fake) |
| 182 | monkeypatch.setattr(addon, "_library_meta_cache", {}) |
| 183 | return type("T", (), {"calls": calls, "responses": responses})() |
| 184 | |
| 185 | |
| 186 | def test_series_metadata_comes_from_the_tmdb_id(tmdb): |
| 187 | tmdb.responses["tv/138502"] = {"name": "X-Men '97", "first_air_date": "2024-03-20"} |
| 188 | tmdb.responses["tv/138502/season/2/episode/7"] = {"name": "Bright Eyes"} |
| 189 | m = addon._library_meta("series", "tmdb:138502", 2, 7) |
| 190 | assert m == {"tmdb_id": "138502", "title": "X-Men '97", |
| 191 | "year": "2024", "episode_title": "Bright Eyes"} |
| 192 | |
| 193 | |
| 194 | def test_movie_metadata_comes_from_the_tmdb_id(tmdb): |
| 195 | tmdb.responses["movie/78"] = {"title": "Blade Runner", "release_date": "1982-06-25"} |
| 196 | m = addon._library_meta("movie", "tmdb:78") |
| 197 | assert m["title"] == "Blade Runner" and m["year"] == "1982" |
| 198 | assert m["episode_title"] == "" |
| 199 | |
| 200 | |
| 201 | def test_an_imdb_ident_is_translated_to_a_tmdb_id(tmdb): |
| 202 | """The cinemeta path supplies tt-ids, but the library names by TMDB id.""" |
| 203 | tmdb.responses["find/tt10310354"] = { |
| 204 | "tv_results": [{"id": 138502}], "movie_results": []} |
| 205 | tmdb.responses["tv/138502"] = {"name": "X-Men '97", "first_air_date": "2024-03-20"} |
| 206 | tmdb.responses["tv/138502/season/2/episode/7"] = {"name": "Bright Eyes"} |
| 207 | m = addon._library_meta("series", "tt10310354", 2, 7) |
| 208 | assert m["tmdb_id"] == "138502" |
| 209 | |
| 210 | |
| 211 | def test_an_unresolvable_ident_yields_no_metadata(tmdb): |
| 212 | """No id, no ingest -- rather than inventing a folder name.""" |
| 213 | tmdb.responses["find/tt00000000"] = {"tv_results": [], "movie_results": []} |
| 214 | assert addon._library_meta("series", "tt00000000", 1, 1) is None |
| 215 | |
| 216 | |
| 217 | def test_a_tmdb_failure_yields_no_metadata_rather_than_a_guess(tmdb): |
| 218 | """_tmdb_get raises for anything unstubbed, so this exercises the real |
| 219 | failure path: the lookup must swallow it and decline, not propagate.""" |
| 220 | assert addon._library_meta("series", "tmdb:999999", 1, 1) is None |
| 221 | |
| 222 | |
| 223 | def test_a_missing_episode_title_is_not_fatal(tmdb): |
| 224 | """TMDB carries the show but not always the episode name. The episode is |
| 225 | still ingestable -- Jellyfin keys on SxxExx.""" |
| 226 | tmdb.responses["tv/1"] = {"name": "Show", "first_air_date": "2020-01-01"} |
| 227 | tmdb.responses["tv/1/season/1/episode/1"] = {} |
| 228 | m = addon._library_meta("series", "tmdb:1", 1, 1) |
| 229 | assert m["episode_title"] == "" and m["title"] == "Show" |
| 230 | |
| 231 | |
| 232 | def test_a_show_with_no_air_date_still_resolves(tmdb): |
| 233 | tmdb.responses["tv/1"] = {"name": "Show"} |
| 234 | tmdb.responses["tv/1/season/1/episode/1"] = {"name": "Pilot"} |
| 235 | m = addon._library_meta("series", "tmdb:1", 1, 1) |
| 236 | assert m["year"] == "" and m["title"] == "Show" |
| 237 | |
| 238 | |
| 239 | def test_metadata_is_cached_across_calls(tmdb): |
| 240 | """Ingest asks per episode; the show lookup must not be repeated for every |
| 241 | episode of a season.""" |
| 242 | tmdb.responses["tv/1"] = {"name": "Show", "first_air_date": "2020-01-01"} |
| 243 | tmdb.responses["tv/1/season/1/episode/1"] = {"name": "Pilot"} |
| 244 | addon._library_meta("series", "tmdb:1", 1, 1) |
| 245 | before = len(tmdb.calls) |
| 246 | addon._library_meta("series", "tmdb:1", 1, 1) |
| 247 | assert len(tmdb.calls) == before |
| 248 | |
| 249 | |
| 250 | # --- one copy per episode, regardless of height ----------------------------- |
| 251 | |
| 252 | def test_an_existing_copy_at_any_height_counts_as_present(tmdb): |
| 253 | """The library holds one copy per episode. A 1080p re-request must not |
| 254 | produce a second file beside an existing 720p one -- and nothing is ever |
| 255 | deleted to make room for an upgrade.""" |
| 256 | tmdb.responses["tv/1"] = {"name": "Show", "first_air_date": "2020-01-01"} |
| 257 | tmdb.responses["tv/1/season/1/episode/1"] = {"name": "Pilot"} |
| 258 | dest = addon._library_path("series", "Show", "2020", "1", |
| 259 | season=1, episode=1, episode_title="Pilot") |
| 260 | assert addon._library_has("series", "tmdb:1", 1, 1) is False |
| 261 | os.makedirs(os.path.dirname(dest), exist_ok=True) |
| 262 | open(dest, "wb").write(b"x") |
| 263 | assert addon._library_has("series", "tmdb:1", 1, 1) is True |
| 264 | |
| 265 | |
| 266 | def test_an_unidentifiable_title_is_reported_absent_not_crashing(tmdb): |
| 267 | assert addon._library_has("series", "tmdb:999999", 1, 1) is False |