"""Tests du scraper French-Stream (séries/films VF-VOSTFR) sur fixtures réelles.""" import json import pytest from app.scrapers.base import ScrapeError, get_source, import_all_scrapers from app.scrapers.sources.french_stream import FrenchStreamScraper # ------------------------------------------------------------ fixtures SEARCH_HTML = """
Breaking Bad - Saison 2
Breaking Bad - Saison 2
Inception
Inception (2010)
Sans lien
""" SERIE_DETAILS_HTML = """

Breaking Bad - Saison 2

-12 - 2008 - Drame, Crime - 48 min

Walter et Jesse montent leur propre affaire.

Breaking Bad - Saison 2
""" FILM_DETAILS_HTML = """

Inception - 2010

Résumé du film Inception en streaming complet vf et vostfr hd vod gratuit

Dom Cobb est un voleur expérimenté.
""" EP_DATA = { "vf": { "1": {"vidzy": "https://vidzy.cc/embed-aaa.html", "uqload": "https://uqload.vc/embed-bbb.html"}, "2": {"vidzy": "https://vidzy.cc/embed-ccc.html"}, }, "vostfr": { "1": {"vidzy": "https://vidzy.cc/embed-ddd.html"}, "2": {"vidzy": "https://vidzy.cc/embed-eee.html"}, }, "vo": {}, "info": { "1": {"title": "Traqués", "synopsis": "Walt et Jesse...", "poster": "https://img/ep1.jpg"}, "2": {"title": "Chasse à l'homme"}, }, } FILM_API = { "players": { "vidzy": { "default": "https://vidzy.live/embed-fff.html", "vff": "https://vidzy.live/embed-fff.html", "vostfr": "https://vidzy.live/embed-ggg.html", } } } LATEST_HTML = """
""" def _soup(html: str): from bs4 import BeautifulSoup return BeautifulSoup(html, "lxml") # ------------------------------------------------------------ source async def test_search(monkeypatch): async def fake_fetch(url, **kwargs): assert url.endswith("/engine/ajax/search.php") assert kwargs["data"] == {"query": "breaking bad", "page": "1"} return SEARCH_HTML monkeypatch.setattr("app.scrapers.sources.french_stream.fetch", fake_fetch) results = await FrenchStreamScraper().search("breaking bad") assert len(results) == 2 serie, film = results assert serie.source_id == "9562" assert serie.media_type == "serie" assert serie.title == "Breaking Bad - Saison 2" assert serie.url == "https://french-stream.lat/9562-breaking-bad-saison-2-streaming-complet-vf-vostfr.html" assert serie.image_url == "https://image.tmdb.org/t/p/w400/bb.jpg" assert film.media_type == "film" assert film.source_id == "1022" async def test_latest(monkeypatch): async def fake_fetch_soup(url, **kwargs): assert url.endswith("/series/") return _soup(LATEST_HTML) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) results = await FrenchStreamScraper().latest() assert [(r.source_id, r.media_type) for r in results] == [ ("15137274", "serie"), ("15139056", "film"), ] assert results[0].title == "The Drop - Saison 1" async def test_browse(monkeypatch): """Parcours par genre : page du genre scrapée, type forcé sur les résultats.""" async def fake_fetch_soup(url, **kwargs): assert url.endswith("/medical-series-/") return _soup(LATEST_HTML) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) results = await FrenchStreamScraper().browse("serie", "medical") # LATEST_HTML contient un film sans « Saison » : le type est forcé à serie assert [r.media_type for r in results] == ["serie", "serie"] assert results[0].title == "The Drop - Saison 1" async def test_browse_unknown_category(): with pytest.raises(ScrapeError, match="Catégorie inconnue"): await FrenchStreamScraper().browse("film", "inexistant") async def test_get_details_serie(monkeypatch): async def fake_fetch_soup(url, **kwargs): assert "newsid=9562" in url return _soup(SERIE_DETAILS_HTML) async def fake_fetch(url, **kwargs): assert "ep-data.php?id=9562" in url return json.dumps(EP_DATA) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch", fake_fetch) details = await FrenchStreamScraper().get_details("9562") assert details.media_type == "serie" assert details.title == "Breaking Bad - Saison 2" assert details.year == 2008 assert details.genres == ["Drame", "Crime"] assert details.synopsis == "Walter et Jesse montent leur propre affaire." assert details.image_url == "https://image.tmdb.org/t/p/w400/bb.jpg" assert details.episode_count == 4 vf1 = details.episodes[0] assert (vf1.season, vf1.number, vf1.version, vf1.title) == (2, 1.0, "vf", "Traqués") assert vf1.url.endswith("#vf-1") versions = {(e.number, e.version) for e in details.episodes} assert versions == {(1.0, "vf"), (1.0, "vostfr"), (2.0, "vf"), (2.0, "vostfr")} async def test_get_details_film(monkeypatch): async def fake_fetch_soup(url, **kwargs): return _soup(FILM_DETAILS_HTML) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) details = await FrenchStreamScraper().get_details("1022") assert details.media_type == "film" assert details.title == "Inception - 2010" assert details.year == 2010 assert details.genres == ["Action", "Science-Fiction"] assert "Résumé du film" not in details.synopsis assert details.episode_count == 1 assert details.episodes[0].url.endswith("#film") async def test_list_episodes_film(monkeypatch): async def fake_fetch_soup(url, **kwargs): return _soup(FILM_DETAILS_HTML) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) episodes = await FrenchStreamScraper().list_episodes("1022") assert len(episodes) == 1 assert episodes[0].number == 1 async def test_extract_embed_links_episode(monkeypatch): async def fake_fetch(url, **kwargs): return json.dumps(EP_DATA) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch", fake_fetch) links = await FrenchStreamScraper().extract_embed_links( "https://french-stream.lat/index.php?newsid=9562#vf-1" ) assert links == ["https://vidzy.cc/embed-aaa.html", "https://uqload.vc/embed-bbb.html"] async def test_extract_embed_links_film_dedup(monkeypatch): async def fake_fetch(url, **kwargs): assert "film_api.php?id=1022" in url return json.dumps(FILM_API) monkeypatch.setattr("app.scrapers.sources.french_stream.fetch", fake_fetch) links = await FrenchStreamScraper().extract_embed_links( "https://french-stream.lat/index.php?newsid=1022#film" ) assert links == ["https://vidzy.live/embed-fff.html", "https://vidzy.live/embed-ggg.html"] async def test_extract_embed_links_bad_fragment(): with pytest.raises(ScrapeError): await FrenchStreamScraper().extract_embed_links( "https://french-stream.lat/index.php?newsid=9562#nimportequoi" ) async def test_extract_embed_links_invalid_json(monkeypatch): async def fake_fetch(url, **kwargs): return "oops" monkeypatch.setattr("app.scrapers.sources.french_stream.fetch", fake_fetch) with pytest.raises(ScrapeError): await FrenchStreamScraper().extract_embed_links( "https://french-stream.lat/index.php?newsid=9562#vf-1" ) async def test_unknown_detail_page(monkeypatch): async def fake_fetch_soup(url, **kwargs): return _soup("404") monkeypatch.setattr("app.scrapers.sources.french_stream.fetch_soup", fake_fetch_soup) with pytest.raises(ScrapeError): await FrenchStreamScraper().get_details("999999999") def test_registered(): import_all_scrapers() source = get_source("french_stream") assert source.media_types == ("serie", "film")