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_ubereats.py4# Desc: Connecteur Uber Eats (menus livraison + notes) — décodage HORS LIGNE5# de l'état __REACT_QUERY_STATE__ (« " » en ", « \ » en %5C),6# construction du menu standard (prix en cents -> dollars, contexte7# delivery), vérification CONSERVATRICE d'un magasin (téléphone,8# postal+civique, GPS+nom) et slugs/candidats de découverte.9# ==============================================================================10import json1112from restoka.connectors.ubereats import (UberEatsConnector, _review_count,13 build_menu, decode_state, slugify,14 store_payload, verify_store)1516STORE = {17 "title": "Chez Testo (Rosemont)",18 "uuid": "a52d9225-dfe5-4f43-ac49-19833a61628f",19 "slug": "chez-testo-rosemont",20 "citySlug": "montreal",21 "currencyCode": "CAD",22 "rating": {"ratingValue": 4.3, "reviewCount": "15000+"},23 "priceBucket": "$$",24 "phoneNumber": "+15142774385",25 "location": {26 "address": "1275, Boul. Rosemont, Montréal, QC H2S 3L9",27 "streetAddress": "1275, Boul. Rosemont",28 "city": "Montréal", "country": "CA", "postalCode": "H2S 3L9",29 "region": "QC", "latitude": 45.5366491, "longitude": -73.5936433,30 },31 "catalogSectionsMap": {32 "cat-1": [33 {"payload": {"standardItemsPayload": {34 "title": {"text": "Poutines"},35 "catalogItems": [36 {"title": "Poutine classique", "price": 1139,37 "itemDescription": "Frites, sauce, fromage"},38 {"title": "Poutine galvaude", "price": 1499,39 "itemDescription": ""},40 {"title": "Item sans prix", "price": 0,41 "itemDescription": ""},42 ]}}},43 {"payload": {"standardItemsPayload": {44 "title": {"text": "Breuvages"},45 "catalogItems": [46 {"title": "Liqueur", "price": 349,47 "itemDescription": ""},48 ]}}},49 {"payload": {"standardItemsPayload": {50 "title": {"text": "Section vide"}, "catalogItems": []}}},51 ],52 },53}545556def _encode_state(state: dict) -> str:57 """Encode comme sur ubereats.com : « " » -> \\u0022, « \\ » -> %5C."""58 blob = json.dumps(state, ensure_ascii=False)59 blob = blob.replace("\\", "%5C").replace('"', "\\u0022")60 return ('<html><script type="application/json" '61 f'id="__REACT_QUERY_STATE__">{blob}</script></html>')626364def _state() -> dict:65 return {"mutations": [], "queries": [66 {"state": {"data": {"isLoggedIn": False}},67 "queryKey": ["getUserV1", {}, None],68 "queryHash": '[\\"getUserV1\\",{},null]'},69 {"state": {"data": STORE},70 "queryKey": ["getStoreV1", {"storeUuid": STORE["uuid"]}]},71 ]}727374def test_decode_state_et_store_payload():75 page = _encode_state(_state())76 state = decode_state(page)77 assert state and len(state["queries"]) == 278 store = store_payload(state)79 assert store and store["title"] == "Chez Testo (Rosemont)"80 assert store["location"]["postalCode"] == "H2S 3L9"81 assert _review_count(store) == 15000828384def test_build_menu_delivery():85 menu = build_menu(STORE, "2026-08-18T00:00:00Z")86 assert menu["price_context"] == "delivery"87 assert menu["price_source"] == "ubereats"88 assert menu["currency"] == "CAD"89 assert [s["name"] for s in menu["sections"]] == ["Poutines", "Breuvages"]90 items = menu["sections"][0]["items"]91 assert items[0]["price"] == 11.39 # cents -> dollars92 assert items[2]["price"] is None # 0 = pas de prix93 assert menu["sections"][1]["items"][0]["price"] == 3.49949596def _row(**kw):97 base = {"uid": "osm:node/9", "name": "Chez Testo",98 "address": "1275 Boulevard Rosemont", "city": "Montréal",99 "postal_code": "H2S 3L9", "phone": "+15142774385",100 "lat": 45.5366, "lng": -73.5936}101 base.update(kw)102 return base103104105def test_verify_store_conservateur():106 assert verify_store(_row(), STORE) == "telephone"107 # sans téléphone : postal + civique108 assert verify_store(_row(phone=""), STORE) == "postal+civique"109 # sans téléphone ni postal : GPS <120 m + nom similaire110 assert verify_store(_row(phone="", postal_code=""), STORE) == "gps+nom"111 # un AUTRE resto au bout de la rue : rien ne matche -> jamais fusionné112 autre = _row(phone="+15140000000", postal_code="H2S 9Z9",113 address="2001 Boulevard Rosemont", name="Autre Bistro",114 lat=45.55, lng=-73.60)115 assert verify_store(autre, STORE) == ""116117118def test_slugify_et_candidats():119 assert slugify("McDonald's") == "mcdonalds"120 assert slugify("Café Dépôt") == "cafe-depot"121 assert slugify("Chez Testo") == "chez-testo"122 slugs = sorted(["chez-testo", "chez-testo-rosemont", "chez-testons",123 "mcdonalds-rosemont", "pizza-x"])124 cand = UberEatsConnector.candidates_for("Chez Testo", slugs)125 assert cand == ["chez-testo", "chez-testo-rosemont"]126 # nom trop court/générique : aucun candidat127 assert UberEatsConnector.candidates_for("Ora", slugs) == []128