From 4ca9cd9a7c414e7cc5c8d3a8539a9f9b09a0b6f0 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Tue, 4 Feb 2025 16:27:57 +0100
Subject: [PATCH 01/15] Corrige le bug Jinja et ajoute tests pour /showSummary

---
 .DS_Store                                   | Bin 6148 -> 6148 bytes
 CACHEDIR.TAG                                |   4 +
 app/__init__.py                             |   0
 app/booking_manager.py                      |  84 ++++++++++++++++++++
 app/models.py                               |  13 +++
 app/server.py                               |  71 +++++++++++++++++
 {templates => app/templates}/booking.html   |   0
 {templates => app/templates}/index.html     |   0
 {templates => app/templates}/welcome.html   |   6 +-
 clubs.json => data/clubs.json               |   0
 competitions.json => data/competitions.json |   0
 launch.json                                 |  17 ++++
 pyvenv.cfg                                  |   8 ++
 server.py                                   |  59 --------------
 14 files changed, 200 insertions(+), 62 deletions(-)
 create mode 100644 CACHEDIR.TAG
 create mode 100644 app/__init__.py
 create mode 100644 app/booking_manager.py
 create mode 100644 app/models.py
 create mode 100644 app/server.py
 rename {templates => app/templates}/booking.html (100%)
 rename {templates => app/templates}/index.html (100%)
 rename {templates => app/templates}/welcome.html (82%)
 rename clubs.json => data/clubs.json (100%)
 rename competitions.json => data/competitions.json (100%)
 create mode 100644 launch.json
 create mode 100644 pyvenv.cfg
 delete mode 100644 server.py

diff --git a/.DS_Store b/.DS_Store
index 04a508e6f6048834346c5f1769770f2ff01806e6..ed2a65c8bc3d2bd8bb480af7d9ea7b7cbbf7c371 100644
GIT binary patch
delta 37
tcmZoMXfc@J&nU1lU^g?Pz-As6WyZ<-SqnCwXR~13*kHi8nVsV=KLFjh3&;Qf

delta 333
zcmZoMXfc@J&nU4mU^g?P#AY5AWyX4ThD?S$hGd2uhEj$UhSZ#N!{Frn+yVv&Z~;<i
zDsuB(T#|C~lYlZDOWfOzas(Z6#HK2R2vr3cyeQU9)@CiK=VT~hNM*=nC;+-C5zH<|
xbrnY`&^|1xkX^QPnZ!)qFtE$8szkUBU8O9l)dd;IE;QIYo7IMKGdss$egGjJO;P{=

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.py b/app/booking_manager.py
new file mode 100644
index 000000000..3ef6047d3
--- /dev/null
+++ b/app/booking_manager.py
@@ -0,0 +1,84 @@
+# app/booking_manager.py
+
+import json
+import os
+from typing import List, Optional
+from app.models import Club, Competition
+
+
+class BookingManager:
+    """Gère la logique de réservation : chargement des données, vérification des règles, etc."""
+
+    def __init__(self, clubs_file: str, competitions_file: str):
+        self.clubs: List[Club] = self.load_clubs(clubs_file)
+        self.competitions: List[Competition] = self.load_competitions(
+            competitions_file)
+
+    @staticmethod
+    def load_clubs(filepath: str) -> List[Club]:
+        """Charge et retourne la liste de clubs depuis un fichier JSON."""
+        if not os.path.exists(filepath):
+            raise FileNotFoundError(f"Fichier introuvable : {filepath}")
+        with open(filepath, "r") as f:
+            data = json.load(f)
+        clubs = []
+        for c in data["clubs"]:
+            clubs.append(
+                Club(name=c["name"], email=c["email"], points=int(c["points"]))
+            )
+        return clubs
+
+    @staticmethod
+    def load_competitions(filepath: str) -> List[Competition]:
+        """Charge et retourne la liste de compétitions depuis un fichier JSON."""
+        if not os.path.exists(filepath):
+            raise FileNotFoundError(f"Fichier introuvable : {filepath}")
+        with open(filepath, "r") as f:
+            data = json.load(f)
+        competitions = []
+        for c in data["competitions"]:
+            competitions.append(
+                Competition(
+                    name=c["name"],
+                    date=c["date"],
+                    number_of_places=int(c["numberOfPlaces"])
+                )
+            )
+        return competitions
+
+    def find_club_by_email(self, email: str) -> Optional[Club]:
+        return next((club for club in self.clubs if club.email == email), None)
+
+    def find_club_by_name(self, name: str) -> Optional[Club]:
+        return next((club for club in self.clubs if club.name == name), None)
+
+    def find_competition_by_name(self, name: str) -> Optional[Competition]:
+        return next((c for c in self.competitions if c.name == name), None)
+
+    def purchase_places(self, club_name: str, competition_name: str, places_requested: int) -> bool:
+        """Tente d'acheter `places_requested` places pour le `club_name` dans `competition_name`.
+           Renvoie True si l’opération réussit, False sinon (règles non respectées).
+        """
+        club = self.find_club_by_name(club_name)
+        competition = self.find_competition_by_name(competition_name)
+
+        # Vérifications basiques
+        if not club or not competition:
+            return False
+
+        # 1) Pas plus de 12 places en une seule fois
+        if places_requested > 12:
+            return False
+
+        # 2) Pas plus de places que le club n'a de points
+        if places_requested > club.points:
+            return False
+
+        # 3) Pas plus de places que celles disponibles dans la compétition
+        if places_requested > competition.number_of_places:
+            return False
+
+        # Si tout est OK, on décrémente les places
+        competition.number_of_places -= places_requested
+        club.points -= places_requested
+        return True
diff --git a/app/models.py b/app/models.py
new file mode 100644
index 000000000..daff602f2
--- /dev/null
+++ b/app/models.py
@@ -0,0 +1,13 @@
+# 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..ef99850eb
--- /dev/null
+++ b/app/server.py
@@ -0,0 +1,71 @@
+# app/server.py
+
+from flask import Flask, render_template, request, redirect, flash, url_for
+from app.booking_manager import BookingManager
+
+app = Flask(__name__)
+app.secret_key = "secret_key_xyz"
+
+# Instanciation du manager avec les chemins de fichiers en paramètre
+manager = BookingManager(
+    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 = manager.find_club_by_email(email)
+    if not club:
+        flash("Email inconnu ou invalide.")
+        return redirect(url_for("index"))
+    return render_template("welcome.html", club=club, competitions=manager.competitions)
+
+
+@app.route("/book/<competition>/<club>")
+def book(competition, club):
+    found_competition = manager.find_competition_by_name(competition)
+    found_club = manager.find_club_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 = manager.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 = manager.find_club_by_name(club_name)
+    return render_template("welcome.html", club=club, competitions=manager.competitions)
+
+
+@app.route("/clubsPoints")
+def clubs_points():
+    return render_template("clubs_points.html", clubs=manager.clubs)
+
+
+@app.route("/logout")
+def logout():
+    return redirect(url_for("index"))
diff --git a/templates/booking.html b/app/templates/booking.html
similarity index 100%
rename from templates/booking.html
rename to app/templates/booking.html
diff --git a/templates/index.html b/app/templates/index.html
similarity index 100%
rename from templates/index.html
rename to app/templates/index.html
diff --git a/templates/welcome.html b/app/templates/welcome.html
similarity index 82%
rename from templates/welcome.html
rename to app/templates/welcome.html
index ff6b261a2..928a83c4a 100644
--- a/templates/welcome.html
+++ b/app/templates/welcome.html
@@ -23,9 +23,9 @@ <h3>Competitions:</h3>
             {{comp['name']}}<br />
             Date: {{comp['date']}}</br>
             Number of Places: {{comp['numberOfPlaces']}}
-            {%if comp['numberOfPlaces']|int >0%}
-            <a href="{{ url_for('book',competition=comp['name'],club=club['name']) }}">Book Places</a>
-            {%endif%}
+            {% if comp.number_of_places|int > 0 %}
+    {{ comp.number_of_places }} places available
+{% endif %}
         </li>
         <hr />
         {% endfor %}
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/<competition>/<club>')
-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

From 194489a16a2c38fc3fb9cf69f29676c5b27e4e8d Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Tue, 4 Feb 2025 17:42:09 +0100
Subject: [PATCH 02/15] Ajout du test pour le chargement du CSS et
 modifications des templates pour harmoniser le style

---
 app/server.py                   |   7 +-
 app/templates/base.html         |  37 ++++++++++
 app/templates/booking.html      |  33 +++++----
 app/templates/clubs_points.html |  24 +++++++
 app/templates/index.html        |  29 ++++----
 app/templates/welcome.html      |  54 ++++++--------
 static/css/style.css            | 120 ++++++++++++++++++++++++++++++++
 7 files changed, 234 insertions(+), 70 deletions(-)
 create mode 100644 app/templates/base.html
 create mode 100644 app/templates/clubs_points.html
 create mode 100644 static/css/style.css

diff --git a/app/server.py b/app/server.py
index ef99850eb..bd306ca80 100644
--- a/app/server.py
+++ b/app/server.py
@@ -3,7 +3,9 @@
 from flask import Flask, render_template, request, redirect, flash, url_for
 from app.booking_manager import BookingManager
 
-app = Flask(__name__)
+# Indique que le dossier statique se trouve dans ../static (à la racine du projet)
+# Les templates seront recherchés par défaut dans "templates" situé dans ce même dossier (ici "app/templates")
+app = Flask(__name__, static_folder="../static")
 app.secret_key = "secret_key_xyz"
 
 # Instanciation du manager avec les chemins de fichiers en paramètre
@@ -33,7 +35,7 @@ def book(competition, club):
     found_competition = manager.find_competition_by_name(competition)
     found_club = manager.find_club_by_name(club)
     if not found_competition or not found_club:
-        flash("Something went wrong-please try again")
+        flash("Something went wrong - please try again")
         return redirect(url_for("index"))
     return render_template("booking.html", club=found_club, competition=found_competition)
 
@@ -43,7 +45,6 @@ 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:
diff --git a/app/templates/base.html b/app/templates/base.html
new file mode 100644
index 000000000..3828a02fa
--- /dev/null
+++ b/app/templates/base.html
@@ -0,0 +1,37 @@
+<!-- app/templates/base.html -->
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{% block title %}GUDLFT{% endblock %}</title>
+  <!-- Vous pouvez ajouter ici un lien vers votre CSS, par exemple : -->
+  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
+</head>
+<body>
+  <!-- Barre de navigation -->
+  <nav>
+    <ul>
+      <li><a href="{{ url_for('index') }}">Accueil</a></li>
+      <li><a href="{{ url_for('clubs_points') }}">Points Clubs</a></li>
+      <li><a href="{{ url_for('logout') }}">Déconnexion</a></li>
+    </ul>
+  </nav>
+
+  <!-- Zone pour afficher les messages flash -->
+  <div class="container">
+    {% with messages = get_flashed_messages() %}
+      {% if messages %}
+        <ul class="flashes">
+          {% for message in messages %}
+            <li>{{ message }}</li>
+          {% endfor %}
+        </ul>
+      {% endif %}
+    {% endwith %}
+
+    <!-- Contenu spécifique à chaque page -->
+    {% block content %}{% endblock %}
+  </div>
+</body>
+</html>
diff --git a/app/templates/booking.html b/app/templates/booking.html
index 06ae1156c..be884db6e 100644
--- a/app/templates/booking.html
+++ b/app/templates/booking.html
@@ -1,17 +1,16 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Booking for {{competition['name']}} || GUDLFT</title>
-</head>
-<body>
-    <h2>{{competition['name']}}</h2>
-    Places available: {{competition['numberOfPlaces']}}
-    <form action="/purchasePlaces" method="post">
-        <input type="hidden" name="club" value="{{club['name']}}">
-        <input type="hidden" name="competition" value="{{competition['name']}}">
-        <label for="places">How many places?</label><input type="number" name="places" id=""/>
-        <button type="submit">Book</button>
-    </form>
-</body>
-</html>
\ No newline at end of file
+<!-- app/templates/booking.html -->
+{% extends "base.html" %}
+
+{% block title %}Réservation | {{ competition['name'] }}{% endblock %}
+
+{% block content %}
+  <h2>{{ competition['name'] }}</h2>
+  <p>Places disponibles: {{ competition['number_of_places'] }}</p>
+  <form action="/purchasePlaces" method="post">
+    <input type="hidden" name="club" value="{{ club['name'] }}">
+    <input type="hidden" name="competition" value="{{ competition['name'] }}">
+    <label for="places">Combien de places ?</label>
+    <input type="number" name="places" id="places" min="1" required>
+    <button type="submit">Réserver</button>
+  </form>
+{% 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 @@
+<!-- app/templates/clubs_points.html -->
+{% extends "base.html" %}
+
+{% block title %}Points des Clubs{% endblock %}
+
+{% block content %}
+  <h2>Points des Clubs</h2>
+  <table border="1">
+    <thead>
+      <tr>
+        <th>Club</th>
+        <th>Points</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for club in clubs %}
+        <tr>
+          <td>{{ club['name'] }}</td>
+          <td>{{ club['points'] }}</td>
+        </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+{% endblock %}
diff --git a/app/templates/index.html b/app/templates/index.html
index 926526b7d..4ef54b808 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -1,16 +1,13 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>GUDLFT Registration</title>
-</head>
-<body>
-    <h1>Welcome to the GUDLFT Registration Portal!</h1>
-    Please enter your secretary email to continue:
-    <form action="showSummary" method="post">
-        <label for="email">Email:</label>
-        <input type="email" name="email" id=""/>
-        <button type="submit">Enter</button>
-    </form>
-</body>
-</html>
\ No newline at end of file
+<!-- app/templates/index.html -->
+{% extends "base.html" %}
+
+{% block title %}Accueil | GUDLFT{% endblock %}
+
+{% block content %}
+  <h1>Bienvenue sur GUDLFT</h1>
+  <form action="/showSummary" method="post">
+    <label for="email">Email :</label>
+    <input type="email" name="email" id="email" required>
+    <button type="submit">Se connecter</button>
+  </form>
+{% endblock %}
diff --git a/app/templates/welcome.html b/app/templates/welcome.html
index 928a83c4a..b821d7bb1 100644
--- a/app/templates/welcome.html
+++ b/app/templates/welcome.html
@@ -1,36 +1,22 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Summary | GUDLFT Registration</title>
-</head>
-<body>
-        <h2>Welcome, {{club['email']}} </h2><a href="{{url_for('logout')}}">Logout</a>
+<!-- app/templates/welcome.html -->
+{% extends "base.html" %}
 
-    {% with messages = get_flashed_messages()%}
-    {% if messages %}
-        <ul>
-       {% for message in messages %}
-            <li>{{message}}</li>
-        {% endfor %}
-       </ul>
-    {% endif%}
-    Points available: {{club['points']}}
-    <h3>Competitions:</h3>
-    <ul>
-        {% for comp in competitions%}
-        <li>
-            {{comp['name']}}<br />
-            Date: {{comp['date']}}</br>
-            Number of Places: {{comp['numberOfPlaces']}}
-            {% if comp.number_of_places|int > 0 %}
-    {{ comp.number_of_places }} places available
-{% endif %}
-        </li>
-        <hr />
-        {% endfor %}
-    </ul>
-    {%endwith%}
+{% block title %}Résumé | GUDLFT Registration{% endblock %}
 
-</body>
-</html>
\ No newline at end of file
+{% block content %}
+  <h2>Welcome, {{ club['email'] }}</h2>
+  <p>Points disponibles: {{ club['points'] }}</p>
+
+  <h3>Compétitions :</h3>
+  <ul>
+    {% for comp in competitions %}
+      <li>
+        {{ comp['name'] }}<br />
+        Date: {{ comp['date'] }}<br />
+        Places disponibles: {{ comp['number_of_places'] }}<br />
+        <a href="{{ url_for('book', competition=comp['name'], club=club['name']) }}">Réserver</a>
+      </li>
+      <hr />
+    {% endfor %}
+  </ul>
+{% endblock %}
diff --git a/static/css/style.css b/static/css/style.css
new file mode 100644
index 000000000..d13d15053
--- /dev/null
+++ b/static/css/style.css
@@ -0,0 +1,120 @@
+/* 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;
+  }
+  
\ No newline at end of file

From d03bd45961e39a620dad214bb972e89c284b9c75 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Tue, 4 Feb 2025 18:00:58 +0100
Subject: [PATCH 03/15] Ajout du test pour le chargement du CSS et
 modifications des templates pour harmoniser le style

---
 Python_Testing | 1 +
 1 file changed, 1 insertion(+)
 create mode 160000 Python_Testing

diff --git a/Python_Testing b/Python_Testing
new file mode 160000
index 000000000..4ca9cd9a7
--- /dev/null
+++ b/Python_Testing
@@ -0,0 +1 @@
+Subproject commit 4ca9cd9a7c414e7cc5c8d3a8539a9f9b09a0b6f0

From f35e17287927636945ae6b83e27f70c44a8bb17f Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Tue, 4 Feb 2025 18:10:38 +0100
Subject: [PATCH 04/15] mep

---
 Python_Testing | 1 +
 1 file changed, 1 insertion(+)
 create mode 160000 Python_Testing

diff --git a/Python_Testing b/Python_Testing
new file mode 160000
index 000000000..4ca9cd9a7
--- /dev/null
+++ b/Python_Testing
@@ -0,0 +1 @@
+Subproject commit 4ca9cd9a7c414e7cc5c8d3a8539a9f9b09a0b6f0

From fd8668b0b35077b975dad472891004f16f87088e Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Tue, 4 Feb 2025 20:42:40 +0100
Subject: [PATCH 05/15] mise des test en ligne

---
 .gitignore                            |  2 +-
 Python_Testing                        |  1 -
 tests/__init__.py                     |  0
 tests/functional/test_functional.py   |  0
 tests/integration/__init__.py         |  0
 tests/integration/test_integration.py | 27 +++++++++++++++++
 tests/unit/__init__.py                |  0
 tests/unit/test_booking_manager.py    | 42 +++++++++++++++++++++++++++
 tests/unit/test_server.py             | 38 ++++++++++++++++++++++++
 tests/unit/test_static_files.py       | 25 ++++++++++++++++
 10 files changed, 133 insertions(+), 2 deletions(-)
 delete mode 160000 Python_Testing
 create mode 100644 tests/__init__.py
 create mode 100644 tests/functional/test_functional.py
 create mode 100644 tests/integration/__init__.py
 create mode 100644 tests/integration/test_integration.py
 create mode 100644 tests/unit/__init__.py
 create mode 100644 tests/unit/test_booking_manager.py
 create mode 100644 tests/unit/test_server.py
 create mode 100644 tests/unit/test_static_files.py

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/Python_Testing b/Python_Testing
deleted file mode 160000
index 4ca9cd9a7..000000000
--- a/Python_Testing
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 4ca9cd9a7c414e7cc5c8d3a8539a9f9b09a0b6f0
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.py b/tests/unit/test_booking_manager.py
new file mode 100644
index 000000000..63b83b3e7
--- /dev/null
+++ b/tests/unit/test_booking_manager.py
@@ -0,0 +1,42 @@
+import unittest
+from app.booking_manager import BookingManager
+
+
+class TestBookingManager(unittest.TestCase):
+
+    def setUp(self):
+        # Instancier un BookingManager avec des JSON de test
+        self.manager = BookingManager(
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
+
+    def test_purchase_places_happy_path(self):
+        """
+        Teste le cas normal :
+        - Club a suffisamment de points
+        - Compétition a suffisamment de places
+        - Moins de 12 places demandées
+        """
+        success = self.manager.purchase_places(
+            "Iron Temple", "Spring Festival", 3)
+        self.assertTrue(success, "L'achat de 3 places devrait réussir.")
+
+    def test_purchase_places_too_many_places_requested(self):
+        """
+        Teste la limite de 12 places : si on essaie d'en prendre 13, ça doit échouer.
+        """
+        success = self.manager.purchase_places(
+            "Iron Temple", "Spring Festival", 13)
+        self.assertFalse(
+            success, "L'achat de 13 places doit échouer (max 12).")
+
+    # Ajoute d'autres tests pour couvrir :
+    # - club sans assez de points
+    # - competition full
+    # - club ou competition inexistant
+    # etc.
+
+
+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..af9f43d3e
--- /dev/null
+++ b/tests/unit/test_static_files.py
@@ -0,0 +1,25 @@
+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):
+        # On envoie une requête GET vers l'URL du CSS
+        response = self.client.get('/static/css/style.css')
+        # Vérifie que le serveur renvoie bien un code 200 (OK)
+        self.assertEqual(response.status_code, 200,
+                         "Le CSS devrait être accessible et retourner un code 200.")
+        # Vérifie que le contenu du CSS n'est pas vide
+        self.assertTrue(len(response.data) > 0,
+                        "Le fichier CSS ne doit pas être vide.")
+        # Optionnel : vérifier qu'une règle CSS connue est présente (ici "body")
+        self.assertIn(b"body", response.data,
+                      "Le CSS doit contenir la règle 'body'.")
+
+
+if __name__ == '__main__':
+    unittest.main()

From e6b637ff451c3a1b1736eedd10a3a174be611c90 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Wed, 5 Feb 2025 16:11:32 +0100
Subject: [PATCH 06/15] refactorisation du code

---
 app/booking_manager.py                        |  84 -------
 app/booking_manager/__init__.py               |   4 +
 app/booking_manager/booking_service.py        |  32 +++
 app/booking_manager/club_manager.py           |  32 +++
 app/booking_manager/competition_manager.py    |  29 +++
 app/booking_manager/data_loader.py            |  19 ++
 app/models.py                                 |   1 +
 app/server.py                                 |  27 +-
 app/templates/base.html                       |   6 -
 app/templates/welcome.html                    |   1 -
 static/css/style.css                          | 231 +++++++++---------
 tests/unit/test_booking_manager.py            |  42 ----
 .../test_booking_manager_competition_full.py  |  25 ++
 tests/unit/test_booking_manager_inexistant.py |  26 ++
 ...est_booking_manager_insufficient_points.py |  21 ++
 tests/unit/test_points_display.py             |  28 +++
 tests/unit/test_static_files.py               |   6 +-
 tests/unit/test_welcome_page.py               |  28 +++
 18 files changed, 374 insertions(+), 268 deletions(-)
 delete mode 100644 app/booking_manager.py
 create mode 100644 app/booking_manager/__init__.py
 create mode 100644 app/booking_manager/booking_service.py
 create mode 100644 app/booking_manager/club_manager.py
 create mode 100644 app/booking_manager/competition_manager.py
 create mode 100644 app/booking_manager/data_loader.py
 delete mode 100644 tests/unit/test_booking_manager.py
 create mode 100644 tests/unit/test_booking_manager_competition_full.py
 create mode 100644 tests/unit/test_booking_manager_inexistant.py
 create mode 100644 tests/unit/test_booking_manager_insufficient_points.py
 create mode 100644 tests/unit/test_points_display.py
 create mode 100644 tests/unit/test_welcome_page.py

diff --git a/app/booking_manager.py b/app/booking_manager.py
deleted file mode 100644
index 3ef6047d3..000000000
--- a/app/booking_manager.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# app/booking_manager.py
-
-import json
-import os
-from typing import List, Optional
-from app.models import Club, Competition
-
-
-class BookingManager:
-    """Gère la logique de réservation : chargement des données, vérification des règles, etc."""
-
-    def __init__(self, clubs_file: str, competitions_file: str):
-        self.clubs: List[Club] = self.load_clubs(clubs_file)
-        self.competitions: List[Competition] = self.load_competitions(
-            competitions_file)
-
-    @staticmethod
-    def load_clubs(filepath: str) -> List[Club]:
-        """Charge et retourne la liste de clubs depuis un fichier JSON."""
-        if not os.path.exists(filepath):
-            raise FileNotFoundError(f"Fichier introuvable : {filepath}")
-        with open(filepath, "r") as f:
-            data = json.load(f)
-        clubs = []
-        for c in data["clubs"]:
-            clubs.append(
-                Club(name=c["name"], email=c["email"], points=int(c["points"]))
-            )
-        return clubs
-
-    @staticmethod
-    def load_competitions(filepath: str) -> List[Competition]:
-        """Charge et retourne la liste de compétitions depuis un fichier JSON."""
-        if not os.path.exists(filepath):
-            raise FileNotFoundError(f"Fichier introuvable : {filepath}")
-        with open(filepath, "r") as f:
-            data = json.load(f)
-        competitions = []
-        for c in data["competitions"]:
-            competitions.append(
-                Competition(
-                    name=c["name"],
-                    date=c["date"],
-                    number_of_places=int(c["numberOfPlaces"])
-                )
-            )
-        return competitions
-
-    def find_club_by_email(self, email: str) -> Optional[Club]:
-        return next((club for club in self.clubs if club.email == email), None)
-
-    def find_club_by_name(self, name: str) -> Optional[Club]:
-        return next((club for club in self.clubs if club.name == name), None)
-
-    def find_competition_by_name(self, name: str) -> Optional[Competition]:
-        return next((c for c in self.competitions if c.name == name), None)
-
-    def purchase_places(self, club_name: str, competition_name: str, places_requested: int) -> bool:
-        """Tente d'acheter `places_requested` places pour le `club_name` dans `competition_name`.
-           Renvoie True si l’opération réussit, False sinon (règles non respectées).
-        """
-        club = self.find_club_by_name(club_name)
-        competition = self.find_competition_by_name(competition_name)
-
-        # Vérifications basiques
-        if not club or not competition:
-            return False
-
-        # 1) Pas plus de 12 places en une seule fois
-        if places_requested > 12:
-            return False
-
-        # 2) Pas plus de places que le club n'a de points
-        if places_requested > club.points:
-            return False
-
-        # 3) Pas plus de places que celles disponibles dans la compétition
-        if places_requested > competition.number_of_places:
-            return False
-
-        # Si tout est OK, on décrémente les places
-        competition.number_of_places -= places_requested
-        club.points -= places_requested
-        return True
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
index daff602f2..9a7d96d8a 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,4 +1,5 @@
 # app/models.py
+
 class Competition:
     def __init__(self, name, date, number_of_places):
         self.name = name
diff --git a/app/server.py b/app/server.py
index bd306ca80..e119af1bf 100644
--- a/app/server.py
+++ b/app/server.py
@@ -1,15 +1,11 @@
-# app/server.py
-
 from flask import Flask, render_template, request, redirect, flash, url_for
-from app.booking_manager import BookingManager
+from app.booking_manager import BookingService
 
-# Indique que le dossier statique se trouve dans ../static (à la racine du projet)
-# Les templates seront recherchés par défaut dans "templates" situé dans ce même dossier (ici "app/templates")
 app = Flask(__name__, static_folder="../static")
 app.secret_key = "secret_key_xyz"
 
-# Instanciation du manager avec les chemins de fichiers en paramètre
-manager = BookingManager(
+# Instanciation du service de réservation
+booking_service = BookingService(
     clubs_file="data/clubs.json",
     competitions_file="data/competitions.json"
 )
@@ -23,17 +19,18 @@ def index():
 @app.route("/showSummary", methods=["POST"])
 def show_summary():
     email = request.form.get("email", "")
-    club = manager.find_club_by_email(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=manager.competitions)
+    return render_template("welcome.html", club=club, competitions=booking_service.competition_manager.competitions)
 
 
 @app.route("/book/<competition>/<club>")
 def book(competition, club):
-    found_competition = manager.find_competition_by_name(competition)
-    found_club = manager.find_club_by_name(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"))
@@ -51,20 +48,20 @@ def purchase_places():
         flash("Le nombre de places est invalide.")
         return redirect(url_for("index"))
 
-    success = manager.purchase_places(
+    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 = manager.find_club_by_name(club_name)
-    return render_template("welcome.html", club=club, competitions=manager.competitions)
+    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=manager.clubs)
+    return render_template("clubs_points.html", clubs=booking_service.club_manager.clubs)
 
 
 @app.route("/logout")
diff --git a/app/templates/base.html b/app/templates/base.html
index 3828a02fa..b2b0af466 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -5,11 +5,9 @@
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>{% block title %}GUDLFT{% endblock %}</title>
-  <!-- Vous pouvez ajouter ici un lien vers votre CSS, par exemple : -->
   <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
 </head>
 <body>
-  <!-- Barre de navigation -->
   <nav>
     <ul>
       <li><a href="{{ url_for('index') }}">Accueil</a></li>
@@ -17,8 +15,6 @@
       <li><a href="{{ url_for('logout') }}">Déconnexion</a></li>
     </ul>
   </nav>
-
-  <!-- Zone pour afficher les messages flash -->
   <div class="container">
     {% with messages = get_flashed_messages() %}
       {% if messages %}
@@ -29,8 +25,6 @@
         </ul>
       {% endif %}
     {% endwith %}
-
-    <!-- Contenu spécifique à chaque page -->
     {% block content %}{% endblock %}
   </div>
 </body>
diff --git a/app/templates/welcome.html b/app/templates/welcome.html
index b821d7bb1..b0e3c68ee 100644
--- a/app/templates/welcome.html
+++ b/app/templates/welcome.html
@@ -6,7 +6,6 @@
 {% block content %}
   <h2>Welcome, {{ club['email'] }}</h2>
   <p>Points disponibles: {{ club['points'] }}</p>
-
   <h3>Compétitions :</h3>
   <ul>
     {% for comp in competitions %}
diff --git a/static/css/style.css b/static/css/style.css
index d13d15053..2a656539d 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -2,119 +2,118 @@
 
 /* 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;
-  }
-  
\ No newline at end of file
+  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/tests/unit/test_booking_manager.py b/tests/unit/test_booking_manager.py
deleted file mode 100644
index 63b83b3e7..000000000
--- a/tests/unit/test_booking_manager.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import unittest
-from app.booking_manager import BookingManager
-
-
-class TestBookingManager(unittest.TestCase):
-
-    def setUp(self):
-        # Instancier un BookingManager avec des JSON de test
-        self.manager = BookingManager(
-            clubs_file="data/clubs.json",
-            competitions_file="data/competitions.json"
-        )
-
-    def test_purchase_places_happy_path(self):
-        """
-        Teste le cas normal :
-        - Club a suffisamment de points
-        - Compétition a suffisamment de places
-        - Moins de 12 places demandées
-        """
-        success = self.manager.purchase_places(
-            "Iron Temple", "Spring Festival", 3)
-        self.assertTrue(success, "L'achat de 3 places devrait réussir.")
-
-    def test_purchase_places_too_many_places_requested(self):
-        """
-        Teste la limite de 12 places : si on essaie d'en prendre 13, ça doit échouer.
-        """
-        success = self.manager.purchase_places(
-            "Iron Temple", "Spring Festival", 13)
-        self.assertFalse(
-            success, "L'achat de 13 places doit échouer (max 12).")
-
-    # Ajoute d'autres tests pour couvrir :
-    # - club sans assez de points
-    # - competition full
-    # - club ou competition inexistant
-    # etc.
-
-
-if __name__ == "__main__":
-    unittest.main()
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_static_files.py b/tests/unit/test_static_files.py
index af9f43d3e..553b15a2c 100644
--- a/tests/unit/test_static_files.py
+++ b/tests/unit/test_static_files.py
@@ -1,3 +1,5 @@
+# tests/unit/test_static_files.py
+
 import unittest
 from app.server import app
 
@@ -8,15 +10,11 @@ def setUp(self):
         self.client = app.test_client()
 
     def test_css_loaded(self):
-        # On envoie une requête GET vers l'URL du CSS
         response = self.client.get('/static/css/style.css')
-        # Vérifie que le serveur renvoie bien un code 200 (OK)
         self.assertEqual(response.status_code, 200,
                          "Le CSS devrait être accessible et retourner un code 200.")
-        # Vérifie que le contenu du CSS n'est pas vide
         self.assertTrue(len(response.data) > 0,
                         "Le fichier CSS ne doit pas être vide.")
-        # Optionnel : vérifier qu'une règle CSS connue est présente (ici "body")
         self.assertIn(b"body", response.data,
                       "Le CSS doit contenir la règle 'body'.")
 
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()

From cb53592999c54cd196d007b61d6a5ee9edc7ca49 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Wed, 5 Feb 2025 17:11:14 +0100
Subject: [PATCH 07/15] =?UTF-8?q?tests=20unitaires=20suppl=C3=A9mentaires?=
 =?UTF-8?q?=20pour=20la=20gestion=20de=20la=20data?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/booking_manager/__init__.py               |  2 +
 app/booking_manager/booking_service.py        | 10 ++-
 app/booking_manager/club_manager.py           | 31 ++++++-
 app/booking_manager/competition_manager.py    | 23 +++++-
 app/booking_manager/data_loader.py            |  2 +
 app/models.py                                 |  3 +-
 app/server.py                                 |  2 +
 data/clubs.json                               | 38 +++++----
 data/competitions.json                        |  2 +-
 .../test_booking_manager_competition_full.py  |  3 +-
 ...est_booking_manager_insufficient_points.py |  4 +-
 .../unit/test_booking_service_persistence.py  | 81 +++++++++++++++++++
 tests/unit/test_club_manager_save.py          | 53 ++++++++++++
 tests/unit/test_welcome_page.py               |  2 -
 14 files changed, 225 insertions(+), 31 deletions(-)
 create mode 100644 tests/unit/test_booking_service_persistence.py
 create mode 100644 tests/unit/test_club_manager_save.py

diff --git a/app/booking_manager/__init__.py b/app/booking_manager/__init__.py
index 7fa7d72a8..9ec577cc3 100644
--- a/app/booking_manager/__init__.py
+++ b/app/booking_manager/__init__.py
@@ -1,3 +1,5 @@
+# app/booking_manager/__init__.py
+
 from .booking_service import BookingService
 from .club_manager import ClubManager
 from .competition_manager import CompetitionManager
diff --git a/app/booking_manager/booking_service.py b/app/booking_manager/booking_service.py
index f43714884..3c21d53b8 100644
--- a/app/booking_manager/booking_service.py
+++ b/app/booking_manager/booking_service.py
@@ -1,3 +1,5 @@
+# app/booking_manager/booking_service.py
+
 from app.booking_manager.club_manager import ClubManager
 from app.booking_manager.competition_manager import CompetitionManager
 
@@ -17,16 +19,18 @@ def purchase_places(self, club_name: str, competition_name: str, places_requeste
 
         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
+
+        # Sauvegarde des modifications dans les fichiers JSON
+        self.club_manager.save_clubs()
+        self.competition_manager.save_competitions()
+
         return True
diff --git a/app/booking_manager/club_manager.py b/app/booking_manager/club_manager.py
index 44755bacd..8376dddb1 100644
--- a/app/booking_manager/club_manager.py
+++ b/app/booking_manager/club_manager.py
@@ -1,3 +1,6 @@
+# app/booking_manager/club_manager.py
+
+import json
 from typing import List, Optional
 from app.models import Club
 from .data_loader import JSONDataLoader
@@ -5,10 +8,11 @@
 
 class ClubManager:
     """
-    Gère le chargement et la recherche des clubs.
+    Gère le chargement, la recherche et la sauvegarde des clubs.
     """
 
     def __init__(self, clubs_file: str):
+        self.clubs_file = clubs_file  # Pour la sauvegarde
         loader = JSONDataLoader(clubs_file)
         data = loader.load_data()
         self.clubs: List[Club] = self._parse_clubs(data)
@@ -20,13 +24,32 @@ def _parse_clubs(self, data: dict) -> List[Club]:
                 Club(
                     name=c["name"],
                     email=c["email"],
-                    points=int(c["points"])
+                    points=int(c["points"]),
+                    id=c.get("id")
                 )
             )
         return clubs
 
-    def find_by_email(self, email: str):
+    def find_by_email(self, email: str) -> Optional[Club]:
         return next((club for club in self.clubs if club.email == email), None)
 
-    def find_by_name(self, name: str):
+    def find_by_name(self, name: str) -> Optional[Club]:
         return next((club for club in self.clubs if club.name == name), None)
+
+    def save_clubs(self, filepath: Optional[str] = None) -> None:
+        """
+        Sauvegarde l'état actuel des clubs dans un fichier JSON.
+        """
+        if filepath is None:
+            filepath = self.clubs_file
+        clubs_data = {"clubs": []}
+        for club in self.clubs:
+            club_dict = {
+                "id": club.id,
+                "name": club.name,
+                "email": club.email,
+                "points": str(club.points)
+            }
+            clubs_data["clubs"].append(club_dict)
+        with open(filepath, "w") as f:
+            json.dump(clubs_data, f, indent=4)
diff --git a/app/booking_manager/competition_manager.py b/app/booking_manager/competition_manager.py
index 520f16da7..c94579932 100644
--- a/app/booking_manager/competition_manager.py
+++ b/app/booking_manager/competition_manager.py
@@ -1,3 +1,6 @@
+# app/booking_manager/competition_manager.py
+
+import json
 from typing import List, Optional
 from app.models import Competition
 from .data_loader import JSONDataLoader
@@ -5,10 +8,11 @@
 
 class CompetitionManager:
     """
-    Gère le chargement et la recherche des compétitions.
+    Gère le chargement, la recherche et la sauvegarde des compétitions.
     """
 
     def __init__(self, competitions_file: str):
+        self.competitions_file = competitions_file  # Pour la sauvegarde
         loader = JSONDataLoader(competitions_file)
         data = loader.load_data()
         self.competitions: List[Competition] = self._parse_competitions(data)
@@ -27,3 +31,20 @@ def _parse_competitions(self, data: dict) -> List[Competition]:
 
     def find_by_name(self, name: str) -> Optional[Competition]:
         return next((comp for comp in self.competitions if comp.name == name), None)
+
+    def save_competitions(self, filepath: Optional[str] = None) -> None:
+        """
+        Sauvegarde l'état actuel des compétitions dans un fichier JSON.
+        """
+        if filepath is None:
+            filepath = self.competitions_file
+        competitions_data = {"competitions": []}
+        for comp in self.competitions:
+            comp_dict = {
+                "name": comp.name,
+                "date": comp.date,
+                "numberOfPlaces": str(comp.number_of_places)
+            }
+            competitions_data["competitions"].append(comp_dict)
+        with open(filepath, "w") as f:
+            json.dump(competitions_data, f, indent=4)
diff --git a/app/booking_manager/data_loader.py b/app/booking_manager/data_loader.py
index 73ec2689f..82da6b023 100644
--- a/app/booking_manager/data_loader.py
+++ b/app/booking_manager/data_loader.py
@@ -1,3 +1,5 @@
+# app/booking_manager/data_loader.py
+
 import json
 import os
 from typing import Any
diff --git a/app/models.py b/app/models.py
index 9a7d96d8a..1783b24c5 100644
--- a/app/models.py
+++ b/app/models.py
@@ -8,7 +8,8 @@ def __init__(self, name, date, number_of_places):
 
 
 class Club:
-    def __init__(self, name, email, points):
+    def __init__(self, name, email, points, id=None):
         self.name = name
         self.email = email
         self.points = points
+        self.id = id
diff --git a/app/server.py b/app/server.py
index e119af1bf..96285b2b4 100644
--- a/app/server.py
+++ b/app/server.py
@@ -1,3 +1,5 @@
+# app/server.py
+
 from flask import Flask, render_template, request, redirect, flash, url_for
 from app.booking_manager import BookingService
 
diff --git a/data/clubs.json b/data/clubs.json
index 1d7ad1ffe..fff390673 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -1,16 +1,22 @@
-{"clubs":[
-    {
-        "name":"Simply Lift",
-        "email":"john@simplylift.co",
-        "points":"13"
-    },
-    {
-        "name":"Iron Temple",
-        "email": "admin@irontemple.com",
-        "points":"4"
-    },
-    {   "name":"She Lifts",
-        "email": "kate@shelifts.co.uk",
-        "points":"12"
-    }
-]}
\ No newline at end of file
+{
+    "clubs": [
+        {
+            "id": "1",
+            "name": "Simply Lift",
+            "email": "john@simplylift.co",
+            "points": "1"
+        },
+        {
+            "id": "2",
+            "name": "Iron Temple",
+            "email": "admin@irontemple.com",
+            "points": "1"
+        },
+        {
+            "id": "3",
+            "name": "She Lifts",
+            "email": "kate@shelifts.co.uk",
+            "points": "2"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/data/competitions.json b/data/competitions.json
index 039fc61bd..0b66bf807 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,7 +3,7 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "25"
+            "numberOfPlaces": "0"
         },
         {
             "name": "Fall Classic",
diff --git a/tests/unit/test_booking_manager_competition_full.py b/tests/unit/test_booking_manager_competition_full.py
index d45d06c5c..de99f2210 100644
--- a/tests/unit/test_booking_manager_competition_full.py
+++ b/tests/unit/test_booking_manager_competition_full.py
@@ -10,11 +10,10 @@ def setUp(self):
             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
+            competition.number_of_places = 2  # Forcer une compétition avec peu de places
         success = self.service.purchase_places(
             "Iron Temple", "Spring Festival", 3)
         self.assertFalse(
diff --git a/tests/unit/test_booking_manager_insufficient_points.py b/tests/unit/test_booking_manager_insufficient_points.py
index c6f9c4dc1..d4354a24b 100644
--- a/tests/unit/test_booking_manager_insufficient_points.py
+++ b/tests/unit/test_booking_manager_insufficient_points.py
@@ -1,3 +1,5 @@
+# tests/unit/test_booking_manager_insufficient_points.py
+
 import unittest
 from app.booking_manager.booking_service import BookingService
 
@@ -10,7 +12,7 @@ def setUp(self):
     def test_purchase_places_insufficient_points(self):
         club = self.service.club_manager.find_by_name("Iron Temple")
         if club:
-            club.points = 2
+            club.points = 2  # Forcer un club avec peu de points
         success = self.service.purchase_places(
             "Iron Temple", "Spring Festival", 3)
         self.assertFalse(
diff --git a/tests/unit/test_booking_service_persistence.py b/tests/unit/test_booking_service_persistence.py
new file mode 100644
index 000000000..11f72d00e
--- /dev/null
+++ b/tests/unit/test_booking_service_persistence.py
@@ -0,0 +1,81 @@
+# tests/unit/test_booking_service_persistence.py
+
+import unittest
+import os
+import json
+from app.booking_manager.booking_service import BookingService
+
+
+class TestBookingServicePersistence(unittest.TestCase):
+    def setUp(self):
+        # Créer des fichiers temporaires pour les tests
+        self.test_clubs_file = "data/test_clubs.json"
+        self.test_competitions_file = "data/test_competitions.json"
+
+        clubs_data = {
+            "clubs": [
+                {"id": "1", "name": "Simply Lift",
+                    "email": "john@simplylift.co", "points": "13"},
+                {"id": "2", "name": "Iron Temple",
+                    "email": "admin@irontemple.com", "points": "4"},
+                {"id": "3", "name": "She Lifts",
+                    "email": "kate@shelifts.co.uk", "points": "12"}
+            ]
+        }
+        competitions_data = {
+            "competitions": [
+                {"name": "Spring Festival", "date": "2020-03-27 10:00:00",
+                    "numberOfPlaces": "25"},
+                {"name": "Fall Classic", "date": "2020-10-22 13:30:00",
+                    "numberOfPlaces": "13"}
+            ]
+        }
+        with open(self.test_clubs_file, "w") as f:
+            json.dump(clubs_data, f, indent=4)
+        with open(self.test_competitions_file, "w") as f:
+            json.dump(competitions_data, f, indent=4)
+
+        self.service = BookingService(
+            clubs_file=self.test_clubs_file,
+            competitions_file=self.test_competitions_file
+        )
+
+    def tearDown(self):
+        if os.path.exists(self.test_clubs_file):
+            os.remove(self.test_clubs_file)
+        if os.path.exists(self.test_competitions_file):
+            os.remove(self.test_competitions_file)
+
+    def test_booking_service_persistence(self):
+        """
+        Vérifie qu'après une réservation de 3 places pour "Iron Temple" dans "Spring Festival",
+        le nombre de points du club est mis à jour (4 - 3 = 1) et le nombre de places restantes
+        dans la compétition est mis à jour (25 - 3 = 22).
+        """
+        success = self.service.purchase_places(
+            "Iron Temple", "Spring Festival", 3)
+        self.assertTrue(success, "La réservation devrait réussir.")
+
+        # Vérifier la mise à jour du fichier clubs
+        with open(self.test_clubs_file, "r") as f:
+            clubs_data = json.load(f)
+        iron_club = next(
+            (club for club in clubs_data["clubs"] if club["name"] == "Iron Temple"), None)
+        self.assertIsNotNone(
+            iron_club, "Les données du club Iron Temple doivent être présentes.")
+        self.assertEqual(
+            iron_club["points"], "1", "Les points doivent être mis à jour (4 - 3 = 1).")
+
+        # Vérifier la mise à jour du fichier competitions
+        with open(self.test_competitions_file, "r") as f:
+            competitions_data = json.load(f)
+        spring_comp = next(
+            (comp for comp in competitions_data["competitions"] if comp["name"] == "Spring Festival"), None)
+        self.assertIsNotNone(
+            spring_comp, "Les données de Spring Festival doivent être présentes.")
+        self.assertEqual(spring_comp["numberOfPlaces"], "22",
+                         "Les places doivent être mises à jour (25 - 3 = 22).")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_club_manager_save.py b/tests/unit/test_club_manager_save.py
new file mode 100644
index 000000000..8755f3f48
--- /dev/null
+++ b/tests/unit/test_club_manager_save.py
@@ -0,0 +1,53 @@
+# tests/unit/test_club_manager_save.py
+
+import unittest
+import os
+import json
+from app.booking_manager.club_manager import ClubManager
+
+
+class TestClubManagerSave(unittest.TestCase):
+    def setUp(self):
+        # Créez un fichier temporaire de clubs pour les tests
+        self.test_file = "data/test_clubs.json"
+        clubs_data = {
+            "clubs": [
+                {"id": "1", "name": "Simply Lift",
+                    "email": "john@simplylift.co", "points": "13"},
+                {"id": "2", "name": "Iron Temple",
+                    "email": "admin@irontemple.com", "points": "4"},
+                {"id": "3", "name": "She Lifts",
+                    "email": "kate@shelifts.co.uk", "points": "12"}
+            ]
+        }
+        with open(self.test_file, "w") as f:
+            json.dump(clubs_data, f, indent=4)
+        self.manager = ClubManager(clubs_file=self.test_file)
+
+    def tearDown(self):
+        # Supprimez le fichier temporaire après le test
+        if os.path.exists(self.test_file):
+            os.remove(self.test_file)
+
+    def test_save_clubs_updates_json(self):
+        # Modifier les points pour un club
+        club = self.manager.find_by_name("Iron Temple")
+        self.assertIsNotNone(club, "Le club 'Iron Temple' doit exister.")
+        club.points = 2  # On simule une réduction de points
+
+        # Sauvegarder l'état actuel
+        self.manager.save_clubs()
+
+        # Relire le fichier pour vérifier la mise à jour
+        with open(self.test_file, "r") as f:
+            data = json.load(f)
+        iron_data = next(
+            (c for c in data["clubs"] if c["name"] == "Iron Temple"), None)
+        self.assertIsNotNone(
+            iron_data, "Les données pour 'Iron Temple' doivent être présentes dans le fichier JSON.")
+        self.assertEqual(
+            iron_data["points"], "2", "Les points du club doivent être mis à jour dans le fichier JSON.")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_welcome_page.py b/tests/unit/test_welcome_page.py
index ec8b92f98..70abc8b95 100644
--- a/tests/unit/test_welcome_page.py
+++ b/tests/unit/test_welcome_page.py
@@ -14,8 +14,6 @@ def test_show_summary_unknown_email_redirects(self):
             '/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,

From 0cb020902b8ec7384f78279ecb776155ea9a3418 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Sun, 9 Feb 2025 20:27:59 +0100
Subject: [PATCH 08/15] usage test

---
 .flaskenv                                     |  2 +
 data/clubs.json                               |  6 +-
 data/competitions.json                        |  4 +-
 tests/integration/test_integration.py         | 53 +++++++++--
 .../unit/test_booking_service_persistence.py  | 93 ++++++++++++++-----
 5 files changed, 124 insertions(+), 34 deletions(-)
 create mode 100644 .flaskenv

diff --git a/.flaskenv b/.flaskenv
new file mode 100644
index 000000000..865919fdd
--- /dev/null
+++ b/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app.server
+FLASK_ENV=development
diff --git a/data/clubs.json b/data/clubs.json
index fff390673..300c3d907 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -4,19 +4,19 @@
             "id": "1",
             "name": "Simply Lift",
             "email": "john@simplylift.co",
-            "points": "1"
+            "points": "12"
         },
         {
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "1"
+            "points": "2"
         },
         {
             "id": "3",
             "name": "She Lifts",
             "email": "kate@shelifts.co.uk",
-            "points": "2"
+            "points": "7"
         }
     ]
 }
\ No newline at end of file
diff --git a/data/competitions.json b/data/competitions.json
index 0b66bf807..78cec8e06 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,12 +3,12 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "0"
+            "numberOfPlaces": "22"
         },
         {
             "name": "Fall Classic",
             "date": "2020-10-22 13:30:00",
-            "numberOfPlaces": "13"
+            "numberOfPlaces": "12"
         }
     ]
 }
\ No newline at end of file
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
index 45570f9ec..605d7b96a 100644
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -1,26 +1,67 @@
+# tests/integration/test_integration.py
+
 import unittest
+import os
+import json
 from app.server import app
 
 
 class TestServerRoutes(unittest.TestCase):
+    """
+    Test d'intégration pour vérifier qu'une réservation de 3 places
+    aboutit à un message de succès ('Great-booking complete!').
+    """
 
     def setUp(self):
+        """
+        Prépare l'application Flask en mode TEST et un client de test.
+        Si nécessaire, réinitialisez ici vos fichiers JSON
+        (par ex. copy test_clubs.json et test_competitions.json).
+        """
         app.config['TESTING'] = True
         self.client = app.test_client()
 
+        # Exemple (optionnel) : si vous avez un paramètre pour surcharger
+        # l'emplacement des fichiers clubs/competitions en mode test,
+        # vous pouvez le faire ici (non détaillé).
+
     def test_purchase_places_route(self):
+        """
+        Envoie un POST à '/purchasePlaces' pour réserver 3 places
+        pour 'Iron Temple' dans 'Spring Festival'. Vérifie qu'on obtient
+        un code 200 et un message de succès ('Great-booking complete!').
+        """
         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 suit la redirection pour voir le contenu final
+        response = self.client.post(
+            "/purchasePlaces",
+            data=data,
+            follow_redirects=True
+        )
+
+        # Vérifie qu'on obtient un code 200
+        self.assertEqual(
+            response.status_code,
+            200,
+            "La requête /purchasePlaces doit retourner un code 200 (OK)."
+        )
+
+        # Vérifie qu'on obtient bien le message de succès
+        # (ex. "Great-booking complete!")
+        lowercase_data = response.data.lower()
+        self.assertIn(
+            b"great-booking complete!",
+            lowercase_data,
+            "Doit contenir un message de réservation réussie ('Great-booking complete!')."
+        )
 
-        # On peut ajouter des checks sur le HTML renvoyé, le nombre de places, etc.
+        # Vous pouvez ajouter d'autres checks ici, par ex. vérifier que
+        # le nombre de places du club et/ou le nombre de points restants
+        # s'affichent correctement dans la page renvoyée.
 
 
 if __name__ == "__main__":
diff --git a/tests/unit/test_booking_service_persistence.py b/tests/unit/test_booking_service_persistence.py
index 11f72d00e..128d3327e 100644
--- a/tests/unit/test_booking_service_persistence.py
+++ b/tests/unit/test_booking_service_persistence.py
@@ -7,40 +7,72 @@
 
 
 class TestBookingServicePersistence(unittest.TestCase):
+    """
+    Teste la persistance des données lors de l'achat de places via BookingService.
+    """
+
     def setUp(self):
-        # Créer des fichiers temporaires pour les tests
+        """
+        Prépare deux fichiers JSON de test (pour clubs et compétitions)
+        afin de ne pas affecter les fichiers de production.
+        """
         self.test_clubs_file = "data/test_clubs.json"
         self.test_competitions_file = "data/test_competitions.json"
 
         clubs_data = {
             "clubs": [
-                {"id": "1", "name": "Simply Lift",
-                    "email": "john@simplylift.co", "points": "13"},
-                {"id": "2", "name": "Iron Temple",
-                    "email": "admin@irontemple.com", "points": "4"},
-                {"id": "3", "name": "She Lifts",
-                    "email": "kate@shelifts.co.uk", "points": "12"}
+                {
+                    "id": "1",
+                    "name": "Simply Lift",
+                    "email": "john@simplylift.co",
+                    "points": "13"
+                },
+                {
+                    "id": "2",
+                    "name": "Iron Temple",
+                    "email": "admin@irontemple.com",
+                    "points": "4"
+                },
+                {
+                    "id": "3",
+                    "name": "She Lifts",
+                    "email": "kate@shelifts.co.uk",
+                    "points": "12"
+                }
             ]
         }
+
         competitions_data = {
             "competitions": [
-                {"name": "Spring Festival", "date": "2020-03-27 10:00:00",
-                    "numberOfPlaces": "25"},
-                {"name": "Fall Classic", "date": "2020-10-22 13:30:00",
-                    "numberOfPlaces": "13"}
+                {
+                    "name": "Spring Festival",
+                    "date": "2020-03-27 10:00:00",
+                    "numberOfPlaces": "25"
+                },
+                {
+                    "name": "Fall Classic",
+                    "date": "2020-10-22 13:30:00",
+                    "numberOfPlaces": "13"
+                }
             ]
         }
+
+        # Écrit les données JSON dans des fichiers temporaires
         with open(self.test_clubs_file, "w") as f:
             json.dump(clubs_data, f, indent=4)
         with open(self.test_competitions_file, "w") as f:
             json.dump(competitions_data, f, indent=4)
 
+        # Instancie BookingService avec ces fichiers de test
         self.service = BookingService(
             clubs_file=self.test_clubs_file,
             competitions_file=self.test_competitions_file
         )
 
     def tearDown(self):
+        """
+        Supprime les fichiers temporaires pour ne pas polluer l'environnement.
+        """
         if os.path.exists(self.test_clubs_file):
             os.remove(self.test_clubs_file)
         if os.path.exists(self.test_competitions_file):
@@ -48,33 +80,48 @@ def tearDown(self):
 
     def test_booking_service_persistence(self):
         """
-        Vérifie qu'après une réservation de 3 places pour "Iron Temple" dans "Spring Festival",
-        le nombre de points du club est mis à jour (4 - 3 = 1) et le nombre de places restantes
-        dans la compétition est mis à jour (25 - 3 = 22).
+        Vérifie qu'après une réservation de 3 places pour "Iron Temple" dans "Spring Festival" :
+        - Le nombre de points du club est mis à jour (4 - 3 = 1).
+        - Le nombre de places dans la compétition est mis à jour (25 - 3 = 22).
+        - Les modifications sont bien persistées dans les fichiers JSON.
         """
         success = self.service.purchase_places(
             "Iron Temple", "Spring Festival", 3)
         self.assertTrue(success, "La réservation devrait réussir.")
 
-        # Vérifier la mise à jour du fichier clubs
+        # Vérification du fichier JSON des clubs
         with open(self.test_clubs_file, "r") as f:
             clubs_data = json.load(f)
+
         iron_club = next(
-            (club for club in clubs_data["clubs"] if club["name"] == "Iron Temple"), None)
+            (club for club in clubs_data["clubs"]
+             if club["name"] == "Iron Temple"),
+            None
+        )
         self.assertIsNotNone(
-            iron_club, "Les données du club Iron Temple doivent être présentes.")
+            iron_club, "Les données du club 'Iron Temple' doivent être présentes.")
         self.assertEqual(
-            iron_club["points"], "1", "Les points doivent être mis à jour (4 - 3 = 1).")
+            iron_club["points"], "1",
+            "Les points doivent être mis à jour (4 - 3 = 1)."
+        )
 
-        # Vérifier la mise à jour du fichier competitions
+        # Vérification du fichier JSON des compétitions
         with open(self.test_competitions_file, "r") as f:
             competitions_data = json.load(f)
+
         spring_comp = next(
-            (comp for comp in competitions_data["competitions"] if comp["name"] == "Spring Festival"), None)
+            (comp for comp in competitions_data["competitions"]
+             if comp["name"] == "Spring Festival"),
+            None
+        )
         self.assertIsNotNone(
-            spring_comp, "Les données de Spring Festival doivent être présentes.")
-        self.assertEqual(spring_comp["numberOfPlaces"], "22",
-                         "Les places doivent être mises à jour (25 - 3 = 22).")
+            spring_comp,
+            "Les données de 'Spring Festival' doivent être présentes."
+        )
+        self.assertEqual(
+            spring_comp["numberOfPlaces"], "22",
+            "Les places doivent être mises à jour (25 - 3 = 22)."
+        )
 
 
 if __name__ == '__main__':

From f4c6591c4bcd58684ed73b059b8dfc89564c624b Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Sun, 9 Feb 2025 20:40:01 +0100
Subject: [PATCH 09/15] correction sur test_persitence

---
 app/server.py          | 53 ++++++++++++++++++++++++++++++++++++++----
 data/clubs.json        |  2 +-
 data/competitions.json |  2 +-
 3 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/app/server.py b/app/server.py
index 96285b2b4..14703de59 100644
--- a/app/server.py
+++ b/app/server.py
@@ -6,7 +6,9 @@
 app = Flask(__name__, static_folder="../static")
 app.secret_key = "secret_key_xyz"
 
-# Instanciation du service de réservation
+# Instanciation du service de réservation, avec les JSON par défaut.
+# Si vous souhaitez utiliser des fichiers de test différents,
+# configurez-les dans le code ou via des variables d'environnement.
 booking_service = BookingService(
     clubs_file="data/clubs.json",
     competitions_file="data/competitions.json"
@@ -15,41 +17,71 @@
 
 @app.route("/")
 def index():
+    """
+    Page d'accueil de l'application.
+    """
     return render_template("index.html")
 
 
 @app.route("/showSummary", methods=["POST"])
 def show_summary():
+    """
+    Récupère l'email du club et affiche la page de résumé (welcome.html).
+    Si le club n'est pas trouvé, renvoie vers la page d'accueil avec un message d'erreur.
+    """
     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)
+
+    # Affiche la liste des compétitions disponibles
+    competitions = booking_service.competition_manager.competitions
+    return render_template("welcome.html", club=club, competitions=competitions)
 
 
 @app.route("/book/<competition>/<club>")
 def book(competition, club):
+    """
+    Affiche la page de réservation (booking.html) pour un club et une compétition donnés.
+    Si le club ou la compétition n'existe pas, renvoie vers l'index.
+    """
     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():
+    """
+    Tente d'acheter 'places' places pour un club et une compétition.
+    - Si la réservation réussit, on affiche 'Great-booking complete!'
+    - Sinon, on affiche 'Impossible de réserver...'
+    """
     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"))
 
+    # Vérification de l'existence du club et de la compétition
+    found_competition = booking_service.competition_manager.find_by_name(
+        competition_name)
+    found_club = booking_service.club_manager.find_by_name(club_name)
+    if not found_competition or not found_club:
+        flash("Something went wrong - please try again")
+        return redirect(url_for("index"))
+
+    # Appel à la logique métier
     success = booking_service.purchase_places(
         club_name, competition_name, places_requested)
     if success:
@@ -57,15 +89,26 @@ def purchase_places():
     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)
+    # Recharger les données du club et de la compétition (après mise à jour)
+    updated_club = booking_service.club_manager.find_by_name(club_name)
+    competitions = booking_service.competition_manager.competitions
+
+    return render_template("welcome.html", club=updated_club, competitions=competitions)
 
 
 @app.route("/clubsPoints")
 def clubs_points():
-    return render_template("clubs_points.html", clubs=booking_service.club_manager.clubs)
+    """
+    Affiche la liste des clubs et leurs points disponibles.
+    Page publique, sans besoin de login.
+    """
+    clubs = booking_service.club_manager.clubs
+    return render_template("clubs_points.html", clubs=clubs)
 
 
 @app.route("/logout")
 def logout():
+    """
+    Déconnecte le club (retour à l'accueil).
+    """
     return redirect(url_for("index"))
diff --git a/data/clubs.json b/data/clubs.json
index 300c3d907..a623b09c2 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,7 +10,7 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "2"
+            "points": "0"
         },
         {
             "id": "3",
diff --git a/data/competitions.json b/data/competitions.json
index 78cec8e06..258118aaa 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,7 +3,7 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "22"
+            "numberOfPlaces": "14"
         },
         {
             "name": "Fall Classic",

From 31c3da04e7057f5f86ba75bcb2820f7178a2a957 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Wed, 19 Feb 2025 13:39:32 +0100
Subject: [PATCH 10/15] =?UTF-8?q?mise=20en=20place=20du=20coverage=20pour?=
 =?UTF-8?q?=20la=20verification=20de=20la=20port=C3=A9e=20des=20tests?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .DS_Store              | Bin 6148 -> 6148 bytes
 .coverage              | Bin 0 -> 53248 bytes
 data/clubs.json        |   2 +-
 data/competitions.json |   2 +-
 4 files changed, 2 insertions(+), 2 deletions(-)
 create mode 100644 .coverage

diff --git a/.DS_Store b/.DS_Store
index ed2a65c8bc3d2bd8bb480af7d9ea7b7cbbf7c371..92e51b34986a28205eb6a042626ccd371dd32328 100644
GIT binary patch
delta 92
zcmZoMXfc=|#>B`mu~2NHo}w@d0|Nsi1A_nqLvc>JVQ_MOZo$ODm5eN#=dfloZC2o5
mXPMa0xtX1Vp9838vmnQJ=E?jbmO$lTZA?&_Wpjkc8fF03!4o3@

literal 6148
zcmeHKJxT*X6n<kOEJ>3_5Nt*~K?*CIHLR6|V9<8sCXxtQHxMoC)*Dy|9!4w0TX+Dm
z@dO&bH#4}iY$Di*=zB2pZQjp*Gat;p3=yf$`JheIBBB6;vEINm$2iWTVoT1$#mc;+
zPe;_J5gk)p$rgtwAPW381^DbXaav=V&;`zJaem|Ru-)xv>BKI%akn@#&7vq7X9+Tb
z(bMJY&Efs_SJ{JaWKTx^DtNh6ql8ZAw7kA}qlozHd4IXRem*ekadl}{@K>WHKBe(e
z@QuNj_<U;RYO{#=eCh7iW3cb|Or6hHjrx3Q&+skN8NQ%X%u}C}Xzc-<UexDZAI@fP
zcAuq;Vz2Wve14)H2*m!00_fRnLDQnlqJStM3VbTS-v^Ds7&^=?nokENa|Hm_;Wmb6
z)1!f!9l+3GZV?`svZX*<s@xSr*>d=`%L^Um7Hv5x!;JShX60@u%CN()O**O2qRgUz
zC{R|QW`DN0|L-im|Cfv8mna|#{3!)gJ?cgsY{~W3%I3J&YK$HR8|UQ~O$jE~j?DwN
d;#CY|h-rQRh7NO!@WAAcfRRBaQQ$`v_yDRMibenc

diff --git a/.coverage b/.coverage
new file mode 100644
index 0000000000000000000000000000000000000000..3d50707efa8f2dd9a018079037391b59de676963
GIT binary patch
literal 53248
zcmeI4du$s?9mjX=^<&p-j}s>`xwK^e>7~&nF}dm}$8og1Ba~iy^l|CqK-^|+uamvn
zUe~)$98gv5y{hAQNPt8T2Zet?LOlKgAwc2}5Fms&?l1ZW(zXA<fw&gLBR#cJ`OWOE
zA4y!A*wS&~x3WF6voqiM&1YUawr8I>e#EeKrC?S|nyu{Nk{r)-`xS-bI0@cyc>6~L
zJc54$F5lws<dwLo#TVn`6eov2&5@_#N6Do8a{S)dt8z*FWbCUEP0YasJdgkqKmthM
z|C@kwT#lrNhxupD+gi4$+f^;6SKZI}qbFt#%+4sY2Oc>xqqt$pZb^Y-a#A^<RLv!&
zqF0rIQPdTqoHufsZItH~dqMYO*DO6xCprj&sg6dRV(F<*HS&<k*5^T@QZ-6iby+#9
zFHf)tUT(Htx7`2`(T({sjiK!Jl8-A@y`We1a!$8gD|Q?CacTV73sNMVO7RzURwk{Q
zgIBDrG!W1%%Ao=X<;-fHTA!_I<=ld9O(<HUggF!JtlMsBt7a)<n@Z6rvtpJl!#2#a
zqSy6Y&DQf97I1<>^ACX6S{l%rqXn9CQ+j!|&}k;#Af*@GOI!fs%BGLCnUH1;iJ8si
zS}6<2pRScEv~nPYo3U+nO4%u`ZF+^h>$=&XE}P|Tt4<#|tx-2st!$jD>5a;tP?{+x
zSTV}xghC4eEnrtHoPIFHbI>aFx^=Dy_gkt~vrTrcLglN|@F73f7mcJxNBN8ASVN(B
z)U07!4UgE?Xd1yCnhw3*x!urS(%wjDFDe!lv%tCxO|7_<1FgBW&t!ZUT9tM*#q=VA
zrEh6#qsD>_O15Zb+uIszF_sP;1jYhCTA^0X(XL|AGUUufy{gUYQ{Gi~Sa_=&5;UD@
zF`S+m?bLJ{O+cM)cqL~z97&Ii@Xv^>8F{tkIgz#|<k#c|!>E6$q-$2K>JKD$8YKM`
z%~8_tBi<D#&SWH<J}}a$I9@z;+Ve!6h!9B+4)TuMBj~t9-=Vfb(2!;qSThmaz4;Z*
zUQpKCPJ_Ylu~0bu&|oLSsR`;d{R}_*(Eu~~;xMCMf<d3YgKZ|$kfzCp_2tP$tq7w@
zMKh|<Wnm0<X8?sp%xe%?%bGQtwO@M5nkSumn)bQ^&2yhU#2RPOp?e=QEz5vul}=mM
zd;L&lY(cZ=C}fQ(S<@`)TG^c&AjKgI@-!@k*|2_A+Sk;umZ`@4;uy2ZpIqoLKMS4C
z?_c04m9%o+o3H3J?5e`pPdg{g#~nl<p)v-Y(t<9!M$;GduF?_9*Pp5Y6RKL$@@{vh
zmC~ZPw4kq(b7!LuXtr(}C0(IWm@%wjEoqjLtLktO=F;)8;GLQQtu1w$e$rKPl-|lY
zqkJTN-+jDO^E#tD&Zrhl>LtxL+dB=kHIg+Ko9*Q&47hN&)7nlbyA~Yu)XjkoZf*@`
zT?&3I?_Nh5rwe?Z+~nYe2NFO6NB{{S0VIF~kN^@u0!RP}AOR$>{Rjm403V_2{{Xqd
zk!$dQ2NFO6NB{{S0VIF~kN^@u0!RP}AOR%swj>}2f;)--507^x`QXT~_aA`0Q}3Rd
zzMuZdKR{mR$m`@^Z_5Ir(MSLZAOR$R1dsp{Kmter2_OL^fCP|0gMb{|$@^ae1f<}I
z=zR-7fB#QMRgQd@%#vi{w~3D@M&rMXKOdixUy+}acf@`aJ1YHE`n>e8cwPLmcrbb+
z`fZ4g2NFO6NB{{S0VIF~IuSUW7C2`lbJBvXoS8)f9*$79^SV_sYdKx3)iZg0F>`F$
zUNFn*tZvaKHcU=uv`Qso>!nH&wq05owoQI&!7dda8v?Q6uEesMr8BL^2SIDJE3GBH
z2>V%Gxf25*H@-<ZD4lI(V5_bT#kFkJ6I3g7D|%)5M5+gxmJQFr$f<NQs|A`z`ayGN
zmv%yB!rddN&Gv!XWS43cco2eZsd>YK3SS1*(@9X>)1_*zSj(!g*BDS%RZCl>Ks5rw
z2fGx8J<?~PnW_|mk<(Rp=!j)lHht~~WML#Ai_<;I!Z+V2TeU*L$QjUiR2LUyH5Z4h
zW_q5LUWd|XWgBuR$dJSF9_IjErUE;y>9ZHqLakVYd=AARpY+!AQB?!_kg9^z$0U&2
zw@E2F-uTt(F^8vIG|lwFK>nl%@|hmU)1B^W(S&X4v>)w{f^29DvL!RG7iqsf6#<3m
zEhwxjcT2wk-6zAKySE3rem-8e0qGMVklyT&xlvWMRR4Zqiy#|A+9N?w5Vug^7AwvK
zK;wZ;YPgdD+m~O3A=@mwb3#i1FgwPB@~*CxTWqDj|EHsK9O+AZBB8{8CI2p-iNBC|
zCHkG{T=Y%yJ#tchUfvn|aqO7%hV%vL{o>!mm&Fftn(i?i2_OL^fCP{L5<mhRcSLYT
z_)XrhbXxzX4-3w4cVdn8|Imj8csNs6hPAH$2cHm}@y*Kpm-YWZFErPz|5J|(&dzS_
zY}_pOpA?+QZq?SU|NG7e&Yo^nH(LKEvx0N5Tj4benKkQw^0eTb?o}3dUjHXF!I|lO
zRxRuQ_`Kj8?{yBX3u|xvFFz(Y=`QkVS<b|s5}bXTmD*@g*=lBU{V$yooJ=p|+t>f%
z0l^7vMYgg2kDeBs>8&XE&AIWtNBjCe@)5z=+Y4PkpXT~Myp`c?qpE7D{?JxIHik6e
zQNa<nQs5RVf=>$01Dn;jtMz~2nBeT%?6(u^m0N7Z=l|hd8WKPPNB{{S0VIF~kN^@u
z0!RP}Ac1X7Ajk)~IQ{*fC$~6w;eiB@01`j~NB{{S0VIF~kN^@u0!RP}Y(oOUU|eMT
z{}X@W$eZNP<SO|M`8oL^`4;&)`679dIAn<wNtPTY50m@Jy<`XJBZ0&}AUYmM00|%g
zB!C2v01`j~NB{{S0VIF~{uc>IX@M7q(${X^yg3pd0{@PoYq#NuMdkP)JsqT%Zr}bX
zFAl)zz`)Atg+Md~-u~3e>QSj5d_(=;IJ2@`5c_C^zLnqqMU<1^y(d}!_ocI6xs@6u
za5h2eAH4k9>ebaN5B@zce?52oGtmSD_a{I_jMJ3ymDTfss7wRpmDNk(82Az~UY5YU
zN2>o~_2^3%B2kfE7vGb6C=vx<GJ4~d9H9{+5aH4fZd{kba5fZPIk#jEg+t&^gz6~)
z+}*<E%BSQ@!`wUmaOJ8tJQohqi$OjS0Qa4N`U`W(_kH#viH3MV6caq#|4%fI+$8@b
zZ;(Hd*T`k^D)}|}8TkqMK0Foho8)WcOXPFpCGs3OPZo(u79ctvNB{{S0VIF~kN^@u
z0!RP}AOR$R1du>00lH4-(`n`m4KZhMkU0Yb%t@t~)8EgWzCPw8lguH6If(>w;&JB4
zGIL@v=1A<D0A3W?Sv1O=NQ61zFmpm7<_H3Ff<fj40s?#iz<&SdlC1`#9|<4<B!C2v
z01`j~NB{{S0VIF~kN^_cZUpG>|G56&ZncW#K>|ns2_OL^fCP{L5<mh-00|%gB+yQP
l?*GU2e>)RALjp(u2_OL^fCP{L5<mh-00|%gB(U8G{0A-1VK)E(

literal 0
HcmV?d00001

diff --git a/data/clubs.json b/data/clubs.json
index a623b09c2..bd4e54d10 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,7 +10,7 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "0"
+            "points": "22"
         },
         {
             "id": "3",
diff --git a/data/competitions.json b/data/competitions.json
index 258118aaa..f07b83025 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,7 +3,7 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "14"
+            "numberOfPlaces": "8"
         },
         {
             "name": "Fall Classic",

From a6eff6f35d2571bd8d52a2747bc354529ffb37e8 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Wed, 19 Feb 2025 16:33:06 +0100
Subject: [PATCH 11/15] phase 1 complete

---
 Python_Testing                                |  1 -
 app/server.py                                 | 58 +++++++++++++------
 app/templates/base.html                       | 17 +++++-
 data/clubs.json                               |  4 +-
 data/competitions.json                        |  4 +-
 tests/integration/test_integration.py         | 33 +----------
 .../test_booking_manager_competition_full.py  |  6 +-
 tests/unit/test_booking_manager_inexistant.py |  6 +-
 ...est_booking_manager_insufficient_points.py |  6 +-
 .../unit/test_booking_service_persistence.py  | 16 ++---
 tests/unit/test_club_manager_save.py          |  2 -
 tests/unit/test_points_display.py             |  4 +-
 tests/unit/test_server.py                     | 32 +++++++---
 tests/unit/test_static_files.py               |  2 -
 tests/unit/test_welcome_page.py               |  2 -
 15 files changed, 100 insertions(+), 93 deletions(-)
 delete mode 160000 Python_Testing

diff --git a/Python_Testing b/Python_Testing
deleted file mode 160000
index 4ca9cd9a7..000000000
--- a/Python_Testing
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 4ca9cd9a7c414e7cc5c8d3a8539a9f9b09a0b6f0
diff --git a/app/server.py b/app/server.py
index 14703de59..c28fea893 100644
--- a/app/server.py
+++ b/app/server.py
@@ -1,24 +1,33 @@
 # app/server.py
 
-from flask import Flask, render_template, request, redirect, flash, url_for
+from flask import Flask, render_template, request, redirect, flash, url_for, session
 from app.booking_manager import BookingService
 
 app = Flask(__name__, static_folder="../static")
 app.secret_key = "secret_key_xyz"
 
-# Instanciation du service de réservation, avec les JSON par défaut.
-# Si vous souhaitez utiliser des fichiers de test différents,
-# configurez-les dans le code ou via des variables d'environnement.
+# Instanciation du service de réservation
 booking_service = BookingService(
     clubs_file="data/clubs.json",
     competitions_file="data/competitions.json"
 )
 
 
+@app.context_processor
+def inject_club_email():
+    """
+    Permet d'accéder à session['club_email'] dans les templates,
+    afin de savoir si un utilisateur est connecté.
+    """
+    return dict(club_email=session.get('club_email'))
+
+
 @app.route("/")
 def index():
     """
-    Page d'accueil de l'application.
+    Page d'accueil.
+    Si un utilisateur est connecté, on va quand même afficher index.html,
+    mais le bouton "Accueil" le renverra à showSummary.
     """
     return render_template("index.html")
 
@@ -27,7 +36,7 @@ def index():
 def show_summary():
     """
     Récupère l'email du club et affiche la page de résumé (welcome.html).
-    Si le club n'est pas trouvé, renvoie vers la page d'accueil avec un message d'erreur.
+    Si le club n'est pas trouvé, on redirige avec un message d'erreur.
     """
     email = request.form.get("email", "")
     club = booking_service.club_manager.find_by_email(email)
@@ -35,7 +44,9 @@ def show_summary():
         flash("Email inconnu ou invalide.")
         return redirect(url_for("index"))
 
-    # Affiche la liste des compétitions disponibles
+    # Stocker l'email en session pour signifier "connecté"
+    session['club_email'] = club.email
+
     competitions = booking_service.competition_manager.competitions
     return render_template("welcome.html", club=club, competitions=competitions)
 
@@ -43,8 +54,7 @@ def show_summary():
 @app.route("/book/<competition>/<club>")
 def book(competition, club):
     """
-    Affiche la page de réservation (booking.html) pour un club et une compétition donnés.
-    Si le club ou la compétition n'existe pas, renvoie vers l'index.
+    Affiche la page de réservation (booking.html) pour un club/compétition donné.
     """
     found_competition = booking_service.competition_manager.find_by_name(
         competition)
@@ -60,8 +70,6 @@ def book(competition, club):
 def purchase_places():
     """
     Tente d'acheter 'places' places pour un club et une compétition.
-    - Si la réservation réussit, on affiche 'Great-booking complete!'
-    - Sinon, on affiche 'Impossible de réserver...'
     """
     competition_name = request.form.get("competition")
     club_name = request.form.get("club")
@@ -73,26 +81,37 @@ def purchase_places():
         flash("Le nombre de places est invalide.")
         return redirect(url_for("index"))
 
-    # Vérification de l'existence du club et de la compétition
     found_competition = booking_service.competition_manager.find_by_name(
         competition_name)
     found_club = booking_service.club_manager.find_by_name(club_name)
+
     if not found_competition or not found_club:
         flash("Something went wrong - please try again")
         return redirect(url_for("index"))
 
-    # Appel à la logique métier
-    success = booking_service.purchase_places(
-        club_name, competition_name, places_requested)
+    # Logique de réservation : on veut des messages plus précis
+    if places_requested > 12:
+        flash("Vous ne pouvez pas réserver plus de 12 places.")
+        success = False
+    else:
+        success = booking_service.purchase_places(
+            club_name, competition_name, places_requested)
+
     if success:
-        flash("Great-booking complete!")
+        flash(
+            f"Great-booking complete! Vous avez réservé {places_requested} places.")
     else:
-        flash("Impossible de réserver ces places (Règle non respectée).")
+        # On vérifie pourquoi c'est False :
+        # - Soit le club n'a pas assez de points
+        # - Soit la compétition n'a pas assez de places (ou est passée)
+        # Dans booking_service, on a un return False générique.
+        # On va personnaliser le message selon la situation :
+        if places_requested <= 12:  # si ce n'est pas déjà la condition "plus de 12"
+            # On suppose que c'est un problème de places trop petites ou club pas assez de points
+            flash("Le concours est complet (ou il ne reste pas assez de places) ou vous n'avez pas assez de points.")
 
-    # Recharger les données du club et de la compétition (après mise à jour)
     updated_club = booking_service.club_manager.find_by_name(club_name)
     competitions = booking_service.competition_manager.competitions
-
     return render_template("welcome.html", club=updated_club, competitions=competitions)
 
 
@@ -111,4 +130,5 @@ def logout():
     """
     Déconnecte le club (retour à l'accueil).
     """
+    session.pop('club_email', None)
     return redirect(url_for("index"))
diff --git a/app/templates/base.html b/app/templates/base.html
index b2b0af466..a30e3b98b 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -1,4 +1,3 @@
-<!-- app/templates/base.html -->
 <!DOCTYPE html>
 <html lang="fr">
 <head>
@@ -10,11 +9,25 @@
 <body>
   <nav>
     <ul>
-      <li><a href="{{ url_for('index') }}">Accueil</a></li>
+      {% if club_email %}
+        <!-- Si connecté, 'Accueil' renvoie à showSummary, en POST on a besoin d'un form ou un GET param -->
+        <li>
+          <form action="{{ url_for('show_summary') }}" method="POST" style="display: inline;">
+
+            <input type="hidden" name="email" value="{{ club_email }}">
+            <button type="submit" style="background:none;border:none;color:blue;cursor:pointer;">
+              Accueil
+            </button>
+          </form>
+        </li>
+      {% else %}
+        <li><a href="{{ url_for('index') }}">Accueil</a></li>
+      {% endif %}
       <li><a href="{{ url_for('clubs_points') }}">Points Clubs</a></li>
       <li><a href="{{ url_for('logout') }}">Déconnexion</a></li>
     </ul>
   </nav>
+
   <div class="container">
     {% with messages = get_flashed_messages() %}
       {% if messages %}
diff --git a/data/clubs.json b/data/clubs.json
index bd4e54d10..0a44b28a4 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,13 +10,13 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "22"
+            "points": "3"
         },
         {
             "id": "3",
             "name": "She Lifts",
             "email": "kate@shelifts.co.uk",
-            "points": "7"
+            "points": "14"
         }
     ]
 }
\ No newline at end of file
diff --git a/data/competitions.json b/data/competitions.json
index f07b83025..f62075ae2 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,12 +3,12 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "8"
+            "numberOfPlaces": "12"
         },
         {
             "name": "Fall Classic",
             "date": "2020-10-22 13:30:00",
-            "numberOfPlaces": "12"
+            "numberOfPlaces": "11"
         }
     ]
 }
\ No newline at end of file
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
index 605d7b96a..2c95192d6 100644
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -1,8 +1,4 @@
-# tests/integration/test_integration.py
-
 import unittest
-import os
-import json
 from app.server import app
 
 
@@ -13,18 +9,9 @@ class TestServerRoutes(unittest.TestCase):
     """
 
     def setUp(self):
-        """
-        Prépare l'application Flask en mode TEST et un client de test.
-        Si nécessaire, réinitialisez ici vos fichiers JSON
-        (par ex. copy test_clubs.json et test_competitions.json).
-        """
         app.config['TESTING'] = True
         self.client = app.test_client()
 
-        # Exemple (optionnel) : si vous avez un paramètre pour surcharger
-        # l'emplacement des fichiers clubs/competitions en mode test,
-        # vous pouvez le faire ici (non détaillé).
-
     def test_purchase_places_route(self):
         """
         Envoie un POST à '/purchasePlaces' pour réserver 3 places
@@ -38,20 +25,10 @@ def test_purchase_places_route(self):
         }
         # On suit la redirection pour voir le contenu final
         response = self.client.post(
-            "/purchasePlaces",
-            data=data,
-            follow_redirects=True
-        )
-
-        # Vérifie qu'on obtient un code 200
-        self.assertEqual(
-            response.status_code,
-            200,
-            "La requête /purchasePlaces doit retourner un code 200 (OK)."
-        )
+            "/purchasePlaces", data=data, follow_redirects=True)
+        self.assertEqual(response.status_code, 200,
+                         "La requête /purchasePlaces doit retourner un code 200 (OK).")
 
-        # Vérifie qu'on obtient bien le message de succès
-        # (ex. "Great-booking complete!")
         lowercase_data = response.data.lower()
         self.assertIn(
             b"great-booking complete!",
@@ -59,10 +36,6 @@ def test_purchase_places_route(self):
             "Doit contenir un message de réservation réussie ('Great-booking complete!')."
         )
 
-        # Vous pouvez ajouter d'autres checks ici, par ex. vérifier que
-        # le nombre de places du club et/ou le nombre de points restants
-        # s'affichent correctement dans la page renvoyée.
-
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unit/test_booking_manager_competition_full.py b/tests/unit/test_booking_manager_competition_full.py
index de99f2210..45c26436b 100644
--- a/tests/unit/test_booking_manager_competition_full.py
+++ b/tests/unit/test_booking_manager_competition_full.py
@@ -1,5 +1,3 @@
-# tests/unit/test_booking_manager_competition_full.py
-
 import unittest
 from app.booking_manager.booking_service import BookingService
 
@@ -7,7 +5,9 @@
 class TestBookingManagerCompetitionFull(unittest.TestCase):
     def setUp(self):
         self.service = BookingService(
-            clubs_file="data/clubs.json", competitions_file="data/competitions.json")
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
 
     def test_purchase_places_competition_full(self):
         competition = self.service.competition_manager.find_by_name(
diff --git a/tests/unit/test_booking_manager_inexistant.py b/tests/unit/test_booking_manager_inexistant.py
index ecd69d779..0d5a368c5 100644
--- a/tests/unit/test_booking_manager_inexistant.py
+++ b/tests/unit/test_booking_manager_inexistant.py
@@ -1,5 +1,3 @@
-# tests/unit/test_booking_manager_inexistant.py
-
 import unittest
 from app.booking_manager.booking_service import BookingService
 
@@ -7,7 +5,9 @@
 class TestBookingManagerInexistant(unittest.TestCase):
     def setUp(self):
         self.service = BookingService(
-            clubs_file="data/clubs.json", competitions_file="data/competitions.json")
+            clubs_file="data/clubs.json",
+            competitions_file="data/competitions.json"
+        )
 
     def test_purchase_places_club_not_found(self):
         success = self.service.purchase_places(
diff --git a/tests/unit/test_booking_manager_insufficient_points.py b/tests/unit/test_booking_manager_insufficient_points.py
index d4354a24b..ae86b7579 100644
--- a/tests/unit/test_booking_manager_insufficient_points.py
+++ b/tests/unit/test_booking_manager_insufficient_points.py
@@ -1,5 +1,3 @@
-# tests/unit/test_booking_manager_insufficient_points.py
-
 import unittest
 from app.booking_manager.booking_service import BookingService
 
@@ -7,7 +5,9 @@
 class TestBookingManagerInsufficientPoints(unittest.TestCase):
     def setUp(self):
         self.service = BookingService(
-            clubs_file="data/clubs.json", competitions_file="data/competitions.json")
+            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")
diff --git a/tests/unit/test_booking_service_persistence.py b/tests/unit/test_booking_service_persistence.py
index 128d3327e..db423138a 100644
--- a/tests/unit/test_booking_service_persistence.py
+++ b/tests/unit/test_booking_service_persistence.py
@@ -1,5 +1,3 @@
-# tests/unit/test_booking_service_persistence.py
-
 import unittest
 import os
 import json
@@ -101,9 +99,7 @@ def test_booking_service_persistence(self):
         self.assertIsNotNone(
             iron_club, "Les données du club 'Iron Temple' doivent être présentes.")
         self.assertEqual(
-            iron_club["points"], "1",
-            "Les points doivent être mis à jour (4 - 3 = 1)."
-        )
+            iron_club["points"], "1", "Les points doivent être mis à jour (4 - 3 = 1).")
 
         # Vérification du fichier JSON des compétitions
         with open(self.test_competitions_file, "r") as f:
@@ -115,13 +111,9 @@ def test_booking_service_persistence(self):
             None
         )
         self.assertIsNotNone(
-            spring_comp,
-            "Les données de 'Spring Festival' doivent être présentes."
-        )
-        self.assertEqual(
-            spring_comp["numberOfPlaces"], "22",
-            "Les places doivent être mises à jour (25 - 3 = 22)."
-        )
+            spring_comp, "Les données de 'Spring Festival' doivent être présentes.")
+        self.assertEqual(spring_comp["numberOfPlaces"], "22",
+                         "Les places doivent être mises à jour (25 - 3 = 22).")
 
 
 if __name__ == '__main__':
diff --git a/tests/unit/test_club_manager_save.py b/tests/unit/test_club_manager_save.py
index 8755f3f48..2b91f0ab7 100644
--- a/tests/unit/test_club_manager_save.py
+++ b/tests/unit/test_club_manager_save.py
@@ -1,5 +1,3 @@
-# tests/unit/test_club_manager_save.py
-
 import unittest
 import os
 import json
diff --git a/tests/unit/test_points_display.py b/tests/unit/test_points_display.py
index cf731bb04..b0db19cfe 100644
--- a/tests/unit/test_points_display.py
+++ b/tests/unit/test_points_display.py
@@ -1,5 +1,3 @@
-# tests/unit/test_points_display.py
-
 import unittest
 from app.server import app
 
@@ -13,7 +11,7 @@ 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
+        # Vérifie qu'un lien "Points Clubs" est présent
         self.assertIn(b"Points Clubs", response.data)
 
     def test_points_display_on_welcome(self):
diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py
index a32d405b6..2c53c197d 100644
--- a/tests/unit/test_server.py
+++ b/tests/unit/test_server.py
@@ -3,6 +3,9 @@
 
 
 class TestWelcomePage(unittest.TestCase):
+    """
+    Teste la page welcome en cas d'email inconnu.
+    """
 
     def setUp(self):
         """
@@ -17,11 +20,16 @@ def test_show_summary_no_undefined_error_for_unknown_email(self):
         et ne produit pas d'erreur Jinja une fois la redirection suivie.
         """
         response = self.client.post(
-            '/showSummary', data={'email': 'email_inconnu@example.com'})
+            '/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.")
+        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(
@@ -30,9 +38,19 @@ def test_show_summary_no_undefined_error_for_unknown_email(self):
             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.")
+        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.")
+        self.assertNotIn(
+            b'UndefinedError',
+            response_followed.data,
+            "Aucune erreur Jinja ne doit être présente dans la page finale."
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/unit/test_static_files.py b/tests/unit/test_static_files.py
index 553b15a2c..b6b5be4df 100644
--- a/tests/unit/test_static_files.py
+++ b/tests/unit/test_static_files.py
@@ -1,5 +1,3 @@
-# tests/unit/test_static_files.py
-
 import unittest
 from app.server import app
 
diff --git a/tests/unit/test_welcome_page.py b/tests/unit/test_welcome_page.py
index 70abc8b95..3b87acce8 100644
--- a/tests/unit/test_welcome_page.py
+++ b/tests/unit/test_welcome_page.py
@@ -1,5 +1,3 @@
-# tests/unit/test_welcome_page.py
-
 import unittest
 from app.server import app
 

From e4b4efd6725deae8a6fc6133e9c775972654d6ab Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Wed, 19 Feb 2025 18:30:22 +0100
Subject: [PATCH 12/15] mise en place du test selenium

---
 app/server.py                               | 48 ++++----------------
 app/templates/base.html                     |  3 +-
 app/templates/booking.html                  |  2 +-
 data/clubs.json                             |  2 +-
 data/competitions.json                      |  2 +-
 tests/functional/test_functional.py         |  0
 tests/functional/test_functional_booking.py | 50 +++++++++++++++++++++
 7 files changed, 63 insertions(+), 44 deletions(-)
 delete mode 100644 tests/functional/test_functional.py
 create mode 100644 tests/functional/test_functional_booking.py

diff --git a/app/server.py b/app/server.py
index c28fea893..964138138 100644
--- a/app/server.py
+++ b/app/server.py
@@ -6,7 +6,6 @@
 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"
@@ -15,47 +14,29 @@
 
 @app.context_processor
 def inject_club_email():
-    """
-    Permet d'accéder à session['club_email'] dans les templates,
-    afin de savoir si un utilisateur est connecté.
-    """
     return dict(club_email=session.get('club_email'))
 
 
 @app.route("/")
 def index():
-    """
-    Page d'accueil.
-    Si un utilisateur est connecté, on va quand même afficher index.html,
-    mais le bouton "Accueil" le renverra à showSummary.
-    """
     return render_template("index.html")
 
 
 @app.route("/showSummary", methods=["POST"])
 def show_summary():
-    """
-    Récupère l'email du club et affiche la page de résumé (welcome.html).
-    Si le club n'est pas trouvé, on redirige avec un message d'erreur.
-    """
     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"))
 
-    # Stocker l'email en session pour signifier "connecté"
     session['club_email'] = club.email
-
     competitions = booking_service.competition_manager.competitions
     return render_template("welcome.html", club=club, competitions=competitions)
 
 
 @app.route("/book/<competition>/<club>")
 def book(competition, club):
-    """
-    Affiche la page de réservation (booking.html) pour un club/compétition donné.
-    """
     found_competition = booking_service.competition_manager.find_by_name(
         competition)
     found_club = booking_service.club_manager.find_by_name(club)
@@ -68,9 +49,6 @@ def book(competition, club):
 
 @app.route("/purchasePlaces", methods=["POST"])
 def purchase_places():
-    """
-    Tente d'acheter 'places' places pour un club et une compétition.
-    """
     competition_name = request.form.get("competition")
     club_name = request.form.get("club")
     places_str = request.form.get("places")
@@ -86,10 +64,10 @@ def purchase_places():
     found_club = booking_service.club_manager.find_by_name(club_name)
 
     if not found_competition or not found_club:
-        flash("Something went wrong - please try again")
+        flash("Something went wrong - please try again.")
         return redirect(url_for("index"))
 
-    # Logique de réservation : on veut des messages plus précis
+    # Logique de réservation
     if places_requested > 12:
         flash("Vous ne pouvez pas réserver plus de 12 places.")
         success = False
@@ -101,15 +79,14 @@ def purchase_places():
         flash(
             f"Great-booking complete! Vous avez réservé {places_requested} places.")
     else:
-        # On vérifie pourquoi c'est False :
-        # - Soit le club n'a pas assez de points
-        # - Soit la compétition n'a pas assez de places (ou est passée)
-        # Dans booking_service, on a un return False générique.
-        # On va personnaliser le message selon la situation :
-        if places_requested <= 12:  # si ce n'est pas déjà la condition "plus de 12"
-            # On suppose que c'est un problème de places trop petites ou club pas assez de points
-            flash("Le concours est complet (ou il ne reste pas assez de places) ou vous n'avez pas assez de points.")
+        if places_requested <= 12:
+            flash("Le concours est complet ou vous n'avez pas assez de points.")
+
+    return redirect(url_for("show_purchase_result", club_name=club_name))
+
 
+@app.route("/showPurchaseResult/<club_name>")
+def show_purchase_result(club_name):
     updated_club = booking_service.club_manager.find_by_name(club_name)
     competitions = booking_service.competition_manager.competitions
     return render_template("welcome.html", club=updated_club, competitions=competitions)
@@ -117,18 +94,11 @@ def purchase_places():
 
 @app.route("/clubsPoints")
 def clubs_points():
-    """
-    Affiche la liste des clubs et leurs points disponibles.
-    Page publique, sans besoin de login.
-    """
     clubs = booking_service.club_manager.clubs
     return render_template("clubs_points.html", clubs=clubs)
 
 
 @app.route("/logout")
 def logout():
-    """
-    Déconnecte le club (retour à l'accueil).
-    """
     session.pop('club_email', None)
     return redirect(url_for("index"))
diff --git a/app/templates/base.html b/app/templates/base.html
index a30e3b98b..fe5cb6d58 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -10,10 +10,9 @@
   <nav>
     <ul>
       {% if club_email %}
-        <!-- Si connecté, 'Accueil' renvoie à showSummary, en POST on a besoin d'un form ou un GET param -->
+        <!-- Si connecté, 'Accueil' renvoie à show_summary, en POST -->
         <li>
           <form action="{{ url_for('show_summary') }}" method="POST" style="display: inline;">
-
             <input type="hidden" name="email" value="{{ club_email }}">
             <button type="submit" style="background:none;border:none;color:blue;cursor:pointer;">
               Accueil
diff --git a/app/templates/booking.html b/app/templates/booking.html
index be884db6e..8a3543438 100644
--- a/app/templates/booking.html
+++ b/app/templates/booking.html
@@ -6,7 +6,7 @@
 {% block content %}
   <h2>{{ competition['name'] }}</h2>
   <p>Places disponibles: {{ competition['number_of_places'] }}</p>
-  <form action="/purchasePlaces" method="post">
+  <form action="{{ url_for('purchase_places') }}" method="post">
     <input type="hidden" name="club" value="{{ club['name'] }}">
     <input type="hidden" name="competition" value="{{ competition['name'] }}">
     <label for="places">Combien de places ?</label>
diff --git a/data/clubs.json b/data/clubs.json
index 0a44b28a4..8a3469eea 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,7 +10,7 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "3"
+            "points": "10"
         },
         {
             "id": "3",
diff --git a/data/competitions.json b/data/competitions.json
index f62075ae2..9d5687360 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,7 +3,7 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "12"
+            "numberOfPlaces": "21"
         },
         {
             "name": "Fall Classic",
diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/functional/test_functional_booking.py b/tests/functional/test_functional_booking.py
new file mode 100644
index 000000000..1c6171e9e
--- /dev/null
+++ b/tests/functional/test_functional_booking.py
@@ -0,0 +1,50 @@
+# tests/functional/test_functional_booking.py
+
+import unittest
+import time
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+
+class TestFunctionalBooking(unittest.TestCase):
+
+    def setUp(self):
+        self.driver = webdriver.Chrome()
+        self.base_url = "http://127.0.0.1:5000"
+
+    def tearDown(self):
+        self.driver.quit()
+
+    def test_book_places_selenium(self):
+        driver = self.driver
+        driver.get(self.base_url)
+
+        # Saisir l'email et cliquer
+        email_input = driver.find_element(By.ID, "email")
+        email_input.send_keys("admin@irontemple.com")
+        submit_button = driver.find_element(
+            By.CSS_SELECTOR, "button[type='submit']")
+        submit_button.click()
+
+        # Cliquer sur "Réserver" (Spring Festival)
+        link = driver.find_element(By.LINK_TEXT, "Réserver")
+        link.click()
+
+        # Saisir 3 places
+        places_input = driver.find_element(By.ID, "places")
+        places_input.send_keys("3")
+        book_button = driver.find_element(
+            By.CSS_SELECTOR, "button[type='submit']")
+        book_button.click()
+
+        # Attendre la redirection
+        time.sleep(1)
+
+        # Vérifier "great-booking complete!"
+        body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
+        print("DEBUG body_text:\n", body_text)
+        self.assertIn("great-booking complete!", body_text)
+
+
+if __name__ == "__main__":
+    unittest.main()

From 648945c7aa964a25b37338d9abce468aca5c21aa Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Sun, 23 Feb 2025 14:19:34 +0100
Subject: [PATCH 13/15] test selenium valide

---
 app/server.py                               | 30 +++++++++++++++++++++
 app/templates/booking.html                  |  3 ++-
 data/clubs.json                             |  2 +-
 data/competitions.json                      |  4 +--
 tests/functional/test_functional_booking.py | 27 +++++++++----------
 5 files changed, 48 insertions(+), 18 deletions(-)

diff --git a/app/server.py b/app/server.py
index 964138138..d3b0b42c2 100644
--- a/app/server.py
+++ b/app/server.py
@@ -6,6 +6,7 @@
 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"
@@ -14,16 +15,27 @@
 
 @app.context_processor
 def inject_club_email():
+    """
+    Permet d'accéder à session['club_email'] dans les templates,
+    afin de savoir si un utilisateur est connecté.
+    """
     return dict(club_email=session.get('club_email'))
 
 
 @app.route("/")
 def index():
+    """
+    Page d'accueil.
+    """
     return render_template("index.html")
 
 
 @app.route("/showSummary", methods=["POST"])
 def show_summary():
+    """
+    Récupère l'email du club et affiche la page de résumé (welcome.html).
+    Si le club n'est pas trouvé, on redirige avec un message d'erreur.
+    """
     email = request.form.get("email", "")
     club = booking_service.club_manager.find_by_email(email)
     if not club:
@@ -37,6 +49,9 @@ def show_summary():
 
 @app.route("/book/<competition>/<club>")
 def book(competition, club):
+    """
+    Affiche la page de réservation (booking.html) pour un club/compétition donné.
+    """
     found_competition = booking_service.competition_manager.find_by_name(
         competition)
     found_club = booking_service.club_manager.find_by_name(club)
@@ -49,6 +64,11 @@ def book(competition, club):
 
 @app.route("/purchasePlaces", methods=["POST"])
 def purchase_places():
+    """
+    Tente d'acheter 'places' places pour un club et une compétition.
+    Après avoir flashé un message de succès ou d'erreur,
+    on redirige vers /showPurchaseResult/<club_name> pour afficher la page finale.
+    """
     competition_name = request.form.get("competition")
     club_name = request.form.get("club")
     places_str = request.form.get("places")
@@ -82,11 +102,15 @@ def purchase_places():
         if places_requested <= 12:
             flash("Le concours est complet ou vous n'avez pas assez de points.")
 
+    # Redirection vers la route qui affiche le résultat
     return redirect(url_for("show_purchase_result", club_name=club_name))
 
 
 @app.route("/showPurchaseResult/<club_name>")
 def show_purchase_result(club_name):
+    """
+    Affiche la page welcome.html, lit le message flash si besoin.
+    """
     updated_club = booking_service.club_manager.find_by_name(club_name)
     competitions = booking_service.competition_manager.competitions
     return render_template("welcome.html", club=updated_club, competitions=competitions)
@@ -94,11 +118,17 @@ def show_purchase_result(club_name):
 
 @app.route("/clubsPoints")
 def clubs_points():
+    """
+    Liste des clubs et leurs points.
+    """
     clubs = booking_service.club_manager.clubs
     return render_template("clubs_points.html", clubs=clubs)
 
 
 @app.route("/logout")
 def logout():
+    """
+    Déconnecte le club.
+    """
     session.pop('club_email', None)
     return redirect(url_for("index"))
diff --git a/app/templates/booking.html b/app/templates/booking.html
index 8a3543438..ae7f33ba6 100644
--- a/app/templates/booking.html
+++ b/app/templates/booking.html
@@ -11,6 +11,7 @@ <h2>{{ competition['name'] }}</h2>
     <input type="hidden" name="competition" value="{{ competition['name'] }}">
     <label for="places">Combien de places ?</label>
     <input type="number" name="places" id="places" min="1" required>
-    <button type="submit">Réserver</button>
+    <!-- Ajout d'un id -->
+    <button id="submit-booking" type="submit">Réserver</button>
   </form>
 {% endblock %}
diff --git a/data/clubs.json b/data/clubs.json
index 8a3469eea..9175ef60b 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,7 +10,7 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "10"
+            "points": "31"
         },
         {
             "id": "3",
diff --git a/data/competitions.json b/data/competitions.json
index 9d5687360..cc8b2f958 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,12 +3,12 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "21"
+            "numberOfPlaces": "36"
         },
         {
             "name": "Fall Classic",
             "date": "2020-10-22 13:30:00",
-            "numberOfPlaces": "11"
+            "numberOfPlaces": "34"
         }
     ]
 }
\ No newline at end of file
diff --git a/tests/functional/test_functional_booking.py b/tests/functional/test_functional_booking.py
index 1c6171e9e..618f55bb4 100644
--- a/tests/functional/test_functional_booking.py
+++ b/tests/functional/test_functional_booking.py
@@ -1,9 +1,10 @@
 # tests/functional/test_functional_booking.py
 
 import unittest
-import time
 from selenium import webdriver
 from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
 
 
 class TestFunctionalBooking(unittest.TestCase):
@@ -19,30 +20,28 @@ def test_book_places_selenium(self):
         driver = self.driver
         driver.get(self.base_url)
 
-        # Saisir l'email et cliquer
+        # 1) Saisir l'email -> Se connecter
         email_input = driver.find_element(By.ID, "email")
         email_input.send_keys("admin@irontemple.com")
-        submit_button = driver.find_element(
-            By.CSS_SELECTOR, "button[type='submit']")
-        submit_button.click()
+        driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
 
-        # Cliquer sur "Réserver" (Spring Festival)
+        # 2) welcome.html => "Réserver" (Spring Festival)
         link = driver.find_element(By.LINK_TEXT, "Réserver")
         link.click()
 
-        # Saisir 3 places
+        # 3) booking.html => Saisir 3 places
         places_input = driver.find_element(By.ID, "places")
         places_input.send_keys("3")
-        book_button = driver.find_element(
-            By.CSS_SELECTOR, "button[type='submit']")
-        book_button.click()
+        # Désormais on clique spécifiquement le bouton id="submit-booking"
+        driver.find_element(By.ID, "submit-booking").click()
 
-        # Attendre la redirection
-        time.sleep(1)
+        # 4) Attente explicite d'apparition du flash
+        wait = WebDriverWait(driver, 5)
+        wait.until(EC.presence_of_element_located(
+            (By.CSS_SELECTOR, ".flashes")))
 
-        # Vérifier "great-booking complete!"
+        # 5) Vérification
         body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
-        print("DEBUG body_text:\n", body_text)
         self.assertIn("great-booking complete!", body_text)
 
 

From 9708d8116c1753663e596d29b43aa053dfd48984 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Sun, 23 Feb 2025 14:43:06 +0100
Subject: [PATCH 14/15] feature added

---
 app/booking_manager/club_manager.py        | 24 ++++++-
 app/booking_manager/competition_manager.py | 24 ++++++-
 data/clubs.json                            |  4 +-
 data/competitions.json                     |  4 +-
 data/fresh_clubs.json                      | 22 ++++++
 data/fresh_competitions.json               | 14 ++++
 tests/unit/test_data_reset.py              | 80 ++++++++++++++++++++++
 7 files changed, 162 insertions(+), 10 deletions(-)
 create mode 100644 data/fresh_clubs.json
 create mode 100644 data/fresh_competitions.json
 create mode 100644 tests/unit/test_data_reset.py

diff --git a/app/booking_manager/club_manager.py b/app/booking_manager/club_manager.py
index 8376dddb1..802b643fb 100644
--- a/app/booking_manager/club_manager.py
+++ b/app/booking_manager/club_manager.py
@@ -1,6 +1,8 @@
 # app/booking_manager/club_manager.py
 
 import json
+import os
+import shutil
 from typing import List, Optional
 from app.models import Club
 from .data_loader import JSONDataLoader
@@ -12,9 +14,9 @@ class ClubManager:
     """
 
     def __init__(self, clubs_file: str):
-        self.clubs_file = clubs_file  # Pour la sauvegarde
-        loader = JSONDataLoader(clubs_file)
-        data = loader.load_data()
+        self.clubs_file = clubs_file  # Fichier "de travail"
+        self.loader = JSONDataLoader(clubs_file)
+        data = self.loader.load_data()
         self.clubs: List[Club] = self._parse_clubs(data)
 
     def _parse_clubs(self, data: dict) -> List[Club]:
@@ -53,3 +55,19 @@ def save_clubs(self, filepath: Optional[str] = None) -> None:
             clubs_data["clubs"].append(club_dict)
         with open(filepath, "w") as f:
             json.dump(clubs_data, f, indent=4)
+
+    def reset_data(self, fresh_filepath: str) -> None:
+        """
+        Copie le fichier fresh_filepath dans self.clubs_file,
+        puis recharge la liste des clubs en mémoire.
+        """
+        if not os.path.exists(fresh_filepath):
+            raise FileNotFoundError(
+                f"Fichier source introuvable : {fresh_filepath}")
+
+        # 1. Copie fresh_filepath => self.clubs_file
+        shutil.copy(fresh_filepath, self.clubs_file)
+
+        # 2. Recharge en mémoire
+        data = self.loader.load_data()
+        self.clubs = self._parse_clubs(data)
diff --git a/app/booking_manager/competition_manager.py b/app/booking_manager/competition_manager.py
index c94579932..1f33520ff 100644
--- a/app/booking_manager/competition_manager.py
+++ b/app/booking_manager/competition_manager.py
@@ -1,6 +1,8 @@
 # app/booking_manager/competition_manager.py
 
 import json
+import os
+import shutil
 from typing import List, Optional
 from app.models import Competition
 from .data_loader import JSONDataLoader
@@ -12,9 +14,9 @@ class CompetitionManager:
     """
 
     def __init__(self, competitions_file: str):
-        self.competitions_file = competitions_file  # Pour la sauvegarde
-        loader = JSONDataLoader(competitions_file)
-        data = loader.load_data()
+        self.competitions_file = competitions_file  # Fichier "de travail"
+        self.loader = JSONDataLoader(competitions_file)
+        data = self.loader.load_data()
         self.competitions: List[Competition] = self._parse_competitions(data)
 
     def _parse_competitions(self, data: dict) -> List[Competition]:
@@ -48,3 +50,19 @@ def save_competitions(self, filepath: Optional[str] = None) -> None:
             competitions_data["competitions"].append(comp_dict)
         with open(filepath, "w") as f:
             json.dump(competitions_data, f, indent=4)
+
+    def reset_data(self, fresh_filepath: str) -> None:
+        """
+        Copie le fichier fresh_filepath dans self.competitions_file,
+        puis recharge les competitions en mémoire.
+        """
+        if not os.path.exists(fresh_filepath):
+            raise FileNotFoundError(
+                f"Fichier source introuvable : {fresh_filepath}")
+
+        # 1. Copie fresh_filepath => self.competitions_file
+        shutil.copy(fresh_filepath, self.competitions_file)
+
+        # 2. Recharge en mémoire
+        data = self.loader.load_data()  # relit le fichier de travail
+        self.competitions = self._parse_competitions(data)
diff --git a/data/clubs.json b/data/clubs.json
index 9175ef60b..e29743cee 100644
--- a/data/clubs.json
+++ b/data/clubs.json
@@ -10,7 +10,7 @@
             "id": "2",
             "name": "Iron Temple",
             "email": "admin@irontemple.com",
-            "points": "31"
+            "points": "34"
         },
         {
             "id": "3",
@@ -19,4 +19,4 @@
             "points": "14"
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/data/competitions.json b/data/competitions.json
index cc8b2f958..8790b8a91 100644
--- a/data/competitions.json
+++ b/data/competitions.json
@@ -3,7 +3,7 @@
         {
             "name": "Spring Festival",
             "date": "2020-03-27 10:00:00",
-            "numberOfPlaces": "36"
+            "numberOfPlaces": "39"
         },
         {
             "name": "Fall Classic",
@@ -11,4 +11,4 @@
             "numberOfPlaces": "34"
         }
     ]
-}
\ No newline at end of file
+}
diff --git a/data/fresh_clubs.json b/data/fresh_clubs.json
new file mode 100644
index 000000000..e29743cee
--- /dev/null
+++ b/data/fresh_clubs.json
@@ -0,0 +1,22 @@
+{
+    "clubs": [
+        {
+            "id": "1",
+            "name": "Simply Lift",
+            "email": "john@simplylift.co",
+            "points": "12"
+        },
+        {
+            "id": "2",
+            "name": "Iron Temple",
+            "email": "admin@irontemple.com",
+            "points": "34"
+        },
+        {
+            "id": "3",
+            "name": "She Lifts",
+            "email": "kate@shelifts.co.uk",
+            "points": "14"
+        }
+    ]
+}
diff --git a/data/fresh_competitions.json b/data/fresh_competitions.json
new file mode 100644
index 000000000..8790b8a91
--- /dev/null
+++ b/data/fresh_competitions.json
@@ -0,0 +1,14 @@
+{
+    "competitions": [
+        {
+            "name": "Spring Festival",
+            "date": "2020-03-27 10:00:00",
+            "numberOfPlaces": "39"
+        },
+        {
+            "name": "Fall Classic",
+            "date": "2020-10-22 13:30:00",
+            "numberOfPlaces": "34"
+        }
+    ]
+}
diff --git a/tests/unit/test_data_reset.py b/tests/unit/test_data_reset.py
new file mode 100644
index 000000000..58d9f9568
--- /dev/null
+++ b/tests/unit/test_data_reset.py
@@ -0,0 +1,80 @@
+# tests/unit/test_data_reset.py
+
+import unittest
+import os
+import shutil
+from app.booking_manager.club_manager import ClubManager
+from app.booking_manager.competition_manager import CompetitionManager
+
+
+class TestDataReset(unittest.TestCase):
+
+    def setUp(self):
+        # Chemins vers vos fichiers "de travail"
+        self.clubs_file = "data/clubs.json"
+        self.competitions_file = "data/competitions.json"
+
+        # Chemins vers des fichiers "frais" (après clonage)
+        self.fresh_clubs = "data/fresh_clubs.json"
+        self.fresh_competitions = "data/fresh_competitions.json"
+
+        # On suppose que fresh_clubs.json / fresh_competitions.json existent déjà
+        # et contiennent l'état initial (points=34 etc.)
+
+        # S'assurer de partir sur un état connu
+        shutil.copy(self.fresh_clubs, self.clubs_file)
+        shutil.copy(self.fresh_competitions, self.competitions_file)
+
+        self.club_manager = ClubManager(self.clubs_file)
+        self.competition_manager = CompetitionManager(self.competitions_file)
+
+    def tearDown(self):
+        # (optionnel) remettre fresh ou faire rien
+        pass
+
+    def test_reset_data(self):
+        """
+        Vérifie qu'après une modification, reset_data() restaure l'état initial.
+        """
+        # 1) On modifie un club
+        club = self.club_manager.find_by_name("Iron Temple")
+        self.assertIsNotNone(club)
+        club.points = 0  # On simule un usage
+
+        # 2) Sauvegarder
+        self.club_manager.save_clubs()
+
+        # 3) Vérifie qu'il a bien 0 points
+        manager_check = ClubManager(self.clubs_file)
+        updated_club = manager_check.find_by_name("Iron Temple")
+        self.assertEqual(updated_club.points, 0)
+
+        # 4) On appelle reset_data
+        self.club_manager.reset_data(self.fresh_clubs)
+
+        # 5) Vérifie que c'est revenu à l'état frais (34 par exemple)
+        club_after_reset = self.club_manager.find_by_name("Iron Temple")
+        self.assertEqual(club_after_reset.points, 34,
+                         "Le club doit retrouver ses points initiaux.")
+
+        # Pareil pour les compétitions
+        comp = self.competition_manager.find_by_name("Spring Festival")
+        self.assertIsNotNone(comp)
+        comp.number_of_places = 10
+        self.competition_manager.save_competitions()
+
+        # re-load pour vérifier
+        manager_check_comp = CompetitionManager(self.competitions_file)
+        self.assertEqual(manager_check_comp.find_by_name(
+            "Spring Festival").number_of_places, 10)
+
+        # reset
+        self.competition_manager.reset_data(self.fresh_competitions)
+        comp_after_reset = self.competition_manager.find_by_name(
+            "Spring Festival")
+        self.assertEqual(comp_after_reset.number_of_places, 39,
+                         "La compétition doit retrouver ses places initiales (39).")
+
+
+if __name__ == "__main__":
+    unittest.main()

From e95fa6abfbd30d88d53395974b7020da448b81a4 Mon Sep 17 00:00:00 2001
From: VincentDesmouceaux <desmontvincent@gmail.com>
Date: Sun, 23 Feb 2025 15:06:53 +0100
Subject: [PATCH 15/15] feature ok

---
 tests/functional/__init__.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 tests/functional/__init__.py

diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
new file mode 100644
index 000000000..e69de29bb