SPB Git forge

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

52commits 1branches 0releases
11.6 MBsize
maindefault branch
19 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%
5.3 KB · 128 lines python
Raw Blame History
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