# ----------------------------------------------------------------------------- # Food-Ka — tests du socle Flipp (circulaires backflipp.wishabi.com) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # Fixtures calquées sur de vraies réponses API (flyers 8072017/8076529, # semaine du 13 au 19 août 2026) — aucun appel réseau. # ----------------------------------------------------------------------------- import pytest from foodka.connectors._flipp import FlippConnector, _categorize from foodka.connectors.rachelle_bery import RachelleBeryConnector from foodka.connectors.richelieu import RichelieuConnector FLYER = { "id": 8072017, "merchant_id": 3372, "merchant": "Marché Richelieu", "name": "Circulaire hebdomadaire", "valid_from": "2026-08-13T00:00:00-04:00", "valid_to": "2026-08-19T23:59:59-04:00", } ITEMS = [ {"id": 1, "name": "PÊCHES ONTARIO | ONTARIO PEACHES", "brand": "ontario", "price": "5.99", "discount": 25, "cutout_image_url": "http://f.wishabi.net/page_items/1/extra_large.jpg", "valid_from": "2026-08-13T00:00:00-04:00", "valid_to": "2026-08-19T23:59:59-04:00"}, {"id": 2, "name": "YOGOURT MÉDITERRANÉE LIBERTÉ (500 g)", "brand": "Liberté", "price": "3.49", "discount": None, "cutout_image_url": None}, {"id": 3, "name": "Scene offre", "brand": None, "price": "", "discount": None, "cutout_image_url": None}, # vignette non-produit {"id": 4, "name": "maïs en épis 2 couleurs | fresh corn on the cob", "brand": None, "price": "0.3", "discount": 62, "cutout_image_url": None}, ] LISTING = {"flyers": [dict(FLYER), {"id": 999, "merchant_id": 1234, "name": "Autre marchand"}]} def _wire(connector, listing, items): """Court-circuite le réseau : rejoue les deux réponses de l'API.""" class FakeResp: def __init__(self, payload): self._payload = payload def json(self): return self._payload def fake_get(url, **kw): return FakeResp(listing if "/flyers?" in url else {"items": items}) connector.get = fake_get return connector def test_flipp_fetch_and_mapping(): products = _wire(RichelieuConnector(), LISTING, ITEMS).fetch() # la vignette sans prix est filtrée ; l'autre marchand est ignoré assert len(products) == 3 peches = products[0].finalize() assert peches.source == "richelieu" assert peches.name == "PÊCHES ONTARIO" # partie française seulement assert peches.keywords == ["ONTARIO PEACHES"] # partie anglaise en mot-clé assert peches.brand == "Ontario" # minuscules Flipp redressées assert peches.price == 5.99 assert peches.on_sale is True assert peches.category == "Fruits et légumes" assert peches.images == ["https://f.wishabi.net/page_items/1/extra_large.jpg"] assert peches.details["valid_to"] == "2026-08-19T23:59:59-04:00" assert peches.details["discount_pct"] == 25 assert peches.url == "https://www.marcherichelieu.ca/circulaire" yogourt = products[1].finalize() assert yogourt.category == "Produits laitiers et œufs" assert yogourt.size_label == "500 g" # format extrait du nom assert yogourt.unit_price == pytest.approx(0.698) mais = products[2].finalize() assert mais.price == 0.3 assert mais.category == "Fruits et légumes" def test_flipp_external_id_stable(): """Le même produit garde le même uid d'une semaine à l'autre (price_log).""" con = _wire(RichelieuConnector(), LISTING, ITEMS) first = {p.uid for p in con.fetch()} moved = [dict(i, id=i["id"] + 5000, discount=10) for i in ITEMS] second = {p.uid for p in _wire(RichelieuConnector(), LISTING, moved).fetch()} assert first == second assert "richelieu:liberte-yogourt-mediterranee-liberte-500-g-500-g" in first def test_flipp_default_category_per_flyer(): """Rachelle-Béry « Espace Santé » : Bio et santé par défaut.""" listing = {"flyers": [{"id": 8075771, "merchant_id": 3295, "name": "Espace Santé"}]} items = [{"id": 9, "name": "CHLOROPHYLLE LAND ART", "brand": "Land Art", "price": "37.99"}, {"id": 10, "name": "PRODUIT MYSTÈRE", "brand": None, "price": "9.99"}] products = _wire(RachelleBeryConnector(), listing, items).fetch() assert [p.category for p in products] == ["Bio et santé", "Bio et santé"] def test_flipp_price_guardrails(): assert FlippConnector._price("5.99") == 5.99 assert FlippConnector._price("0.3") == 0.3 assert FlippConnector._price(13.49) == 13.49 assert FlippConnector._price("") is None assert FlippConnector._price(None) is None assert FlippConnector._price("0") is None assert FlippConnector._price("abc") is None def test_categorize_word_boundaries(): # « gâteau » ne doit PAS devenir Boissons via le mot « eau » assert _categorize("GÂTEAUX HOSTESS", "") == "Boulangerie" # « vinaigre » ne doit PAS devenir Boissons via « vin » assert _categorize("VINAIGRE DE CIDRE", "") == "Garde-manger" assert _categorize("BIFTECKS FRANÇAIS ET TOURNEDOS", "") == "Viandes et volailles" assert _categorize("PRODUIT INCONNU", "") == "Autres"