""" Tests pour le parser Picnic. Utilise le fichier samples/picnic_sample.html — vrai mail de livraison du 14 février 2026, commande 502-110-1147. Ce mail présente des corruptions QP importantes (balises cassées, attributs HTML encodés, sauts de ligne au milieu de séquences UTF-8) qui ont nécessité un travail spécifique de robustesse dans le parser. """ from datetime import date from pathlib import Path import pytest from tickettracker.parsers import picnic from tickettracker.models.receipt import Receipt SAMPLE_DIR = Path(__file__).parent.parent / "samples" PICNIC_SAMPLE = SAMPLE_DIR / "picnic_sample.html" @pytest.fixture(scope="module") def receipt() -> Receipt: """Parse le fichier sample une seule fois pour tous les tests du module.""" html = PICNIC_SAMPLE.read_text(encoding="ascii", errors="replace") return picnic.parse(html) # --------------------------------------------------------------------------- # Structure générale # --------------------------------------------------------------------------- def test_store(receipt): assert receipt.store == "picnic" def test_date(receipt): # Livraison du samedi 14 février 2026 assert receipt.date == date(2026, 2, 14) def test_order_id(receipt): assert receipt.order_id == "502-110-1147" def test_total(receipt): # Total Payé avec Paypal : 95,10 € assert receipt.total == pytest.approx(95.10) def test_nombre_articles(receipt): # 29 produits distincts dans ce ticket assert len(receipt.items) == 29 # --------------------------------------------------------------------------- # Articles clés — vérifie nom, quantité, prix total # --------------------------------------------------------------------------- def _find(receipt, name_fragment: str): """Cherche un article par fragment de nom (insensible à la casse).""" needle = name_fragment.lower() matches = [it for it in receipt.items if needle in it.name.lower()] assert matches, f"Article contenant '{name_fragment}' introuvable dans : {[i.name for i in receipt.items]}" return matches[0] def test_gerble_pepites(receipt): item = _find(receipt, "pépites chocolat") assert item.quantity == 2 assert item.total_price == pytest.approx(3.58) assert item.unit == "250 g" def test_soda_zero(receipt): # Article dont l'image avait un alt==3D"..." corrompu item = _find(receipt, "Soda zéro") assert item.quantity == 1 assert item.total_price == pytest.approx(6.95) def test_le_saunier_prix_remise(receipt): # Article soldé : prix original 3,05 € → prix réel 2,74 € # Le parser doit extraire le prix APRÈS remise item = _find(receipt, "Saunier") assert item.total_price == pytest.approx(2.74) def test_saint_eloi_mais(receipt): # Article dans une structure HTML 4-colonnes corrompue item = _find(receipt, "maïs doux") assert item.quantity == 1 assert item.total_price == pytest.approx(0.95) def test_jardin_bio(receipt): # Article avec badge qty non encadré par item = _find(receipt, "Jardin Bio") assert item.quantity == 3 assert item.total_price == pytest.approx(4.95) def test_jean_roze(receipt): item = _find(receipt, "Jean Rozé") assert item.quantity == 2 assert item.total_price == pytest.approx(12.78) def test_oignon_jaune(receipt): # Article dont l'image avait sr=c=3D"..." corrompu → src absent item = _find(receipt, "Oignon") assert item.quantity == 2 assert item.total_price == pytest.approx(4.38) assert item.unit == "500 g" def test_alfapac(receipt): # Article avec badge qty corrompu, extrait via texte brut item = _find(receipt, "Alfapac") assert item.quantity == 1 assert item.total_price == pytest.approx(2.15) # --------------------------------------------------------------------------- # Cohérence arithmétique # --------------------------------------------------------------------------- def test_somme_articles_cohérente(receipt): """La somme des articles moins le solde Picnic (-0,30 €) = total payé.""" somme = sum(it.total_price for it in receipt.items) solde_picnic = 0.30 # crédit appliqué sur la commande suivante assert somme - solde_picnic == pytest.approx(receipt.total, abs=0.02) def test_prix_unitaire_coherent(receipt): """Pour chaque article multi-unité, unit_price * qty ≈ total_price.""" for item in receipt.items: if item.quantity > 1: assert item.unit_price * item.quantity == pytest.approx( item.total_price, rel=0.01 ), f"Prix incohérent pour {item.name}" # --------------------------------------------------------------------------- # Robustesse — HTML invalide # --------------------------------------------------------------------------- def test_parse_html_minimal_lève_valueerror(): """Un HTML sans date de livraison doit lever ValueError.""" with pytest.raises(ValueError): picnic.parse("Rien ici.")