SPB Git

spb/sorti-ka

Public

Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.

9.6 KB · 217 lines python
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File:   tests/test_phase4.py4# Desc:   Phase 4 (régions manquantes + billetteries) — tests des nouveaux5#         connecteurs sur fixtures réelles figées. Aucun appel réseau6#         (CLAUDE.md §18).7# ==============================================================================8from __future__ import annotations910import json11from pathlib import Path1213from sortika.connectors import CONNECTORS1415FIXTURES = Path(__file__).parent / "fixtures"161718def _no_cache(monkeypatch, connector):19    """Cache disque neutralisé : ni lecture ni écriture pendant les tests."""20    monkeypatch.setattr(connector, "load_cache", lambda: {})21    monkeypatch.setattr(connector, "save_cache", lambda cache: None)222324class _Resp:25    def __init__(self, text):26        self.text = text272829def test_phase4_registry_autodiscovers():30    for sid in ("monteregie", "centreduquebec", "chaudiereappalaches",31                "gaspesie"):32        assert sid in CONNECTORS, sid333435def test_monteregie_parses_fixture(monkeypatch):36    from sortika.connectors.monteregie import MonteregieConnector37    c = MonteregieConnector()38    _no_cache(monkeypatch, c)39    rows = json.loads(40        (FIXTURES / "monteregie_liste.json").read_text(encoding="utf-8"))41    fiche = (FIXTURES / "monteregie_fiche.html").read_text(encoding="utf-8")42    monkeypatch.setattr(c, "_list_events", lambda: rows)43    monkeypatch.setattr(c, "get", lambda url, **kw: _Resp(fiche))44    events = [e.finalize() for e in c.fetch()]45    assert len(events) == 346    ev = next(e for e in events if "PLAN D" in e.title)47    # plage FR de l'ACF → ISO ; GPS de l'ACF ; région via l'ATR48    assert (ev.start_date, ev.end_date) == ("2026-09-18", "2026-09-20")49    assert ev.lat and ev.lng and ev.region == "Montérégie"50    # fiche JSON-LD Event → adresse civique + ville (Châteauguay)51    assert ev.city == "Châteauguay" and ev.address == "15 Boulevard Maple"52    assert ev.postal_code == "J6J 3P7"53    assert ev.website.startswith("http") and ev.image.startswith("http")54    assert ev.description                       # content.rendered nettoyé555657def test_centreduquebec_parses_fixture(monkeypatch):58    from sortika.connectors.centreduquebec import CentreDuQuebecConnector59    c = CentreDuQuebecConnector()60    _no_cache(monkeypatch, c)61    liste = (FIXTURES / "centreduquebec_liste.html").read_text(encoding="utf-8")62    fiche = (FIXTURES / "centreduquebec_fiche.html").read_text(encoding="utf-8")63    monkeypatch.setattr(64        c, "get",65        lambda url, **kw: _Resp(liste if url.rstrip("/").endswith("evenements")66                                else fiche))67    events = [e.finalize() for e in c.fetch()]68    assert len(events) >= 8                     # ~10 fiches sur la liste réelle69    ev = next(e for e in events if "Inverness" in e.title)70    assert (ev.start_date, ev.end_date) == ("2026-09-01", "2026-09-06")71    assert ev.region == "Centre-du-Québec"      # dérivée de l'ATR72    assert ev.url.startswith("https://tourismecentreduquebec.com/evenements/")73    # fiche : bloc « Adresse » + og:description/og:image74    assert any(e.address for e in events)75    assert any(e.image for e in events)767778def test_chaudiereappalaches_parses_fixture(monkeypatch):79    from sortika.connectors.chaudiereappalaches import (80        ChaudiereAppalachesConnector)81    c = ChaudiereAppalachesConnector()82    _no_cache(monkeypatch, c)83    liste = (FIXTURES / "chaudiereappalaches_liste.html").read_text(84        encoding="utf-8")85    fiche = (FIXTURES / "chaudiereappalaches_fiche.html").read_text(86        encoding="utf-8")87    monkeypatch.setattr(88        c, "get",89        lambda url, **kw: _Resp(fiche if "-fr-" in url else liste))90    events = [e.finalize() for e in c.fetch()]91    # 12 fiches listées → le budget FICHE_MAX en visite 12, toutes parsées92    # sur la même fixture de fiche (dates ISO du JSON-LD Event)93    assert events94    ev = events[0]95    assert "Accordéon" in ev.title96    assert (ev.start_date, ev.end_date) == ("2026-09-04", "2026-09-06")97    assert ev.city == "Montmagny" and ev.region == "Chaudière-Appalaches"98    assert ev.address == "301, Boulevard Taché Est"99    assert ev.lat and ev.lng                    # GeoCoordinates du JSON-LD100    assert ev.venue == ""                       # « Adresse » = libellé CMS101102103def test_gaspesie_parses_fixture(monkeypatch):104    from sortika.connectors.gaspesie import GaspesieConnector105    c = GaspesieConnector()106    _no_cache(monkeypatch, c)107    liste = (FIXTURES / "gaspesie_liste.html").read_text(encoding="utf-8")108    fiche = (FIXTURES / "gaspesie_fiche.html").read_text(encoding="utf-8")109    monkeypatch.setattr(110        c, "_get_waf",111        lambda url: liste if url.rstrip("/").endswith("festival-et-evenements")112        else fiche)113    events = [e.finalize() for e in c.fetch()]114    assert events                               # fiches sans date écartées115    ev = events[0]116    assert ev.title == "Festival BleuBleu"117    # « 18 au 21 juin 2026 » (sans « du ») → plage complète, jamais inventée118    assert (ev.start_date, ev.end_date) == ("2026-06-18", "2026-06-21")119    assert ev.city == "Carleton-sur-Mer"120    assert ev.region == "Gaspésie–Îles-de-la-Madeleine"121    assert ev.description and ev.image122123124# --- billetteries (Phase 4) --------------------------------------------------125126def test_billetteries_registry_autodiscovers():127    for sid in ("ticketacces", "ovation", "tuxedobillet"):128        assert sid in CONNECTORS, sid129130131def test_ticketacces_parses_fixtures(monkeypatch):132    from sortika.connectors.ticketacces import TicketAccesConnector133    c = TicketAccesConnector()134    _no_cache(monkeypatch, c)135    portail = (FIXTURES / "ticketacces_portail.html").read_text(encoding="utf-8")136    cal = (FIXTURES / "ticketacces_calendrier.html").read_text(encoding="utf-8")137    fiche = (FIXTURES / "ticketacces_fiche.html").read_text(encoding="utf-8")138139    def fake_get(url):140        if "portail/index" in url:141            return portail142        if "calendrier" in url:143            return cal144        return fiche145    monkeypatch.setattr(c, "_get_cp1252", fake_get)146    events = [e.finalize() for e in c.fetch()]147    # le portail-fixture contient 3 orgs QC + 1 hors-Québec (exclue) ; le148    # calendrier-fixture liste 2 représentations de La Chapelle (Québec)149    assert events150    ev = next(e for e in events if "Parking" in e.title)151    assert ev.start_date == "2026-08-22" and ev.start_time == "12:30"152    assert ev.venue == "La Chapelle" and ev.city == "Québec"153    assert ev.region == "Capitale-Nationale"154    assert ev.organizer                    # nom de l'organisation du portail155    # fiche : adresse + prix plancher (45,00 $ / 25,00 $ → 25.0)156    assert ev.address == "620, avenue Plante"157    assert ev.price_min == 25.0158    assert ev.description                  # og:description de la fiche159160161def test_ticketacces_excludes_non_quebec(monkeypatch):162    from sortika.connectors.ticketacces import TicketAccesConnector163    c = TicketAccesConnector()164    portail = (FIXTURES / "ticketacces_portail.html").read_text(encoding="utf-8")165    monkeypatch.setattr(c, "_get_cp1252", lambda url: portail)166    orgs = c._orgs_quebec()167    assert "lachapellespectacles" in orgs and "agora" in orgs168    # l'annuaire réseau étiquette le NB par comtés — jamais « Québec »169    assert all("edmundston" not in d and d != "scrn" for d in orgs)170171172def test_ovation_parses_fixtures(monkeypatch):173    from sortika.connectors.ovation import OvationConnector174    c = OvationConnector()175    _no_cache(monkeypatch, c)176    allev = (FIXTURES / "ovation_allevents.html").read_text(encoding="utf-8")177    fiche = (FIXTURES / "ovation_fiche.html").read_text(encoding="utf-8")178    monkeypatch.setattr(c, "_load_orgs", lambda: [179        {"code": "00001J", "nom": "Théâtre Petit Champlain", "ville": "Québec"}])180    monkeypatch.setattr(181        c, "_get_utf8",182        lambda url: allev if "AllEvents" in url else fiche)183    events = [e.finalize() for e in c.fetch()]184    assert events185    ev = events[0]186    # fragment EventDescription : date pleine FR + heure + salle + adresse187    assert ev.title.startswith("Marjo")188    assert ev.start_date == "2026-08-26" and ev.start_time == "20:00"189    assert ev.venue == "Théâtre Petit Champlain"190    assert ev.address == "68 Rue du Petit Champlain"191    assert ev.city == "Québec" and ev.postal_code == "G1K 4H4"192    assert ev.region == "Capitale-Nationale"193    assert ev.artists == ["Marjo"]         # lien EventsByPerformer, pas inventé194    assert "musique" in ev.categories195    assert ev.description.startswith("Rockeuse")196197198def test_tuxedobillet_parses_actor_items(monkeypatch):199    import json as _json200    from sortika.connectors.tuxedobillet import TuxedoBilletConnector201    c = TuxedoBilletConnector()202    items = _json.loads(203        (FIXTURES / "tuxedobillet_items.json").read_text(encoding="utf-8"))204    monkeypatch.setattr(c, "load_cache", lambda: {"ts": 10, "items": items})205    monkeypatch.setattr(c, "save_cache", lambda cache: None)206    monkeypatch.setattr(c, "_run_actor", lambda tenants: items)207    events = [e.finalize() for e in c.fetch()]208    # 3 items : 2 du tenant configuré (espacetheatre), 1 hors config (écarté)209    assert len(events) == 2210    ev = next(e for e in events if "Calamine" in e.title)211    assert ev.start_date == "2026-09-04"   # « 4 Sep 2026 » (mois EN traduit)212    assert ev.city == "Mont-Laurier" and ev.region == "Laurentides"213    assert ev.venue == "Espace Théâtre"214    perusse = next(e for e in events if "Pérusse" in e.title)215    assert perusse.start_date == "2027-06-17"   # « 17 juin 2027 » (FR)216    assert perusse.artists == []           # sous-titre ≠ artiste garanti217
Files · bdd9e43db3e3f5542da71ef8955d61d9ffc1455d
Menu

Theme