Files
TicketTracker/tests/test_picnic.py

154 lines
5.0 KiB
Python
Raw Normal View History

"""
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 <strong>
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("<html><body>Rien ici.</body></html>")