Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)
Python 69.3%
TypeScript 16.7%
CSS 7.9%
JavaScript 4.7%
HTML 1.4%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: tests/test_yelp_scrape.py4# Desc: Connecteur yelp-scrape (avis/notes sans clé API) — parsing HORS5# LIGNE du cache Apollo embarqué dans une page de recherche yelp.ca6# (scripts data-apollo-state HTML-échappés) et croisement CONSERVATEUR7# nom + civique + ville (ambigu ou fermé = ignoré).8# ==============================================================================9import html10import json1112from restoka.connectors.yelp_scrape import (_city_key, match_business,13 parse_apollo_businesses)1415# Cache Apollo minimal, encodé comme sur yelp.ca (" dans un commentaire)16_CACHE = {17 "BusinessCategory:cat1": {"__typename": "BusinessCategory",18 "encid": "cat1", "title": "Poutineries"},19 "BusinessLocation:loc1": {20 "__typename": "BusinessLocation", "encid": "loc1",21 "address": {"__typename": "BusinessAddress",22 "city": "Quebec City",23 "addressLine1": "640 Grande Allée E"},24 "neighborhoods": [],25 },26 "Business:biz1": {27 "__typename": "Business", "encid": "biz1",28 "alias": "chez-ashton-québec-2", "name": "Chez Ashton",29 "rating": 3.5, "reviewCount": 42,30 "categories": [{"__ref": "BusinessCategory:cat1"}],31 "priceRange": {"__typename": "PriceRange", "display": "$"},32 "location": {"__ref": "BusinessLocation:loc1"},33 'activeAlert({"deviceType":"WWW"})': None,34 },35 "BusinessLocation:loc2": {36 "__typename": "BusinessLocation", "encid": "loc2",37 "address": {"__typename": "BusinessAddress",38 "city": "Levis", "addressLine1": "5430 Rue Wilfrid-Hallé"},39 "neighborhoods": [],40 },41 "Business:biz2": { # fermé définitivement -> jamais croisé42 "__typename": "Business", "encid": "biz2",43 "alias": "chez-ashton-levis", "name": "Chez Ashton",44 "rating": 4.0, "reviewCount": 3,45 "categories": [], "priceRange": None,46 "location": {"__ref": "BusinessLocation:loc2"},47 'activeAlert({"deviceType":"WWW"})': {"__typename": "BusinessAlert",48 "type": "permclosed"},49 },50}515253def _page(cache: dict) -> str:54 blob = html.escape(json.dumps(cache, ensure_ascii=False), quote=True)55 return ('<html><body><script data-apollo-state="x" '56 f'type="application/json"><!--{blob}--></script></body></html>')575859def test_parse_apollo_businesses():60 biz = parse_apollo_businesses(_page(_CACHE))61 assert len(biz) == 262 b1 = next(b for b in biz if b["alias"] == "chez-ashton-québec-2")63 assert b1["rating"] == 3.5 and b1["review_count"] == 4264 assert b1["price"] == "$" and b1["categories"] == ["Poutineries"]65 assert b1["city"] == "Quebec City"66 assert b1["address"] == "640 Grande Allée E"67 assert b1["closed"] is False68 b2 = next(b for b in biz if b["alias"] == "chez-ashton-levis")69 assert b2["closed"] is True707172def test_city_key():73 assert _city_key("Quebec City") == "quebec"74 assert _city_key("Québec") == "quebec"75 assert _city_key("Montréal") == "montreal"76 assert _city_key("Saint-Nicolas") == "saint nicolas"777879def test_match_business_conservateur():80 candidates = parse_apollo_businesses(_page(_CACHE))81 resto = {"name": "Chez Ashton", "city": "Québec",82 "address": "640 Grande Allée Est"}83 hit = match_business(resto, candidates)84 assert hit and hit["alias"] == "chez-ashton-québec-2"8586 # mauvais civique -> aucun croisement (voisin de la même rue)87 assert match_business({"name": "Chez Ashton", "city": "Québec",88 "address": "700 Grande Allée Est"},89 candidates) is None90 # le permclosed de Lévis n'est jamais retenu, même ville exacte91 assert match_business({"name": "Chez Ashton", "city": "Lévis",92 "address": "5430 Rue Wilfrid-Hallé"},93 candidates) is None94 # ambiguïté (deux commerces distincts qui passent) -> None95 cache2 = dict(_CACHE)96 cache2["Business:biz3"] = dict(_CACHE["Business:biz1"],97 encid="biz3", alias="ashton-restaurant",98 name="Restaurant Ashton")99 cands2 = parse_apollo_businesses(_page(cache2))100 assert match_business(resto, cands2) is None101