| … | … | @@ -3,7 +3,7 @@ from unittest.mock import MagicMock |
| 3 | 3 | |
| 4 | 4 | from lib import search |
| 5 | 5 | from lib.api.stremio.addon_manager import AddonManager |
| 6 | | from lib.api.stremio.models import Meta, MetaPreview |
| 6 | from lib.api.stremio.models import Meta, MetaPreview, Stream |
| 7 | 7 | from lib.clients.stremio import addon_client, addon_selection, catalog_menus, helpers |
| 8 | 8 | from lib.clients.stremio.catalog_menus import CATALOG_PAGE_SIZE |
| 9 | 9 | from lib.clients.stremio.constants import ( |
| … | … | @@ -1272,26 +1272,35 @@ def test_add_meta_items_resolves_tmdb_id_from_imdb_for_context_menu(monkeypatch) |
| 1272 | 1272 | assert menu_labels.count("Open Settings") == 1 |
| 1273 | 1273 | |
| 1274 | 1274 | |
| 1275 | def _catalog_stream(**overrides): |
| 1276 | """A REAL Stream, the way addon_client builds one. |
| 1277 | |
| 1278 | This used to be `type("Stream", (), {...})()` -- an ad-hoc object carrying |
| 1279 | four attributes and none of the model's behaviour. It drifted the moment the |
| 1280 | fork plumbed behaviorHints.proxyHeaders through playback: production called |
| 1281 | `stream.get_proxy_headers()`, the stub had no such method, and the test |
| 1282 | failed with AttributeError on code that was working correctly in the field. |
| 1283 | |
| 1284 | Building the real model means the test cannot pass while the production |
| 1285 | object it stands for has moved on. |
| 1286 | """ |
| 1287 | data = { |
| 1288 | "title": "Movie Stream", |
| 1289 | "description": "Stream plot", |
| 1290 | "url": "https://video", |
| 1291 | "infoHash": "", |
| 1292 | } |
| 1293 | data.update(overrides) |
| 1294 | return Stream.from_dict(data) |
| 1295 | |
| 1296 | |
| 1275 | 1297 | def test_list_stremio_movie_builds_enriched_play_media_payload(monkeypatch): |
| 1276 | 1298 | captured = {} |
| 1277 | 1299 | |
| 1278 | 1300 | monkeypatch.setattr( |
| 1279 | 1301 | catalog_menus, |
| 1280 | 1302 | "catalogs_get_cache", |
| 1281 | | lambda *args, **kwargs: { |
| 1282 | | "streams": [ |
| 1283 | | type( |
| 1284 | | "Stream", |
| 1285 | | (), |
| 1286 | | { |
| 1287 | | "title": "Movie Stream", |
| 1288 | | "description": "Stream plot", |
| 1289 | | "url": "https://video", |
| 1290 | | "infoHash": "", |
| 1291 | | }, |
| 1292 | | )() |
| 1293 | | ] |
| 1294 | | }, |
| 1303 | lambda *args, **kwargs: {"streams": [_catalog_stream()]}, |
| 1295 | 1304 | ) |
| 1296 | 1305 | monkeypatch.setattr(catalog_menus, "notification", lambda *args, **kwargs: None) |
| 1297 | 1306 | monkeypatch.setattr(catalog_menus, "end_of_directory", lambda *args, **kwargs: None) |
| … | … | @@ -1334,5 +1343,54 @@ def test_list_stremio_movie_builds_enriched_play_media_payload(monkeypatch): |
| 1334 | 1343 | "catalog_type": "movie", |
| 1335 | 1344 | "meta_id": "custom:movie", |
| 1336 | 1345 | "url": "https://video", |
| 1346 | # Present and empty: a direct stream always carries the key, so a |
| 1347 | # regression that stopped plumbing headers shows up as a missing key |
| 1348 | # rather than as an empty dict that looks the same as "none needed". |
| 1349 | "proxyHeaders": {}, |
| 1337 | 1350 | "type": catalog_menus.IndexerType.DIRECT, |
| 1338 | 1351 | } |
| 1352 | |
| 1353 | |
| 1354 | def test_list_stremio_movie_carries_proxy_headers_to_playback(monkeypatch): |
| 1355 | """The reason this fork exists. |
| 1356 | |
| 1357 | Hotlink-gated CDNs 403 every segment without the Referer the addon |
| 1358 | advertised, so behaviorHints.proxyHeaders has to survive all the way into |
| 1359 | the playback payload. Nothing at this level covered it, which is how the |
| 1360 | stub above went stale without anyone noticing. |
| 1361 | """ |
| 1362 | captured = {} |
| 1363 | stream = _catalog_stream( |
| 1364 | behaviorHints={ |
| 1365 | "notWebReady": True, |
| 1366 | # Stremio nests these under `request`; the model flattens them. |
| 1367 | "proxyHeaders": {"request": {"Referer": "https://vidcore.net/", |
| 1368 | "User-Agent": "Mozilla/5.0"}}, |
| 1369 | } |
| 1370 | ) |
| 1371 | monkeypatch.setattr(catalog_menus, "catalogs_get_cache", |
| 1372 | lambda *args, **kwargs: {"streams": [stream]}) |
| 1373 | monkeypatch.setattr(catalog_menus, "notification", lambda *args, **kwargs: None) |
| 1374 | monkeypatch.setattr(catalog_menus, "end_of_directory", lambda *args, **kwargs: None) |
| 1375 | monkeypatch.setattr(catalog_menus, "make_list_item", lambda label="", path="": MagicMock()) |
| 1376 | monkeypatch.setattr(catalog_menus, "addDirectoryItem", lambda *args, **kwargs: None) |
| 1377 | |
| 1378 | def _build_url(action, **kwargs): |
| 1379 | captured["data"] = json.loads(kwargs["data"]) |
| 1380 | return "plugin://test" |
| 1381 | |
| 1382 | monkeypatch.setattr(catalog_menus, "build_url", _build_url) |
| 1383 | |
| 1384 | catalog_menus.list_stremio_movie( |
| 1385 | { |
| 1386 | "addon_url": "https://example.com/addon", |
| 1387 | "catalog_type": "movie", |
| 1388 | "meta_id": "custom:movie", |
| 1389 | "ids": json.dumps({"tmdb_id": "100"}), |
| 1390 | } |
| 1391 | ) |
| 1392 | |
| 1393 | assert captured["data"]["proxyHeaders"] == { |
| 1394 | "Referer": "https://vidcore.net/", |
| 1395 | "User-Agent": "Mozilla/5.0", |
| 1396 | } |