# ============================================================================== # Author: Simon-Pierre Boucher # File: tests/test_ubereats.py # Desc: Connecteur Uber Eats (menus livraison + notes) — décodage HORS LIGNE # de l'état __REACT_QUERY_STATE__ (« " » en ", « \ » en %5C), # construction du menu standard (prix en cents -> dollars, contexte # delivery), vérification CONSERVATRICE d'un magasin (téléphone, # postal+civique, GPS+nom) et slugs/candidats de découverte. # ============================================================================== import json from restoka.connectors.ubereats import (UberEatsConnector, _review_count, build_menu, decode_state, slugify, store_payload, verify_store) STORE = { "title": "Chez Testo (Rosemont)", "uuid": "a52d9225-dfe5-4f43-ac49-19833a61628f", "slug": "chez-testo-rosemont", "citySlug": "montreal", "currencyCode": "CAD", "rating": {"ratingValue": 4.3, "reviewCount": "15000+"}, "priceBucket": "$$", "phoneNumber": "+15142774385", "location": { "address": "1275, Boul. Rosemont, Montréal, QC H2S 3L9", "streetAddress": "1275, Boul. Rosemont", "city": "Montréal", "country": "CA", "postalCode": "H2S 3L9", "region": "QC", "latitude": 45.5366491, "longitude": -73.5936433, }, "catalogSectionsMap": { "cat-1": [ {"payload": {"standardItemsPayload": { "title": {"text": "Poutines"}, "catalogItems": [ {"title": "Poutine classique", "price": 1139, "itemDescription": "Frites, sauce, fromage"}, {"title": "Poutine galvaude", "price": 1499, "itemDescription": ""}, {"title": "Item sans prix", "price": 0, "itemDescription": ""}, ]}}}, {"payload": {"standardItemsPayload": { "title": {"text": "Breuvages"}, "catalogItems": [ {"title": "Liqueur", "price": 349, "itemDescription": ""}, ]}}}, {"payload": {"standardItemsPayload": { "title": {"text": "Section vide"}, "catalogItems": []}}}, ], }, } def _encode_state(state: dict) -> str: """Encode comme sur ubereats.com : « " » -> \\u0022, « \\ » -> %5C.""" blob = json.dumps(state, ensure_ascii=False) blob = blob.replace("\\", "%5C").replace('"', "\\u0022") return ('') def _state() -> dict: return {"mutations": [], "queries": [ {"state": {"data": {"isLoggedIn": False}}, "queryKey": ["getUserV1", {}, None], "queryHash": '[\\"getUserV1\\",{},null]'}, {"state": {"data": STORE}, "queryKey": ["getStoreV1", {"storeUuid": STORE["uuid"]}]}, ]} def test_decode_state_et_store_payload(): page = _encode_state(_state()) state = decode_state(page) assert state and len(state["queries"]) == 2 store = store_payload(state) assert store and store["title"] == "Chez Testo (Rosemont)" assert store["location"]["postalCode"] == "H2S 3L9" assert _review_count(store) == 15000 def test_build_menu_delivery(): menu = build_menu(STORE, "2026-08-18T00:00:00Z") assert menu["price_context"] == "delivery" assert menu["price_source"] == "ubereats" assert menu["currency"] == "CAD" assert [s["name"] for s in menu["sections"]] == ["Poutines", "Breuvages"] items = menu["sections"][0]["items"] assert items[0]["price"] == 11.39 # cents -> dollars assert items[2]["price"] is None # 0 = pas de prix assert menu["sections"][1]["items"][0]["price"] == 3.49 def _row(**kw): base = {"uid": "osm:node/9", "name": "Chez Testo", "address": "1275 Boulevard Rosemont", "city": "Montréal", "postal_code": "H2S 3L9", "phone": "+15142774385", "lat": 45.5366, "lng": -73.5936} base.update(kw) return base def test_verify_store_conservateur(): assert verify_store(_row(), STORE) == "telephone" # sans téléphone : postal + civique assert verify_store(_row(phone=""), STORE) == "postal+civique" # sans téléphone ni postal : GPS <120 m + nom similaire assert verify_store(_row(phone="", postal_code=""), STORE) == "gps+nom" # un AUTRE resto au bout de la rue : rien ne matche -> jamais fusionné autre = _row(phone="+15140000000", postal_code="H2S 9Z9", address="2001 Boulevard Rosemont", name="Autre Bistro", lat=45.55, lng=-73.60) assert verify_store(autre, STORE) == "" def test_slugify_et_candidats(): assert slugify("McDonald's") == "mcdonalds" assert slugify("Café Dépôt") == "cafe-depot" assert slugify("Chez Testo") == "chez-testo" slugs = sorted(["chez-testo", "chez-testo-rosemont", "chez-testons", "mcdonalds-rosemont", "pizza-x"]) cand = UberEatsConnector.candidates_for("Chez Testo", slugs) assert cand == ["chez-testo", "chez-testo-rosemont"] # nom trop court/générique : aucun candidat assert UberEatsConnector.candidates_for("Ora", slugs) == []