feat: support .eml Picnic + correction fuzzy matching

Support .eml :
  - pipeline._eml_to_html() extrait le HTML des emails Picnic
  - Déposer un .eml dans inbox/picnic/ fonctionne comme un .html
  - Pas de nouvelle dépendance (module email stdlib)
  - 5 tests ajoutés (test_eml.py)

Correction fuzzy matching :
  - Le score est maintenant calculé sur le nom seul (avant " | ")
  - Évite que les différences de marque/poids pénalisent le score
  - Résultat : 8 paires trouvées vs 0 avant la correction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 18:23:57 +01:00
parent be4d4a7076
commit 8af474c928
3 changed files with 146 additions and 2 deletions

107
tests/test_eml.py Normal file
View File

@@ -0,0 +1,107 @@
"""
Tests de l'extraction HTML depuis les fichiers .eml (pipeline._eml_to_html).
Stratégie : on construit des .eml synthétiques en mémoire (tmp_path)
sans dépendre d'un vrai mail Picnic.
"""
import pytest
from pathlib import Path
from tickettracker.pipeline import _eml_to_html
# ---------------------------------------------------------------------------
# Helpers pour construire des .eml de test
# ---------------------------------------------------------------------------
def _make_eml(tmp_path: Path, html: str, add_text_part: bool = True) -> Path:
"""Crée un fichier .eml multipart/alternative avec une partie HTML."""
boundary = "BOUNDARY123"
lines = [
"MIME-Version: 1.0",
f'Content-Type: multipart/alternative; boundary="{boundary}"',
"From: picnic@picnic.app",
"Subject: Votre commande Picnic",
"",
f"--{boundary}",
]
if add_text_part:
lines += [
"Content-Type: text/plain; charset=utf-8",
"",
"Version texte de l'email.",
"",
f"--{boundary}",
]
lines += [
"Content-Type: text/html; charset=utf-8",
"",
html,
"",
f"--{boundary}--",
]
p = tmp_path / "ticket.eml"
p.write_text("\n".join(lines), encoding="utf-8")
return p
def _make_eml_no_html(tmp_path: Path) -> Path:
"""Crée un .eml sans partie HTML (texte seul)."""
boundary = "BOUNDARY456"
content = "\n".join([
"MIME-Version: 1.0",
f'Content-Type: multipart/alternative; boundary="{boundary}"',
"",
f"--{boundary}",
"Content-Type: text/plain; charset=utf-8",
"",
"Texte seul, pas de HTML.",
"",
f"--{boundary}--",
])
p = tmp_path / "no_html.eml"
p.write_text(content, encoding="utf-8")
return p
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_eml_to_html_retourne_le_contenu_html(tmp_path):
"""_eml_to_html extrait correctement le HTML d'un .eml multipart."""
html = "<html><body><p>Commande Picnic</p></body></html>"
eml = _make_eml(tmp_path, html)
result = _eml_to_html(eml)
assert "Commande Picnic" in result
def test_eml_to_html_contient_les_balises(tmp_path):
"""Le HTML retourné contient bien les balises HTML."""
html = "<html><body><h1>Titre</h1></body></html>"
eml = _make_eml(tmp_path, html)
result = _eml_to_html(eml)
assert "<h1>" in result or "Titre" in result
def test_eml_to_html_retourne_str(tmp_path):
"""_eml_to_html retourne une chaîne de caractères."""
eml = _make_eml(tmp_path, "<html><body>test</body></html>")
result = _eml_to_html(eml)
assert isinstance(result, str)
def test_eml_to_html_sans_partie_texte(tmp_path):
"""Fonctionne aussi sur un .eml avec uniquement une partie HTML."""
html = "<html><body><p>HTML only</p></body></html>"
eml = _make_eml(tmp_path, html, add_text_part=False)
result = _eml_to_html(eml)
assert "HTML only" in result
def test_eml_to_html_leve_valueerror_si_pas_de_html(tmp_path):
"""Lève ValueError si le .eml ne contient aucune partie HTML."""
eml = _make_eml_no_html(tmp_path)
with pytest.raises(ValueError, match="Aucune partie HTML"):
_eml_to_html(eml)