"""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 = """
"""
SERIE_DETAILS_HTML = """
Breaking Bad - Saison 2
-12 - 2008 -
Drame, Crime - 48 min
Walter et Jesse montent leur propre affaire.
"""
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")