Découvrir v2 : rails par type, exploration par genre

- Rails Nouveautés séparés : 🆕 animés (tri date Kitsu) et 🆕 séries & films
  (French-Stream) — chaque type garde son rail, plus d'écrasement mutuel
- Section 🎭 Explorer : chips type × genre — animés via catégories Kitsu,
  séries (/<genre>-series-/) et films (/films/<genre>/) via French-Stream ;
  état dans l'URL (/discover?t=serie&g=medical), partageable
- Scraper : browse(media_type, genre) + catalogue YAML surchargeable ;
  parsing div.short mutualisé avec les nouveautés
- Films réels retirés du mode « Animés » (ils suivent le rail séries)
- Endpoints /api/discover (latest_anime/latest_serie), /api/discover/genres,
  /api/discover/browse — parcours filtré par la préférence du compte
- Warmup démarrage : latest_by_type()
This commit is contained in:
Roman
2026-09-25 14:48:29 +00:00
parent 248ec60d35
commit d747c574b8
12 changed files with 526 additions and 84 deletions
+18
View File
@@ -132,6 +132,24 @@ async def test_latest(monkeypatch):
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