diff --git a/.DS_Store b/.DS_Store index 04a508e6f..ed2a65c8b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 2cba99d87..cefb4c172 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ bin include lib .Python -tests/ + .envrc __pycache__ \ No newline at end of file diff --git a/CACHEDIR.TAG b/CACHEDIR.TAG new file mode 100644 index 000000000..837feeff9 --- /dev/null +++ b/CACHEDIR.TAG @@ -0,0 +1,4 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by Python virtualenv. +# For information about cache directory tags, see: +# https://bford.info/cachedir/ \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/booking_manager/__init__.py b/app/booking_manager/__init__.py new file mode 100644 index 000000000..7fa7d72a8 --- /dev/null +++ b/app/booking_manager/__init__.py @@ -0,0 +1,4 @@ +from .booking_service import BookingService +from .club_manager import ClubManager +from .competition_manager import CompetitionManager +from .data_loader import JSONDataLoader diff --git a/app/booking_manager/booking_service.py b/app/booking_manager/booking_service.py new file mode 100644 index 000000000..f43714884 --- /dev/null +++ b/app/booking_manager/booking_service.py @@ -0,0 +1,32 @@ +from app.booking_manager.club_manager import ClubManager +from app.booking_manager.competition_manager import CompetitionManager + + +class BookingService: + """ + Ordonne le processus de réservation en utilisant les gestionnaires de clubs et de compétitions. + """ + + def __init__(self, clubs_file: str, competitions_file: str): + self.club_manager = ClubManager(clubs_file) + self.competition_manager = CompetitionManager(competitions_file) + + def purchase_places(self, club_name: str, competition_name: str, places_requested: int) -> bool: + club = self.club_manager.find_by_name(club_name) + competition = self.competition_manager.find_by_name(competition_name) + + if not club or not competition: + return False + + if places_requested > 12: + return False + + if places_requested > club.points: + return False + + if places_requested > competition.number_of_places: + return False + + competition.number_of_places -= places_requested + club.points -= places_requested + return True diff --git a/app/booking_manager/club_manager.py b/app/booking_manager/club_manager.py new file mode 100644 index 000000000..44755bacd --- /dev/null +++ b/app/booking_manager/club_manager.py @@ -0,0 +1,32 @@ +from typing import List, Optional +from app.models import Club +from .data_loader import JSONDataLoader + + +class ClubManager: + """ + Gère le chargement et la recherche des clubs. + """ + + def __init__(self, clubs_file: str): + loader = JSONDataLoader(clubs_file) + data = loader.load_data() + self.clubs: List[Club] = self._parse_clubs(data) + + def _parse_clubs(self, data: dict) -> List[Club]: + clubs = [] + for c in data.get("clubs", []): + clubs.append( + Club( + name=c["name"], + email=c["email"], + points=int(c["points"]) + ) + ) + return clubs + + def find_by_email(self, email: str): + return next((club for club in self.clubs if club.email == email), None) + + def find_by_name(self, name: str): + return next((club for club in self.clubs if club.name == name), None) diff --git a/app/booking_manager/competition_manager.py b/app/booking_manager/competition_manager.py new file mode 100644 index 000000000..520f16da7 --- /dev/null +++ b/app/booking_manager/competition_manager.py @@ -0,0 +1,29 @@ +from typing import List, Optional +from app.models import Competition +from .data_loader import JSONDataLoader + + +class CompetitionManager: + """ + Gère le chargement et la recherche des compétitions. + """ + + def __init__(self, competitions_file: str): + loader = JSONDataLoader(competitions_file) + data = loader.load_data() + self.competitions: List[Competition] = self._parse_competitions(data) + + def _parse_competitions(self, data: dict) -> List[Competition]: + competitions = [] + for c in data.get("competitions", []): + competitions.append( + Competition( + name=c["name"], + date=c["date"], + number_of_places=int(c["numberOfPlaces"]) + ) + ) + return competitions + + def find_by_name(self, name: str) -> Optional[Competition]: + return next((comp for comp in self.competitions if comp.name == name), None) diff --git a/app/booking_manager/data_loader.py b/app/booking_manager/data_loader.py new file mode 100644 index 000000000..73ec2689f --- /dev/null +++ b/app/booking_manager/data_loader.py @@ -0,0 +1,19 @@ +import json +import os +from typing import Any + + +class JSONDataLoader: + """ + Classe générique pour charger des données depuis un fichier JSON. + """ + + def __init__(self, filepath: str): + self.filepath = filepath + + def load_data(self) -> Any: + if not os.path.exists(self.filepath): + raise FileNotFoundError(f"Fichier introuvable : {self.filepath}") + with open(self.filepath, "r") as f: + data = json.load(f) + return data diff --git a/app/models.py b/app/models.py new file mode 100644 index 000000000..9a7d96d8a --- /dev/null +++ b/app/models.py @@ -0,0 +1,14 @@ +# app/models.py + +class Competition: + def __init__(self, name, date, number_of_places): + self.name = name + self.date = date + self.number_of_places = number_of_places + + +class Club: + def __init__(self, name, email, points): + self.name = name + self.email = email + self.points = points diff --git a/app/server.py b/app/server.py new file mode 100644 index 000000000..e119af1bf --- /dev/null +++ b/app/server.py @@ -0,0 +1,69 @@ +from flask import Flask, render_template, request, redirect, flash, url_for +from app.booking_manager import BookingService + +app = Flask(__name__, static_folder="../static") +app.secret_key = "secret_key_xyz" + +# Instanciation du service de réservation +booking_service = BookingService( + clubs_file="data/clubs.json", + competitions_file="data/competitions.json" +) + + +@app.route("/") +def index(): + return render_template("index.html") + + +@app.route("/showSummary", methods=["POST"]) +def show_summary(): + email = request.form.get("email", "") + club = booking_service.club_manager.find_by_email(email) + if not club: + flash("Email inconnu ou invalide.") + return redirect(url_for("index")) + return render_template("welcome.html", club=club, competitions=booking_service.competition_manager.competitions) + + +@app.route("/book//") +def book(competition, club): + found_competition = booking_service.competition_manager.find_by_name( + competition) + found_club = booking_service.club_manager.find_by_name(club) + if not found_competition or not found_club: + flash("Something went wrong - please try again") + return redirect(url_for("index")) + return render_template("booking.html", club=found_club, competition=found_competition) + + +@app.route("/purchasePlaces", methods=["POST"]) +def purchase_places(): + competition_name = request.form.get("competition") + club_name = request.form.get("club") + places_str = request.form.get("places") + try: + places_requested = int(places_str) + except ValueError: + flash("Le nombre de places est invalide.") + return redirect(url_for("index")) + + success = booking_service.purchase_places( + club_name, competition_name, places_requested) + if success: + flash("Great-booking complete!") + else: + flash("Impossible de réserver ces places (Règle non respectée).") + + club = booking_service.club_manager.find_by_name(club_name) + return render_template("welcome.html", club=club, competitions=booking_service.competition_manager.competitions) + + +@app.route("/clubsPoints") +def clubs_points(): + return render_template("clubs_points.html", clubs=booking_service.club_manager.clubs) + + +@app.route("/logout") +def logout(): + return redirect(url_for("index")) diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 000000000..b2b0af466 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,31 @@ + + + + + + + {% block title %}GUDLFT{% endblock %} + + + + +
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+ + diff --git a/app/templates/booking.html b/app/templates/booking.html new file mode 100644 index 000000000..be884db6e --- /dev/null +++ b/app/templates/booking.html @@ -0,0 +1,16 @@ + +{% extends "base.html" %} + +{% block title %}Réservation | {{ competition['name'] }}{% endblock %} + +{% block content %} +

{{ competition['name'] }}

+

Places disponibles: {{ competition['number_of_places'] }}

+
+ + + + + +
+{% endblock %} diff --git a/app/templates/clubs_points.html b/app/templates/clubs_points.html new file mode 100644 index 000000000..f7a06436c --- /dev/null +++ b/app/templates/clubs_points.html @@ -0,0 +1,24 @@ + +{% extends "base.html" %} + +{% block title %}Points des Clubs{% endblock %} + +{% block content %} +

Points des Clubs

+ + + + + + + + + {% for club in clubs %} + + + + + {% endfor %} + +
ClubPoints
{{ club['name'] }}{{ club['points'] }}
+{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 000000000..4ef54b808 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,13 @@ + +{% extends "base.html" %} + +{% block title %}Accueil | GUDLFT{% endblock %} + +{% block content %} +

Bienvenue sur GUDLFT

+
+ + + +
+{% endblock %} diff --git a/app/templates/welcome.html b/app/templates/welcome.html new file mode 100644 index 000000000..b0e3c68ee --- /dev/null +++ b/app/templates/welcome.html @@ -0,0 +1,21 @@ + +{% extends "base.html" %} + +{% block title %}Résumé | GUDLFT Registration{% endblock %} + +{% block content %} +

Welcome, {{ club['email'] }}

+

Points disponibles: {{ club['points'] }}

+

Compétitions :

+ +{% endblock %} diff --git a/clubs.json b/data/clubs.json similarity index 100% rename from clubs.json rename to data/clubs.json diff --git a/competitions.json b/data/competitions.json similarity index 100% rename from competitions.json rename to data/competitions.json diff --git a/launch.json b/launch.json new file mode 100644 index 000000000..a0cc0ba8c --- /dev/null +++ b/launch.json @@ -0,0 +1,17 @@ +{ + "name": "Python Debugger: Flask", + "type": "debugpy", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "app/server.py", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "jinja": true, + "justMyCode": true +} diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 000000000..9c71c33dd --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,8 @@ +home = /usr/local/bin +implementation = CPython +version_info = 3.12.2.final.0 +virtualenv = 20.28.0 +include-system-site-packages = false +base-prefix = /Library/Frameworks/Python.framework/Versions/3.12 +base-exec-prefix = /Library/Frameworks/Python.framework/Versions/3.12 +base-executable = /Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12 diff --git a/server.py b/server.py deleted file mode 100644 index 4084baeac..000000000 --- a/server.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -from flask import Flask,render_template,request,redirect,flash,url_for - - -def loadClubs(): - with open('clubs.json') as c: - listOfClubs = json.load(c)['clubs'] - return listOfClubs - - -def loadCompetitions(): - with open('competitions.json') as comps: - listOfCompetitions = json.load(comps)['competitions'] - return listOfCompetitions - - -app = Flask(__name__) -app.secret_key = 'something_special' - -competitions = loadCompetitions() -clubs = loadClubs() - -@app.route('/') -def index(): - return render_template('index.html') - -@app.route('/showSummary',methods=['POST']) -def showSummary(): - club = [club for club in clubs if club['email'] == request.form['email']][0] - return render_template('welcome.html',club=club,competitions=competitions) - - -@app.route('/book//') -def book(competition,club): - foundClub = [c for c in clubs if c['name'] == club][0] - foundCompetition = [c for c in competitions if c['name'] == competition][0] - if foundClub and foundCompetition: - return render_template('booking.html',club=foundClub,competition=foundCompetition) - else: - flash("Something went wrong-please try again") - return render_template('welcome.html', club=club, competitions=competitions) - - -@app.route('/purchasePlaces',methods=['POST']) -def purchasePlaces(): - competition = [c for c in competitions if c['name'] == request.form['competition']][0] - club = [c for c in clubs if c['name'] == request.form['club']][0] - placesRequired = int(request.form['places']) - competition['numberOfPlaces'] = int(competition['numberOfPlaces'])-placesRequired - flash('Great-booking complete!') - return render_template('welcome.html', club=club, competitions=competitions) - - -# TODO: Add route for points display - - -@app.route('/logout') -def logout(): - return redirect(url_for('index')) \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 000000000..2a656539d --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,119 @@ +/* static/css/style.css */ + +/* Style global */ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + background-color: #f8f8f8; + margin: 0; + padding: 0; + color: #333; +} + +/* Barre de navigation */ +nav { + background-color: #333; + color: #fff; + padding: 10px 20px; +} + +nav ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; +} + +nav ul li { + margin-right: 15px; +} + +nav ul li a { + color: #fff; + text-decoration: none; + font-weight: bold; +} + +nav ul li a:hover { + text-decoration: underline; +} + +/* Conteneur principal */ +.container { + width: 90%; + max-width: 1000px; + margin: 20px auto; + background-color: #fff; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + border-radius: 5px; +} + +/* Titres */ +h1, h2, h3 { + color: #444; +} + +/* Formulaires */ +form label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +form input[type="email"], +form input[type="number"], +form input[type="text"] { + width: 100%; + max-width: 300px; + padding: 8px; + margin-bottom: 10px; + border: 1px solid #ddd; + border-radius: 4px; +} + +form button { + background-color: #333; + color: #fff; + border: none; + padding: 10px 15px; + border-radius: 4px; + cursor: pointer; +} + +form button:hover { + background-color: #555; +} + +/* Messages flash */ +.flashes { + list-style: none; + padding: 0; + margin: 10px 0; +} + +.flashes li { + background-color: #e74c3c; + color: #fff; + padding: 10px; + border-radius: 4px; + margin-bottom: 5px; +} + +/* Tableaux */ +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +table th, +table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +table th { + background-color: #333; + color: #fff; +} diff --git a/templates/booking.html b/templates/booking.html deleted file mode 100644 index 06ae1156c..000000000 --- a/templates/booking.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Booking for {{competition['name']}} || GUDLFT - - -

{{competition['name']}}

- Places available: {{competition['numberOfPlaces']}} -
- - - - -
- - \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 926526b7d..000000000 --- a/templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - GUDLFT Registration - - -

Welcome to the GUDLFT Registration Portal!

- Please enter your secretary email to continue: -
- - - -
- - \ No newline at end of file diff --git a/templates/welcome.html b/templates/welcome.html deleted file mode 100644 index ff6b261a2..000000000 --- a/templates/welcome.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Summary | GUDLFT Registration - - -

Welcome, {{club['email']}}

Logout - - {% with messages = get_flashed_messages()%} - {% if messages %} -
    - {% for message in messages %} -
  • {{message}}
  • - {% endfor %} -
- {% endif%} - Points available: {{club['points']}} -

Competitions:

-
    - {% for comp in competitions%} -
  • - {{comp['name']}}
    - Date: {{comp['date']}}
    - Number of Places: {{comp['numberOfPlaces']}} - {%if comp['numberOfPlaces']|int >0%} - Book Places - {%endif%} -
  • -
    - {% endfor %} -
- {%endwith%} - - - \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py new file mode 100644 index 000000000..45570f9ec --- /dev/null +++ b/tests/integration/test_integration.py @@ -0,0 +1,27 @@ +import unittest +from app.server import app + + +class TestServerRoutes(unittest.TestCase): + + def setUp(self): + app.config['TESTING'] = True + self.client = app.test_client() + + def test_purchase_places_route(self): + data = { + "club": "Iron Temple", + "competition": "Spring Festival", + "places": "3" + } + response = self.client.post("/purchasePlaces", data=data) + # Vérifier qu'on obtient un code 200 et un message de succès + self.assertEqual(response.status_code, 200) + self.assertIn(b"booking complete", response.data.lower(), + "Doit contenir un message de réservation réussie.") + + # On peut ajouter des checks sur le HTML renvoyé, le nombre de places, etc. + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/test_booking_manager_competition_full.py b/tests/unit/test_booking_manager_competition_full.py new file mode 100644 index 000000000..d45d06c5c --- /dev/null +++ b/tests/unit/test_booking_manager_competition_full.py @@ -0,0 +1,25 @@ +# tests/unit/test_booking_manager_competition_full.py + +import unittest +from app.booking_manager.booking_service import BookingService + + +class TestBookingManagerCompetitionFull(unittest.TestCase): + def setUp(self): + self.service = BookingService( + clubs_file="data/clubs.json", competitions_file="data/competitions.json") + + def test_purchase_places_competition_full(self): + # Forcer la compétition "Spring Festival" à n'avoir que 2 places disponibles + competition = self.service.competition_manager.find_by_name( + "Spring Festival") + if competition: + competition.number_of_places = 2 + success = self.service.purchase_places( + "Iron Temple", "Spring Festival", 3) + self.assertFalse( + success, "L'achat de 3 places doit échouer si la compétition n'a que 2 places disponibles.") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_booking_manager_inexistant.py b/tests/unit/test_booking_manager_inexistant.py new file mode 100644 index 000000000..ecd69d779 --- /dev/null +++ b/tests/unit/test_booking_manager_inexistant.py @@ -0,0 +1,26 @@ +# tests/unit/test_booking_manager_inexistant.py + +import unittest +from app.booking_manager.booking_service import BookingService + + +class TestBookingManagerInexistant(unittest.TestCase): + def setUp(self): + self.service = BookingService( + clubs_file="data/clubs.json", competitions_file="data/competitions.json") + + def test_purchase_places_club_not_found(self): + success = self.service.purchase_places( + "NonExistentClub", "Spring Festival", 3) + self.assertFalse( + success, "L'achat doit échouer si le club n'existe pas.") + + def test_purchase_places_competition_not_found(self): + success = self.service.purchase_places( + "Iron Temple", "NonExistentCompetition", 3) + self.assertFalse( + success, "L'achat doit échouer si la compétition n'existe pas.") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_booking_manager_insufficient_points.py b/tests/unit/test_booking_manager_insufficient_points.py new file mode 100644 index 000000000..c6f9c4dc1 --- /dev/null +++ b/tests/unit/test_booking_manager_insufficient_points.py @@ -0,0 +1,21 @@ +import unittest +from app.booking_manager.booking_service import BookingService + + +class TestBookingManagerInsufficientPoints(unittest.TestCase): + def setUp(self): + self.service = BookingService( + clubs_file="data/clubs.json", competitions_file="data/competitions.json") + + def test_purchase_places_insufficient_points(self): + club = self.service.club_manager.find_by_name("Iron Temple") + if club: + club.points = 2 + success = self.service.purchase_places( + "Iron Temple", "Spring Festival", 3) + self.assertFalse( + success, "L'achat de 3 places doit échouer si le club n'a que 2 points.") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_points_display.py b/tests/unit/test_points_display.py new file mode 100644 index 000000000..cf731bb04 --- /dev/null +++ b/tests/unit/test_points_display.py @@ -0,0 +1,28 @@ +# tests/unit/test_points_display.py + +import unittest +from app.server import app + + +class TestPointsDisplay(unittest.TestCase): + def setUp(self): + app.config['TESTING'] = True + self.client = app.test_client() + + def test_points_display_on_index(self): + """Vérifie que la page d'accueil contient un indice sur l'affichage des points.""" + response = self.client.get("/") + self.assertEqual(response.status_code, 200) + # Par exemple, vérifier que le lien vers "Points Clubs" est présent + self.assertIn(b"Points Clubs", response.data) + + def test_points_display_on_welcome(self): + """Vérifie que la page welcome affiche les points après connexion.""" + response = self.client.post( + "/showSummary", data={"email": "admin@irontemple.com"}, follow_redirects=True) + self.assertEqual(response.status_code, 200) + self.assertIn(b"Points disponibles", response.data) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py new file mode 100644 index 000000000..a32d405b6 --- /dev/null +++ b/tests/unit/test_server.py @@ -0,0 +1,38 @@ +import unittest +from app.server import app + + +class TestWelcomePage(unittest.TestCase): + + def setUp(self): + """ + Prépare l'application Flask en mode TEST. + """ + app.config['TESTING'] = True + self.client = app.test_client() + + def test_show_summary_no_undefined_error_for_unknown_email(self): + """ + Vérifie que l'application redirige (302) pour un email inconnu + et ne produit pas d'erreur Jinja une fois la redirection suivie. + """ + response = self.client.post( + '/showSummary', data={'email': 'email_inconnu@example.com'}) + + # On s'attend à un code 302 (redirection) + self.assertEqual(response.status_code, 302, + "La route doit rediriger pour un email inconnu.") + + # Optionnel : suivre la redirection pour valider le contenu final + response_followed = self.client.post( + '/showSummary', + data={'email': 'email_inconnu@example.com'}, + follow_redirects=True + ) + # Après la redirection, on devrait avoir un code 200 (page affichée) + self.assertEqual(response_followed.status_code, 200, + "Après redirection, on doit avoir un code 200.") + + # Vérifie qu'on ne voit pas 'UndefinedError' dans la page + self.assertNotIn(b'UndefinedError', response_followed.data, + "Aucune erreur Jinja ne doit être présente dans la page finale.") diff --git a/tests/unit/test_static_files.py b/tests/unit/test_static_files.py new file mode 100644 index 000000000..553b15a2c --- /dev/null +++ b/tests/unit/test_static_files.py @@ -0,0 +1,23 @@ +# tests/unit/test_static_files.py + +import unittest +from app.server import app + + +class TestStaticFiles(unittest.TestCase): + def setUp(self): + app.config['TESTING'] = True + self.client = app.test_client() + + def test_css_loaded(self): + response = self.client.get('/static/css/style.css') + self.assertEqual(response.status_code, 200, + "Le CSS devrait être accessible et retourner un code 200.") + self.assertTrue(len(response.data) > 0, + "Le fichier CSS ne doit pas être vide.") + self.assertIn(b"body", response.data, + "Le CSS doit contenir la règle 'body'.") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_welcome_page.py b/tests/unit/test_welcome_page.py new file mode 100644 index 000000000..ec8b92f98 --- /dev/null +++ b/tests/unit/test_welcome_page.py @@ -0,0 +1,28 @@ +# tests/unit/test_welcome_page.py + +import unittest +from app.server import app + + +class TestWelcomePage(unittest.TestCase): + def setUp(self): + app.config['TESTING'] = True + self.client = app.test_client() + + def test_show_summary_unknown_email_redirects(self): + response = self.client.post( + '/showSummary', data={'email': 'unknown@example.com'}) + self.assertEqual(response.status_code, 302, + "Doit rediriger pour un email inconnu.") + + # Suivre la redirection pour vérifier l'absence d'erreur Jinja + response_followed = self.client.post( + '/showSummary', data={'email': 'unknown@example.com'}, follow_redirects=True) + self.assertEqual(response_followed.status_code, 200, + "Après redirection, le code doit être 200.") + self.assertNotIn(b"UndefinedError", response_followed.data, + "Aucune erreur Jinja ne doit apparaître.") + + +if __name__ == '__main__': + unittest.main()