feat: refonte UX page /matches — révision carte par carte

- Remplace la table trop large par une carte de révision centrée
- Une paire à la fois : noms wrappés, score, prix moyens
- Valider/Rejeter via fetch() sans rechargement de page
- Passage automatique à la paire suivante après chaque action
- Compteurs mis à jour en temps réel (en attente/validées/rejetées)
- Message de fin avec lien vers /compare quand tout est traité
- Ajout tests pytest pour la page /matches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 16:45:51 +01:00
parent 1d8f139c7c
commit b2934ed190
4 changed files with 333 additions and 50 deletions

View File

@@ -268,3 +268,118 @@ def test_api_product_history_not_found(client_with_data):
"""GET /api/product/<inconnu>/history retourne 404."""
resp = client_with_data.get("/api/product/ProduitInexistant/history")
assert resp.status_code == 404
# ---------------------------------------------------------------------------
# Tests /matches — DB vide et avec données
# ---------------------------------------------------------------------------
def test_matches_page_empty_200(client):
"""/matches accessible même si la base est vide."""
resp = client.get("/matches")
assert resp.status_code == 200
def test_matches_page_shows_no_pending(client):
"""/matches sans données affiche un message indiquant qu'il n'y a rien à valider."""
resp = client.get("/matches")
assert resp.status_code == 200
# Le template affiche soit "Aucune paire" soit un message d'invitation
assert "match" in resp.text.lower() or "paire" in resp.text.lower()
@pytest.fixture
def db_path_with_match(db_path: Path) -> Path:
"""Base avec 1 paire fuzzy pending dans product_matches."""
conn = schema.get_connection(db_path)
try:
with conn:
conn.execute(
"INSERT INTO product_matches "
"(name_picnic, name_leclerc, score, status, created_at) "
"VALUES ('lait demi-écrémé', 'lait demi ecreme', 92.0, 'pending', '2026-01-01T00:00:00')"
)
finally:
conn.close()
return db_path
@pytest.fixture
def client_with_match(db_path_with_match: Path):
"""TestClient avec une paire fuzzy pending."""
with patch("tickettracker.config.DB_PATH", db_path_with_match):
yield TestClient(app)
def test_matches_page_shows_pending(client_with_match):
"""/matches affiche la paire pending."""
resp = client_with_match.get("/matches")
assert resp.status_code == 200
assert "lait demi" in resp.text.lower()
def test_api_match_validate_200(client_with_match, db_path_with_match):
"""POST /api/match/1/validate retourne 200 et met à jour le statut."""
resp = client_with_match.post("/api/match/1/validate")
assert resp.status_code == 200
# Vérification en base
conn = schema.get_connection(db_path_with_match)
status = conn.execute("SELECT status FROM product_matches WHERE id=1").fetchone()["status"]
conn.close()
assert status == "validated"
def test_api_match_reject_200(client_with_match, db_path_with_match):
"""POST /api/match/1/reject retourne 200 et met à jour le statut."""
resp = client_with_match.post("/api/match/1/reject")
assert resp.status_code == 200
conn = schema.get_connection(db_path_with_match)
status = conn.execute("SELECT status FROM product_matches WHERE id=1").fetchone()["status"]
conn.close()
assert status == "rejected"
def test_api_match_validate_not_found(client):
"""POST /api/match/999/validate retourne 404."""
resp = client.post("/api/match/999/validate")
assert resp.status_code == 404
def test_api_match_reject_not_found(client):
"""POST /api/match/999/reject retourne 404."""
resp = client.post("/api/match/999/reject")
assert resp.status_code == 404
def test_api_compare_includes_fuzzy_match(db_path_with_data: Path):
"""GET /api/compare retourne les fuzzy matches validés dans les résultats."""
# Insérer un fuzzy match validé
conn = schema.get_connection(db_path_with_data)
try:
with conn:
# Normaliser les articles pour avoir les données dans price_history
conn.execute(
"UPDATE items SET name_normalized = 'lait demi-écrémé' "
"WHERE name_raw = 'Lait demi-écremé'"
)
conn.execute(
"UPDATE items SET name_normalized = 'lait demi ecreme' "
"WHERE name_raw = 'LAIT DEMI ECREME'"
)
# Insérer un fuzzy match validé liant les deux noms
conn.execute(
"INSERT INTO product_matches "
"(name_picnic, name_leclerc, score, status, created_at) "
"VALUES ('lait demi-écrémé', 'lait demi ecreme', 92.0, 'validated', '2026-01-01T00:00:00')"
)
finally:
conn.close()
with patch("tickettracker.config.DB_PATH", db_path_with_data):
test_client = TestClient(app)
resp = test_client.get("/api/compare")
assert resp.status_code == 200
products = resp.json()
match_types = [p["match_type"] for p in products]
assert "fuzzy" in match_types