# ============================================================================== # Author: Simon-Pierre Boucher # File: tests/test_normalize.py # Desc: Tests de la couche de normalisation (dates, prix, catégories, régions) # ============================================================================== from __future__ import annotations from sortika.normalize import (artists_from_title, clean_city, clean_text, map_categories, parse_date_iso, parse_latlng, parse_price, parse_time, utc_to_local) from sortika.regions import region_from_city, region_from_tourist from sortika.schema import Event def test_parse_date_iso(): assert parse_date_iso("2026-09-11T00:00:00") == "2026-09-11" assert parse_date_iso("2026-09-11") == "2026-09-11" assert parse_date_iso("") is None assert parse_date_iso("demain") is None assert parse_date_iso("0001-99-99") is None def test_parse_time(): assert parse_time("17 août 2026, 20h00") == "20:00" assert parse_time("19h30") == "19:30" assert parse_time("15 h 00") == "15:00" assert parse_time("19:30:00") == "19:30" assert parse_time("2026-09-11T19:30:00") == "19:30" assert parse_time("17 août 2026") is None assert parse_time("") is None assert parse_time(None) is None def test_utc_to_local(): # hiver (UTC-5) et été (UTC-4), vérifiés sur les flux Laval/Sherbrooke assert utc_to_local("2025-02-13T23:30:00") == ("2025-02-13", "18:30") assert utc_to_local("2026-08-01T22:30:00Z") == ("2026-08-01", "18:30") # minuit local = placeholder « toute la journée » → pas d'heure assert utc_to_local("2026-07-10T04:00:00Z") == ("2026-07-10", None) assert utc_to_local(None) == (None, None) def test_artists_from_title(): # patron sûr : suffixe = salle connue de l'événement assert artists_from_title("Les Cowboys Fringants - Club Soda", "Club Soda") == ["Les Cowboys Fringants"] # patron sûr : « X en concert / en spectacle » assert artists_from_title("Marc Dupré en spectacle", "") == ["Marc Dupré"] # ambigu ou événement → jamais d'artiste inventé assert artists_from_title("Festival d'été de Québec", "") == [] assert artists_from_title("Soirée humour - Club Soda", "Club Soda") == [] assert artists_from_title("Un artiste - Une autre salle", "Club Soda") == [] assert artists_from_title("", "") == [] def test_parse_price(): assert parse_price("Gratuit") == (True, None, "Gratuit") assert parse_price("Entrée libre") == (True, None, "Entrée libre") free, amt, _ = parse_price("Adulte 25 $, enfant 12,50 $") assert free is False and amt == 12.50 assert parse_price("Payant")[0] is False assert parse_price("")[0] is None def test_map_categories(): assert map_categories(["Festival"]) == ["festival"] assert map_categories(["Manifestation sportive"]) == ["sport"] assert map_categories(["Chanson", "Sports et plein air"]) == ["musique", "sport"] assert map_categories(["Conseil et comité"]) == ["communautaire"] assert map_categories(["Zzz-inconnu"]) == ["autre"] assert map_categories([]) == ["autre"] def test_regions(): assert region_from_tourist("Cantons-de-l'Est") == "Estrie" assert region_from_tourist("Charlevoix") == "Capitale-Nationale" assert region_from_tourist("Québec") == "Capitale-Nationale" assert region_from_city("Lévis") == "Chaudière-Appalaches" assert region_from_city("Gatineau") == "Outaouais" assert region_from_city("Ville-Inconnue") == "" def test_clean(): assert clean_city("Montréal (Québec)") == "Montréal" assert clean_text("a b c") == "a b c" def test_parse_latlng(): assert parse_latlng("45.5", "-73.5") == (45.5, -73.5) assert parse_latlng("0", "0") == (None, None) assert parse_latlng(None, None) == (None, None) def test_event_finalize_and_dedup(): a = Event(source="sitq", external_id="X1", url="https://a", title=" Festival d'été ", city="Québec (Québec)", tourist_region="Québec", raw_categories=["Festival"], start_date="2026-07-01T00:00:00", price_label="Gratuit").finalize() b = Event(source="montreal", external_id="Y2", url="https://b", title="Festival d'été", city="Québec", start_date="2026-07-01").finalize() assert a.title == "Festival d'été" assert a.region == "Capitale-Nationale" assert a.categories == ["festival"] assert a.is_free is True assert a.end_date == "2026-07-01" assert a.dedup_key() == b.dedup_key() def test_canonical_city_mamh(): """Phase 2 : les graphies sans accents des sources sont ramenées au nom officiel du Répertoire MAMH (facettes ville unifiées).""" from sortika.normalize import canonical_city assert canonical_city("Montreal") == "Montréal" assert canonical_city("Quebec") == "Québec" assert canonical_city("Levis") == "Lévis" assert canonical_city("Trois-Rivieres") == "Trois-Rivières" assert canonical_city("MONTRÉAL") == "Montréal" assert canonical_city("Toronto") == "Toronto" # hors répertoire : intact assert clean_city("Montreal, Québec") == "Montréal" def test_normalize_status(): from sortika.schema import normalize_status assert normalize_status("cancelled") == "cancelled" assert normalize_status("ANNULÉ") == "cancelled" assert normalize_status("sold_out") == "soldout" assert normalize_status("postponed") == "postponed" assert normalize_status("rescheduled") == "postponed" assert normalize_status("buy_now") == "" assert normalize_status("") == "" assert normalize_status(None) == ""