SPB Git forge

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.

58commits 1branches 0releases
13.7 MBsize
maindefault branch
17 days agolast push
HTML 82.9% Python 15.2% TypeScript 0.9% JavaScript 0.7%
13.2 KB · 334 lines python
Raw Blame History
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File:   tests/test_atr_nouvelles.py4# Desc:   Vague ATR 2026-08 (régions touristiques non couvertes) — tests des5#         10 nouveaux connecteurs (cotenord, ilesdelamadeleine, charlevoix,6#         eeyouistchee, mauricie, laurentides, tourismelaval, quebeccite,7#         mtlorg, abitibi) sur fixtures réelles figées. Aucun appel réseau8#         (CLAUDE.md §18).9# ==============================================================================10from __future__ import annotations1112import json13from pathlib import Path1415from sortika.connectors import CONNECTORS1617FIXTURES = Path(__file__).parent / "fixtures"181920def _read(name: str) -> str:21    return (FIXTURES / name).read_text(encoding="utf-8")222324def _no_cache(monkeypatch, connector):25    """Cache disque neutralisé : ni lecture ni écriture pendant les tests."""26    monkeypatch.setattr(connector, "load_cache", lambda: {})27    monkeypatch.setattr(connector, "save_cache", lambda cache: None)282930class _Resp:31    def __init__(self, text):32        self.text = text3334    def json(self):35        return json.loads(self.text)363738def test_atr_registry_autodiscovers():39    for sid in ("cotenord", "ilesdelamadeleine", "charlevoix", "eeyouistchee",40                "mauricie", "laurentides", "tourismelaval", "quebeccite",41                "mtlorg", "abitibi"):42        assert sid in CONNECTORS, sid434445# -- Tourisme Côte-Nord --------------------------------------------------------4647def test_cotenord_parses_fixture(monkeypatch):48    from sortika.connectors.cotenord import CoteNordConnector49    c = CoteNordConnector()50    _no_cache(monkeypatch, c)51    html = _read("cotenord_liste.html")52    monkeypatch.setattr(c, "get", lambda url, **kw: _Resp(html))53    monkeypatch.setattr("sortika.connectors.cotenord.CN_FICHE_MAX", 0)54    events = c.fetch()55    assert len(events) >= 2056    corvee = next(e for e in events if "Corvée" in e.title)57    assert corvee.start_date == "2026-09-04"58    assert corvee.end_date == "2026-09-07"59    assert corvee.city == "Fermont"60    assert corvee.lat and corvee.lng61    assert "monts Groulx" in corvee.description62    assert corvee.url.endswith("/evenements-et-spectacles/")63    f = corvee.finalize()64    assert f.region == "Côte-Nord"65    assert f.is_free is True          # « Gratuit » publié par la source666768# -- Tourisme Îles-de-la-Madeleine ---------------------------------------------6970def test_ilesdelamadeleine_parses_fixture(monkeypatch):71    from sortika.connectors.ilesdelamadeleine import (72        IlesDeLaMadeleineConnector)73    c = IlesDeLaMadeleineConnector()74    _no_cache(monkeypatch, c)75    liste = _read("ilesdelamadeleine_liste.html")76    fiche = _read("ilesdelamadeleine_fiche.html")7778    def fake_get(url, **kw):79        return _Resp(fiche if "/festival-acadien/" in url else liste)80    monkeypatch.setattr(c, "get", fake_get)81    events = c.fetch()82    assert len(events) >= 883    acadien = next(e for e in events if "acadien" in e.title.lower())84    assert acadien.start_date == "2026-08-01"85    assert acadien.end_date == "2026-08-15"86    assert acadien.city == "Havre-Aubert"87    assert acadien.address == "966, route 199"88    assert acadien.postal_code == "G4T 9C7"89    assert acadien.finalize().region == "Gaspésie–Îles-de-la-Madeleine"909192def test_ilesdelamadeleine_fiche_detail():93    from sortika.connectors.ilesdelamadeleine import (94        IlesDeLaMadeleineConnector)95    detail = IlesDeLaMadeleineConnector()._parse_fiche(96        _read("ilesdelamadeleine_fiche.html"))97    assert detail["city"] == "Havre-Aubert"98    assert "Festival acadien" in detail["description"]99    assert detail["start_date"] == "2026-08-01"100101102# -- Tourisme Charlevoix (Algolia + API get-event) -----------------------------103104def test_charlevoix_parses_fixture(monkeypatch):105    from sortika.connectors.charlevoix import CharlevoixConnector106    c = CharlevoixConnector()107    _no_cache(monkeypatch, c)108    algolia = json.loads(_read("charlevoix_algolia.json"))109    getevent = json.loads(_read("charlevoix_getevent.json"))110111    def fake_post(url, payload, headers=None):112        return getevent if "get-event" in url else algolia113    monkeypatch.setattr(c, "_post_json", fake_post)114    monkeypatch.setattr("sortika.connectors.charlevoix.CHX_FICHE_MAX", 1)115    events = c.fetch()116    assert len(events) == 3117    sante = next(e for e in events if "Santé" in e.title)118    assert sante.start_date == "2026-05-17"119    assert sante.start_time == "07:30"           # 11:30 UTC → 07:30 locale120    assert sante.city == "Baie-Saint-Paul"121    assert sante.address == "15 Rue Forget"122    assert sante.postal_code == "G3Z 3G1"123    assert sante.lat and sante.lng124    f = sante.finalize()125    assert f.region == "Capitale-Nationale"126    assert f.categories                          # catégories mappées127128129def test_charlevoix_getevent_enrichment():130    from sortika.connectors.charlevoix import CharlevoixConnector131132    class _C(CharlevoixConnector):133        def _post_json(self, url, payload, headers=None):134            return json.loads(_read("charlevoix_getevent.json"))135    detail = _C()._fiche("rendez-vous-de-la-sante")136    assert detail["image"].startswith("https://cms.tourisme-charlevoix.com/")137    assert detail["website"].startswith("https://www.baiesaintpaul.com/")138    assert detail["free"] is False139140141# -- Eeyou Istchee Baie-James --------------------------------------------------142143def test_eeyouistchee_parses_fixture(monkeypatch):144    from sortika.connectors.eeyouistchee import EeyouIstcheeConnector145    c = EeyouIstcheeConnector()146    _no_cache(monkeypatch, c)147    liste = _read("eeyouistchee_liste.html")148    fiche = _read("eeyouistchee_fiche.html")149150    def fake_get(url, **kw):151        return _Resp(fiche if "/327/" in url else liste)152    monkeypatch.setattr(c, "get", fake_get)153    monkeypatch.setattr("sortika.connectors.eeyouistchee.EIB_FICHE_MAX", 1)154    events = c.fetch()155    assert len(events) >= 20156    chapais = next(e for e in events if e.title == "Chapais en fête")157    assert chapais.city == "Chapais"158    assert chapais.start_date == "2026-08-15"159    assert chapais.image.startswith(160        "https://www.eeyouistcheebaiejames.com/fichiersUploadOpt/")161    festaout = next(e for e in events if e.title == "Festival en août")162    assert festaout.start_date == "2026-07-30"163    assert festaout.end_date == "2026-08-02"164    assert festaout.finalize().region == "Nord-du-Québec"165166167def test_eeyouistchee_fiche_detail():168    from sortika.connectors.eeyouistchee import EeyouIstcheeConnector169    detail = EeyouIstcheeConnector()._parse_fiche(170        _read("eeyouistchee_fiche.html"))171    assert "Festival en août" in detail["description"]172    assert detail["postal_code"] == "G8P 1P1"173174175# -- Tourisme Mauricie (API acolyte + JSON-LD fiche) ---------------------------176177def test_mauricie_parses_fixture(monkeypatch):178    from sortika.connectors.mauricie import MauricieConnector179    c = MauricieConnector()180    _no_cache(monkeypatch, c)181    api = json.loads(_read("mauricie_events.json"))182    fiche = _read("mauricie_fiche.html")183    monkeypatch.setattr(c, "get_json", lambda url, **kw: api)184    monkeypatch.setattr(c, "get", lambda url, **kw: _Resp(fiche))185    monkeypatch.setattr("sortika.connectors.mauricie.MAU_FICHE_MAX", 1)186    events = c.fetch()187    assert len(events) == len(api["events"])188    quai = next(e for e in events if "Quai en fête" in e.title)189    assert quai.city == "Bécancour"190    assert quai.lat and quai.lng191    assert quai.image.startswith("https://cdn.footlight.io/")192    f = quai.finalize()193    assert f.region == "Centre-du-Québec"       # ville du CdQ chez l'ATR Mauricie194    assert f.start_date == "2026-08-25"195196197def test_mauricie_fiche_jsonld():198    from sortika.connectors.mauricie import MauricieConnector199    detail = MauricieConnector()._parse_fiche(_read("mauricie_fiche.html"))200    assert detail["start_date"] == "2026-08-25"201    assert detail["address"] == "100 Avenue des Nénuphars"202    assert detail["city"] == "Bécancour"203    assert detail["postal_code"] == "G9H 2S8"204    assert "Quai en fête" in detail["description"]205206207# -- Tourisme Laurentides (REST mec-events + fiche MEC) ------------------------208209def test_laurentides_parses_fixture(monkeypatch):210    from sortika.connectors.laurentides import LaurentidesConnector211    c = LaurentidesConnector()212    _no_cache(monkeypatch, c)213    rows = json.loads(_read("laurentides_liste.json"))214    fiche = _read("laurentides_fiche.html")215    monkeypatch.setattr(c, "_list_events", lambda: rows)216    monkeypatch.setattr(c, "get", lambda url, **kw: _Resp(fiche))217    monkeypatch.setattr("sortika.connectors.laurentides.LAU_FICHE_MAX", 1)218    events = c.fetch()219    assert len(events) == 2220    noel = next(e for e in events if "Noël" in e.title)221    assert noel.start_date == "2026-12-05"222    assert noel.start_time == "10:00"            # 15:00 UTC → 10:00 locale223    assert noel.end_time == "16:00"224    assert noel.venue == "Hôtel de ville à Lac-Supérieur"225    assert noel.city == "Lac-Supérieur"226    assert noel.price_label == "Gratuite"227    f = noel.finalize()228    assert f.region == "Laurentides"229    assert f.is_free is True                     # « Gratuite » → gratuit230231232def test_laurentides_mec_date():233    from sortika.connectors.laurentides import _mec_date234    assert _mec_date("Déc 05 2026") == "2026-12-05"235    assert _mec_date("Juil 1 2027") == "2027-07-01"236    assert _mec_date("") is None237238239# -- Tourisme Laval (cartes SSR FacetWP) ---------------------------------------240241def test_tourismelaval_parses_fixture(monkeypatch):242    from sortika.connectors.tourismelaval import TourismeLavalConnector243    c = TourismeLavalConnector()244    html = _read("tourismelaval_liste.html")245    calls = []246247    def fake_get(url, **kw):248        calls.append(url)249        if len(calls) > 1:                       # page 2 = mêmes cartes → stop250            return _Resp(html)251        return _Resp(html)252    monkeypatch.setattr(c, "get", fake_get)253    events = c.fetch()254    assert len(events) == 12                     # 12 cartes uniques en page 1255    quiz = next(e for e in events if e.title == "Quiz nature")256    assert quiz.start_date == "2026-08-18"257    assert quiz.end_date == "2026-08-25"258    assert quiz.city == "Laval"259    assert quiz.description.startswith("Partez à l’aventure")260    assert quiz.image261    assert quiz.finalize().region == "Laval"262263264# -- Québec cité (Algolia) -----------------------------------------------------265266def test_quebeccite_parses_fixture(monkeypatch):267    from sortika.connectors.quebeccite import QuebecCiteConnector268    c = QuebecCiteConnector()269    hits = json.loads(_read("quebeccite_algolia.json"))["hits"]270    monkeypatch.setattr(c, "_hits", lambda: hits)271    events = c.fetch()272    assert len(events) == 3273    roi = next(e for e in events if "Petit Roi" in e.title)274    assert roi.start_date == "2026-09-25"275    assert roi.start_time is None                # dates à 12:00 UTC = jour entier276    assert roi.venue == "Grand Théâtre de Québec"277    assert roi.lat and roi.lng278    assert roi.price_label.startswith("À partir de")279    f = roi.finalize()280    assert f.region == "Capitale-Nationale"281    assert f.is_free is False and f.price_min == 98.73282283284# -- Tourisme Montréal (Algolia) -----------------------------------------------285286def test_mtlorg_parses_fixture(monkeypatch):287    from sortika.connectors.mtlorg import MtlOrgConnector288    c = MtlOrgConnector()289    hits = json.loads(_read("mtlorg_algolia.json"))["hits"]290    monkeypatch.setattr(c, "_hits", lambda: hits)291    events = c.fetch()292    assert events                                # produits datés seulement293    for e in events:294        assert e.start_date                      # jamais d'événement sans date295    fmcm = next(e for e in events if "musique de chambre" in e.title)296    assert fmcm.start_date == "2026-06-09"297    assert fmcm.end_date == "2026-06-21"298    assert fmcm.url.startswith("https://www.mtl.org/fr/")299    assert fmcm.finalize().region == "Montréal"300301302def test_mtlorg_pick_range():303    from sortika.connectors.mtlorg import _pick_range304    dates = [{"start": "2025-06-01", "end": "2025-06-10"},305             {"start": "2026-09-01", "end": "2026-09-05"}]306    assert _pick_range(dates, "2026-08-25") == ("2026-09-01", "2026-09-05")307    assert _pick_range(dates, "2027-01-01") == ("2026-09-01", "2026-09-05")308    assert _pick_range([], "2026-08-25") == (None, None)309310311# -- Tourisme Abitibi-Témiscamingue (répertoire SSR via anti-bot) --------------312313def test_abitibi_parses_fixture(monkeypatch):314    from sortika.connectors.abitibi import AbitibiConnector315    c = AbitibiConnector()316    html = _read("abitibi_liste.html")317    calls = []318319    def fake_get(url, **kw):320        calls.append(url)321        return _Resp(html)322    monkeypatch.setattr(c, "get", fake_get)323    events = c.fetch()324    assert len(events) == 12                     # 12 cartes uniques en page 1325    fme = next(e for e in events if "musique émergente" in e.title)326    assert fme.start_date == "2026-09-03"327    assert fme.end_date == "2026-09-06"328    assert fme.city == "Rouyn-Noranda"329    assert fme.image.startswith("https://mto.media.tourinsoft.eu/")330    assert fme.finalize().region == "Abitibi-Témiscamingue"331    beltane = next(e for e in events if "Beltane" in e.title)332    assert beltane.start_date == "2026-08-26"333    assert beltane.end_date == "2026-08-30"334