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_doordash.py4# Desc: Connecteur DoorDash (enrichissement livraison) — parsing JSON-LD5# (Restaurant + Menu, sections imbriquées, prix « $X.XX »), croisement6# CONSERVATEUR verify_store (gps+nom / civique+ville+nom, jamais de7# fusion hasardeuse), slugs candidats par préfixe.8# ==============================================================================9import json1011from restoka.connectors.doordash import (DoorDashConnector, build_menu,12 parse_ldjson, slugify, verify_store)1314LD_RESTO = {15 "@type": "Restaurant",16 "name": "Chez Test",17 "address": {"streetAddress": "123 Rue Principale",18 "addressLocality": "Montréal", "addressRegion": "QC"},19 "geo": {"latitude": "45.5200", "longitude": "-73.5800"},20 "servesCuisine": ["Pizza"],21 "priceRange": "$$",22 "aggregateRating": {"ratingValue": 4.6, "reviewCount": 210},23}2425LD_MENU = {26 "@type": "Menu",27 "hasMenuSection": [[{28 "name": "Pizzas",29 "hasMenuItem": [30 {"name": "Margherita", "description": "Tomate, basilic",31 "offers": {"price": "$16.50"}},32 {"name": "Sans prix", "offers": {}},33 ],34 }, {35 "name": "Pizzas", # doublon : ignoré36 "hasMenuItem": [{"name": "Dup", "offers": {"price": "$1.00"}}],37 }]],38}394041def _page(*blocks) -> str:42 return "".join(43 f'<script type="application/ld+json">{json.dumps(b)}</script>'44 for b in blocks)454647def test_parse_ldjson_et_build_menu():48 resto, menu = parse_ldjson(_page(LD_RESTO, LD_MENU))49 assert resto["name"] == "Chez Test"50 m = build_menu(menu, "2026-08-25T00:00:00Z")51 assert m["price_context"] == "delivery" and m["price_source"] == "doordash"52 assert len(m["sections"]) == 1 # doublon de section écarté53 items = m["sections"][0]["items"]54 assert items[0]["price"] == 16.5055 assert items[1]["price"] is None # prix absent toléré56 assert build_menu({}, "x") is None575859def test_parse_ldjson_page_sans_blocs():60 assert parse_ldjson("<html>rien</html>") == (None, None)616263def _row(**over):64 base = {"uid": "osm:1", "name": "Chez Test",65 "address": "123 Rue Principale", "city": "Montréal",66 "postal_code": "H2X1Y4", "phone": "",67 "lat": 45.5201, "lng": -73.5801}68 base.update(over)69 return base707172def test_verify_store_gps_nom():73 assert verify_store(_row(), LD_RESTO) == "gps+nom"747576def test_verify_store_civique_ville_nom():77 # GPS absent côté base : retombe sur civique + ville + nom78 assert verify_store(_row(lat=None, lng=None), LD_RESTO) \79 == "civique+ville+nom"808182def test_verify_store_rejets():83 # autre resto au même endroit : nom différent -> rejet84 autre = dict(LD_RESTO, name="Pizzeria Autre Chose Complètement")85 assert verify_store(_row(), autre) == ""86 # même nom mais loin (>120 m) et civique différent -> rejet87 loin = dict(LD_RESTO, geo={"latitude": "45.60", "longitude": "-73.70"},88 address={"streetAddress": "999 Autre Rue",89 "addressLocality": "Laval"})90 assert verify_store(_row(), loin) == ""919293def test_slugify_et_candidats():94 assert slugify("Café L'Époque & Fils") == "cafe-lepoque-fils"95 slugs = sorted(["chez-test-montreal-123", "chez-test-laval-456",96 "chez-testeur-999", "autre-resto-1"])97 cand = DoorDashConnector.candidates_for("Chez Test", slugs)98 assert set(cand) == {"chez-test-montreal-123", "chez-test-laval-456"}99 assert DoorDashConnector.candidates_for("Ab", slugs) == [] # trop court100