Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.
HTML 82.9%
Python 15.2%
TypeScript 0.9%
JavaScript 0.7%
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