Spaces:
Sleeping
Sleeping
| """ | |
| test_app.py : tests de non-régression d'Avis'IA Resto | |
| Usage : python test_app.py | |
| Produit un rapport horodaté rapport_tests.txt | |
| Fonctionne avec ou sans clé API Mistral (le mode secours est testé aussi). | |
| """ | |
| import os | |
| from datetime import datetime | |
| import app | |
| RESULTATS = [] | |
| def verifier(nom, condition, detail=""): | |
| RESULTATS.append((nom, bool(condition), detail)) | |
| print(f"{'✅' if condition else '❌'} {nom}" + (f" ({detail})" if detail else "")) | |
| def main(): | |
| print("=" * 60) | |
| print("TESTS AVIS'IA RESTO,", datetime.now().strftime("%d/%m/%Y %H:%M")) | |
| print("=" * 60) | |
| mode_ia = app.client is not None | |
| # 1. Robustesse : avis vide | |
| r, m = app.generer_reponse("", 3, "Test", "Test") | |
| verifier("Avis vide refusé avec message clair", "⚠️" in m) | |
| # 2. Avis négatif : une réponse est toujours produite | |
| r_neg, m_neg = app.generer_reponse("Service très lent, plat froid, très déçu.", | |
| 1, "Le Normand Caen", "Marc, gérant") | |
| verifier("Avis négatif : réponse produite", len(r_neg) > 30) | |
| verifier("Mode affiché à l'utilisateur", ("✅" in m_neg) or ("🔶" in m_neg)) | |
| # 3. Avis positif | |
| r_pos, m_pos = app.generer_reponse("Excellente tarte normande, accueil parfait !", | |
| 5, "Le Normand Caen", "Marc, gérant") | |
| verifier("Avis positif : réponse produite", len(r_pos) > 30) | |
| # 4. Rubrique qualité : longueur, absence de promesses interdites, | |
| # personnalisation (uniquement significative en mode IA) | |
| ok_neg, detail_neg = app._controle_qualite( | |
| "Service très lent, plat froid, très déçu.", r_neg, | |
| verifier_personnalisation=("✅" in m_neg)) | |
| verifier("Qualité rubrique : avis négatif", ok_neg, detail_neg) | |
| ok_pos, detail_pos = app._controle_qualite( | |
| "Excellente tarte normande, accueil parfait !", r_pos, | |
| verifier_personnalisation=("✅" in m_pos)) | |
| verifier("Qualité rubrique : avis positif", ok_pos, detail_pos) | |
| verifier("Réponse négative différente de la réponse positive", | |
| r_neg.strip() != r_pos.strip()) | |
| # 5. Toutes les notes ont un template de secours | |
| verifier("Templates de secours pour les 5 notes", | |
| all(n in app.TEMPLATES for n in range(1, 6))) | |
| # 6. Analyse CSV multi-restaurants (onglet 2), séparateur virgule | |
| exemple = "avis_restaurant_exemple.csv" | |
| if os.path.exists(exemple): | |
| class F: | |
| name = exemple | |
| s, f1, f2, f3, f4, f5, f6, f7 = app.analyser_csv(F()) | |
| verifier("CSV multi-restaurants : analyse sans erreur", s.startswith("###")) | |
| figs_ok = all(hasattr(f, "size") and f.size[0] > 0 and f.size[1] > 0 | |
| for f in (f1, f2, f3, f4, f5, f6, f7)) | |
| verifier("Les 7 graphiques contiennent des données", figs_ok) | |
| fonds_ok = all(f.mode in ("RGB", "RGBA") for f in (f1, f2, f3, f4, f5, f6, f7)) | |
| verifier("Les 7 graphiques sont des images valides (RGB/RGBA)", fonds_ok) | |
| else: | |
| verifier("CSV multi-restaurants présent", False, "fichier introuvable") | |
| # 7. Analyse CSV, séparateur point-virgule | |
| if os.path.exists(exemple): | |
| import pandas as pd | |
| pd.read_csv(exemple).to_csv("_test_pv.csv", sep=";", index=False) | |
| class G: | |
| name = "_test_pv.csv" | |
| s2, *_ = app.analyser_csv(G()) | |
| verifier("Séparateur point-virgule détecté automatiquement", | |
| s2.startswith("###")) | |
| os.remove("_test_pv.csv") | |
| # 8. CSV d'analyse invalide (colonnes manquantes) | |
| with open("_test_ko.csv", "w", encoding="utf-8") as f: | |
| f.write("colonne_inconnue\nvaleur\n") | |
| class H: | |
| name = "_test_ko.csv" | |
| s3, *_ = app.analyser_csv(H()) | |
| verifier("CSV d'analyse invalide : message d'erreur explicite", s3.startswith("❌")) | |
| os.remove("_test_ko.csv") | |
| # 9. Traitement en lot (onglet 1, dépôt CSV) avec le CSV d'exemple | |
| lot = "avis_a_repondre_exemple.csv" | |
| if os.path.exists(lot): | |
| class I: | |
| name = lot | |
| statut_lot, apercu, chemin = app.generer_reponses_batch(I()) | |
| verifier("CSV en lot : traitement sans erreur", statut_lot.startswith("✅")) | |
| verifier("CSV en lot : une réponse par ligne", | |
| apercu is not None and len(apercu) > 0 | |
| and apercu["reponse_generee"].notna().all()) | |
| verifier("CSV en lot : fichier de sortie créé", | |
| chemin is not None and os.path.exists(chemin)) | |
| tableau_html = app._df_vers_tableau_html(apercu) | |
| verifier("Tableau HTML : vrai tableau sans coin arrondi", | |
| "<table" in tableau_html and "border-radius: 0" in tableau_html) | |
| else: | |
| verifier("CSV en lot d'exemple présent", False, "fichier introuvable") | |
| # 10. Traitement en lot : colonnes manquantes détectées proprement | |
| with open("_test_lot_ko.csv", "w", encoding="utf-8") as f: | |
| f.write("colonne_inconnue\nvaleur\n") | |
| class J: | |
| name = "_test_lot_ko.csv" | |
| statut_ko, apercu_ko, chemin_ko = app.generer_reponses_batch(J()) | |
| verifier("CSV en lot invalide : message d'erreur explicite", statut_ko.startswith("❌")) | |
| os.remove("_test_lot_ko.csv") | |
| # 11. Traitement en lot : fichier absent | |
| statut_vide, _, _ = app.generer_reponses_batch(None) | |
| verifier("CSV en lot absent : message d'avertissement", statut_vide.startswith("⚠️")) | |
| # 12. État de la clé API | |
| statut = "configurée" if mode_ia else "absente, mode secours" | |
| verifier("Gestion de la clé API sans plantage", True, statut) | |
| # Rapport horodaté | |
| ok = sum(1 for _, v, _ in RESULTATS if v) | |
| total = len(RESULTATS) | |
| with open("rapport_tests.txt", "w", encoding="utf-8") as f: | |
| f.write("RAPPORT DE TESTS AVIS'IA RESTO\n") | |
| f.write(f"Date : {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n") | |
| f.write(f"Clé API Mistral : {statut}\n") | |
| f.write(f"Résultat : {ok}/{total} tests passés\n") | |
| f.write("-" * 50 + "\n") | |
| for nom, v, detail in RESULTATS: | |
| ligne = f"[{'OK' if v else 'ECHEC'}] {nom}" | |
| if detail: | |
| ligne += f" ({detail})" | |
| f.write(ligne + "\n") | |
| print("-" * 60) | |
| print(f"RÉSULTAT : {ok}/{total} tests passés") | |
| print("Rapport écrit dans rapport_tests.txt") | |
| return 0 if ok == total else 1 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |