diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 4ba731c..c58c5c7 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -8,7 +8,7 @@ on: jobs: # ───────────────────────────────────────────── - # BUILD — commun aux deux branches + # COMPILATION — commun aux deux branches # ───────────────────────────────────────────── build: name: Build & Push Docker Image @@ -20,17 +20,11 @@ jobs: # Le tag d'image dépend de la branche : # main → webzine:latest - # dev → webzine:dev - name: Set image tag id: vars run: | - if [ "${{ gitea.ref_name }}" = "main" ]; then - echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT - echo "ENV_LABEL=production" >> $GITHUB_OUTPUT - else - echo "IMAGE_TAG=dev" >> $GITHUB_OUTPUT - echo "ENV_LABEL=development" >> $GITHUB_OUTPUT - fi + echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT + echo "ENV_LABEL=production" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -43,6 +37,7 @@ jobs: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} + # Construction et publication de l'image Docker - name: Build and push Docker image uses: docker/build-push-action@v5 with: @@ -58,37 +53,36 @@ jobs: env_label: ${{ steps.vars.outputs.ENV_LABEL }} # ───────────────────────────────────────────── - # DEPLOY — Machine de PRODUCTION (branche main) + # DÉPLOIEMENT — Serveur de PRODUCTION (branche main) # ───────────────────────────────────────────── deploy-production: name: Deploy to Production needs: build - if: gitea.ref_name == 'main' runs-on: ubuntu-latest steps: - name: Deploy via SSH to PRODUCTION server uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.PROD_SSH_HOST }} - username: ${{ secrets.PROD_SSH_USER }} + host: ${{ secrets.PROD_HOST }} + username: ${{ secrets.PROD_USER }} key: ${{ secrets.PROD_SSH_KEY }} port: ${{ secrets.PROD_SSH_PORT || 22 }} script: | set -e - echo "=== [PROD] Pulling image ===" + echo "=== [PROD] Récupération de l'image ===" docker login ${{ vars.REGISTRY_URL }} \ -u ${{ secrets.REGISTRY_USERNAME }} \ -p ${{ secrets.REGISTRY_PASSWORD }} docker pull ${{ vars.REGISTRY_URL }}/webzine/webzine:latest - echo "=== [PROD] Stopping old container ===" + echo "=== [PROD] Arrêt de l'ancien conteneur ===" docker stop webzine-prod 2>/dev/null || true docker rm webzine-prod 2>/dev/null || true - echo "=== [PROD] Starting new container ===" + echo "=== [PROD] Démarrage du nouveau conteneur ===" docker run -d \ --name webzine-prod \ --restart unless-stopped \ @@ -99,53 +93,7 @@ jobs: -e ASPNETCORE_ENVIRONMENT=Production \ ${{ vars.REGISTRY_URL }}/webzine/webzine:latest - echo "=== [PROD] Cleaning up old images ===" + echo "=== [PROD] Nettoyage des anciennes images ===" docker image prune -f - echo "=== [PROD] Deployment complete ===" - - # ───────────────────────────────────────────── - # DEPLOY — Machine de DÉVELOPPEMENT (branche dev) - # ───────────────────────────────────────────── - deploy-development: - name: Deploy to Development - needs: build - if: gitea.ref_name == 'dev' - runs-on: ubuntu-latest - - steps: - - name: Deploy via SSH to DEVELOPMENT server - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.DEV_SSH_HOST }} - username: ${{ secrets.DEV_SSH_USER }} - key: ${{ secrets.DEV_SSH_KEY }} - port: ${{ secrets.DEV_SSH_PORT || 22 }} - script: | - set -e - - echo "=== [DEV] Pulling image ===" - docker login ${{ vars.REGISTRY_URL }} \ - -u ${{ secrets.REGISTRY_USERNAME }} \ - -p ${{ secrets.REGISTRY_PASSWORD }} - - docker pull ${{ vars.REGISTRY_URL }}/webzine/webzine:dev - - echo "=== [DEV] Stopping old container ===" - docker stop webzine-dev 2>/dev/null || true - docker rm webzine-dev 2>/dev/null || true - - echo "=== [DEV] Starting new container ===" - docker run -d \ - --name webzine-dev \ - --restart unless-stopped \ - -p 8080:8080 \ - -v /opt/webzine/dev/data:/app/Data \ - -v /opt/webzine/dev/logs:/Logs \ - -e ASPNETCORE_ENVIRONMENT=Development \ - ${{ vars.REGISTRY_URL }}/webzine/webzine:dev - - echo "=== [DEV] Cleaning up old images ===" - docker image prune -f - - echo "=== [DEV] Deployment complete ===" \ No newline at end of file + echo "=== [PROD] Déploiement terminé ===" \ No newline at end of file diff --git a/.gitea/workflows/pr-endpoint-check.yml b/.gitea/workflows/pr-endpoint-check.yml index c0936d2..c67fab3 100644 --- a/.gitea/workflows/pr-endpoint-check.yml +++ b/.gitea/workflows/pr-endpoint-check.yml @@ -14,13 +14,13 @@ jobs: steps: # ───────────────────────────────────────────── - # 1. Checkout code + # Récupération du code source # ───────────────────────────────────────────── - name: Checkout PR branch uses: actions/checkout@v4 # ───────────────────────────────────────────── - # 2. Setup .NET 10 + # Installation de .NET 10 # ───────────────────────────────────────────── - name: Setup .NET 10 uses: actions/setup-dotnet@v4 @@ -28,7 +28,7 @@ jobs: dotnet-version: "10.0.x" # ───────────────────────────────────────────── - # 3. Restore & Build + # Restauration des dépendances et compilation # ───────────────────────────────────────────── - name: Restore dependencies run: dotnet restore Webzine.sln @@ -37,7 +37,7 @@ jobs: run: dotnet build Webzine.sln --no-restore --configuration Release # ───────────────────────────────────────────── - # 4. Run unit tests (entity tests) + # Exécution des tests unitaires (entités) # ───────────────────────────────────────────── - name: Run unit tests run: | @@ -47,7 +47,7 @@ jobs: --logger "console;verbosity=normal" # ───────────────────────────────────────────── - # 5. Start the web application in background + # Démarrage de l'application web en arrière-plan # ───────────────────────────────────────────── - name: Start Webzine application run: | @@ -57,16 +57,16 @@ jobs: --no-build \ -- --urls "http://localhost:5038" & - echo "Waiting for application to start..." + echo "Attente du démarrage de l'application..." timeout 60 bash -c ' until curl -sf http://localhost:5038 > /dev/null 2>&1; do sleep 1 done ' - echo "Application is ready!" + echo "Application prête !" # ───────────────────────────────────────────── - # 6. Run endpoint performance tests + # Exécution des tests de performance des endpoints # ───────────────────────────────────────────── - name: Test endpoint response times id: perf_test @@ -77,7 +77,7 @@ jobs: echo "failed=$FAIL_COUNT" >> "$GITHUB_OUTPUT" # ───────────────────────────────────────────── - # 7. Post report as PR comment + # Publication du rapport en commentaire de PR # ───────────────────────────────────────────── - name: Post performance report as PR comment if: always() @@ -87,15 +87,15 @@ jobs: REPO: ${{ gitea.repository }} PR_NUMBER: ${{ gitea.event.pull_request.number }} run: | - REPORT_CONTENT=$(cat /tmp/webzine_endpoint_report.txt 2>/dev/null || echo "No report generated.") + REPORT_CONTENT=$(cat /tmp/webzine_endpoint_report.txt 2>/dev/null || echo "Aucun rapport généré.") FAILED_COUNT="${{ steps.perf_test.outputs.failed }}" if [ "${FAILED_COUNT:-0}" -gt 0 ]; then - HEADER="## ❌ Performance Check FAILED" - INTRO="${FAILED_COUNT} endpoint(s) exceeded 1 second or returned a server error." + HEADER="## ❌ Vérification des performances ÉCHOUÉE" + INTRO="${FAILED_COUNT} endpoint(s) ont dépassé 1 seconde ou retourné une erreur serveur." else - HEADER="## ✅ Performance Check PASSED" - INTRO="All endpoints responded in under 1 second." + HEADER="## ✅ Vérification des performances RÉUSSIE" + INTRO="Tous les endpoints ont répondu en moins d'une seconde." fi BODY=$(cat < Threshold: **1000ms** | Checked by the **PR Endpoint Performance** workflow. + > Seuil : **1000ms** | Vérifié par le workflow **PR Endpoint Performance**. EOF ) @@ -118,15 +118,15 @@ jobs: "$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" # ───────────────────────────────────────────── - # 8. Fail the job if any endpoint failed + # Blocage de la PR si un endpoint a échoué # ───────────────────────────────────────────── - name: Enforce performance gate run: | FAILED="${{ steps.perf_test.outputs.failed }}" if [ "${FAILED:-0}" -gt 0 ]; then - echo "❌ PR REJECTED: ${FAILED} endpoint(s) failed the 1-second threshold." - echo " Fix the slow/failing endpoints listed above before merging." + echo "❌ PR REJETÉE : ${FAILED} endpoint(s) n'ont pas respecté le seuil d'une seconde." + echo " Corrigez les endpoints lents ou en erreur avant de fusionner." exit 1 else - echo "✅ All endpoints passed the performance gate." + echo "✅ Tous les endpoints ont passé le contrôle de performance." fi \ No newline at end of file diff --git a/scripts/test-endpoints.sh b/scripts/test-endpoints.sh index a768ecd..146cfcb 100644 --- a/scripts/test-endpoints.sh +++ b/scripts/test-endpoints.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # ============================================================================= # test-endpoints.sh -# Tests all Webzine endpoints and reports which ones exceed the threshold. +# Teste tous les endpoints du Webzine et signale ceux qui dépassent le seuil. # -# Usage: -# ./scripts/test-endpoints.sh [BASE_URL] [MAX_MS] +# Usage : +# ./scripts/test-endpoints.sh [URL_BASE] [MAX_MS] # -# Examples: -# ./scripts/test-endpoints.sh # defaults: localhost:5038, 1000ms +# Exemples : +# ./scripts/test-endpoints.sh # défauts : localhost:5038, 1000ms # ./scripts/test-endpoints.sh http://localhost:5038 500 # ============================================================================= @@ -15,207 +15,213 @@ set -euo pipefail BASE_URL="${1:-http://localhost:5038}" MAX_MS="${2:-1000}" -REPORT_FILE="/tmp/webzine_endpoint_report.txt" -FAILED=0 +RAPPORT_FICHIER="/tmp/webzine_rapport_endpoints.txt" +ECHECS=0 TOTAL=0 -# ── Colours ────────────────────────────────────────────────────────────────── -RED='\033[0;31m' -YELLOW='\033[1;33m' -GREEN='\033[0;32m' +# ── Couleurs ────────────────────────────────────────────────────────────────── +ROUGE='\033[0;31m' +JAUNE='\033[1;33m' +VERT='\033[0;32m' CYAN='\033[0;36m' -BOLD='\033[1m' +GRAS='\033[1m' RESET='\033[0m' -# ── Helpers ────────────────────────────────────────────────────────────────── -log() { echo -e "$*"; } -pass() { log "${GREEN}[PASS]${RESET} $*"; } -fail() { log "${RED}[FAIL]${RESET} $*"; FAILED=$((FAILED + 1)); } -slow() { log "${YELLOW}[SLOW]${RESET} $*"; FAILED=$((FAILED + 1)); } -info() { log "${CYAN}$*${RESET}"; } +# ── Fonctions utilitaires ────────────────────────────────────────────────────── +log() { echo -e "$*"; } +succes() { log "${VERT}[OK]${RESET} $*"; } +echec() { log "${ROUGE}[ÉCHEC]${RESET} $*"; ECHECS=$((ECHECS + 1)); } +lent() { log "${JAUNE}[LENT]${RESET} $*"; ECHECS=$((ECHECS + 1)); } +info() { log "${CYAN}$*${RESET}"; } -# ── check_endpoint ──────────────────────────────────────────────────────────── -# Arguments: -# $1 HTTP method (GET | POST) -# $2 URL (absolute) -# $3 Label (human-readable) -# $4 Body data (optional, for POST) -# $5 Content-Type (optional, default: application/x-www-form-urlencoded) +# ── verifier_endpoint ───────────────────────────────────────────────────────── +# Arguments : +# $1 Méthode HTTP (GET | POST) +# $2 URL (absolue) +# $3 Libellé (texte lisible) +# $4 Corps (optionnel, pour POST) +# $5 Content-Type (optionnel, défaut : application/x-www-form-urlencoded) # ───────────────────────────────────────────────────────────────────────────── -check_endpoint() { - local METHOD="${1:-GET}" +verifier_endpoint() { + local METHODE="${1:-GET}" local URL="$2" - local LABEL="$3" - local BODY="${4:-}" - local CONTENT_TYPE="${5:-application/x-www-form-urlencoded}" + local LIBELLE="$3" + local CORPS="${4:-}" + local TYPE_CONTENU="${5:-application/x-www-form-urlencoded}" TOTAL=$((TOTAL + 1)) - if [ "$METHOD" = "POST" ] && [ -n "$BODY" ]; then - RESPONSE=$(curl -s -o /dev/null \ + # Exécution de la requête selon la méthode HTTP + if [ "$METHODE" = "POST" ] && [ -n "$CORPS" ]; then + REPONSE=$(curl -s -o /dev/null \ -w "%{http_code}|%{time_total}" \ -X POST \ - -H "Content-Type: $CONTENT_TYPE" \ - -d "$BODY" \ + -H "Content-Type: $TYPE_CONTENU" \ + -d "$CORPS" \ --max-time 10 \ - "$URL" 2>&1) || RESPONSE="000|9.999" + "$URL" 2>&1) || REPONSE="000|9.999" else - RESPONSE=$(curl -s -o /dev/null \ + REPONSE=$(curl -s -o /dev/null \ -w "%{http_code}|%{time_total}" \ -X GET \ --max-time 10 \ --location \ - "$URL" 2>&1) || RESPONSE="000|9.999" + "$URL" 2>&1) || REPONSE="000|9.999" fi - HTTP_CODE=$(echo "$RESPONSE" | cut -d'|' -f1) - TIME_TOTAL=$(echo "$RESPONSE" | cut -d'|' -f2) + # Extraction du code HTTP et du temps de réponse + CODE_HTTP=$(echo "$REPONSE" | cut -d'|' -f1) + TEMPS_TOTAL=$(echo "$REPONSE" | cut -d'|' -f2) - # Convert to integer milliseconds — awk is locale-safe, no bc/printf decimal issues - TIME_MS=$(awk "BEGIN {printf \"%.0f\", $TIME_TOTAL * 1000}") + # Conversion en millisecondes entières — awk évite les problèmes de locale décimale + TEMPS_MS=$(awk "BEGIN {printf \"%.0f\", $TEMPS_TOTAL * 1000}") - # Evaluate result - if [ "${HTTP_CODE:-0}" -ge 500 ] 2>/dev/null; then - slow "$LABEL → HTTP $HTTP_CODE (${TIME_MS}ms)" - echo "[FAIL] $LABEL → HTTP $HTTP_CODE (${TIME_MS}ms)" >> "$REPORT_FILE" - elif [ "${TIME_MS:-99999}" -gt "$MAX_MS" ] 2>/dev/null; then - slow "$LABEL → ${TIME_MS}ms exceeds ${MAX_MS}ms threshold" - echo "[SLOW] $LABEL → ${TIME_MS}ms (limit: ${MAX_MS}ms)" >> "$REPORT_FILE" + # Évaluation du résultat : erreur serveur, dépassement de seuil ou succès + if [ "${CODE_HTTP:-0}" -ge 500 ] 2>/dev/null; then + echec "$LIBELLE → HTTP $CODE_HTTP (${TEMPS_MS}ms)" + echo "[ÉCHEC] $LIBELLE → HTTP $CODE_HTTP (${TEMPS_MS}ms)" >> "$RAPPORT_FICHIER" + elif [ "${TEMPS_MS:-99999}" -gt "$MAX_MS" ] 2>/dev/null; then + lent "$LIBELLE → ${TEMPS_MS}ms dépasse le seuil de ${MAX_MS}ms" + echo "[LENT] $LIBELLE → ${TEMPS_MS}ms (limite : ${MAX_MS}ms)" >> "$RAPPORT_FICHIER" else - pass "$LABEL → ${TIME_MS}ms" - echo "[OK] $LABEL → ${TIME_MS}ms" >> "$REPORT_FILE" + succes "$LIBELLE → ${TEMPS_MS}ms" + echo "[OK] $LIBELLE → ${TEMPS_MS}ms" >> "$RAPPORT_FICHIER" fi } # ============================================================================= -# MAIN +# PROGRAMME PRINCIPAL # ============================================================================= -# Initialise report -> "$REPORT_FILE" -cat >> "$REPORT_FILE" < "$RAPPORT_FICHIER" +cat >> "$RAPPORT_FICHIER" </dev/null || echo "$STYLE") - check_endpoint GET "$BASE_URL/titre/style/$ENCODED" "GET /titre/style/$STYLE" + ENCODE=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$STYLE'))" 2>/dev/null || echo "$STYLE") + verifier_endpoint GET "$BASE_URL/titre/style/$ENCODE" "GET /titre/style/$STYLE" done log "" info "── Artiste ───────────────────────────────────────────────" +# Test des pages artiste avec des noms en kebab-case ARTISTES=("fatal-bazooka" "daft-punk" "justice" "kraftwerk") for ARTISTE in "${ARTISTES[@]}"; do - check_endpoint GET "$BASE_URL/artiste/$ARTISTE" "GET /artiste/$ARTISTE" + verifier_endpoint GET "$BASE_URL/artiste/$ARTISTE" "GET /artiste/$ARTISTE" done log "" info "── Recherche (POST) ──────────────────────────────────────" +# Test de la recherche plein texte par mots-clés MOTS=("rock" "jazz" "pop" "metal") for MOT in "${MOTS[@]}"; do - check_endpoint POST "$BASE_URL/recherche" \ + verifier_endpoint POST "$BASE_URL/recherche" \ "POST /recherche (mot=$MOT)" \ "mot=$MOT" done log "" -# ── ADMINISTRATION SECTION ──────────────────────────────────────────────────── -info "── Administration – Dashboard ────────────────────────────" -check_endpoint GET "$BASE_URL/Administration/Dashboard" "GET /Administration/Dashboard" +# ── SECTION ADMINISTRATION ──────────────────────────────────────────────────── +info "── Administration – Tableau de bord ──────────────────────" +verifier_endpoint GET "$BASE_URL/Administration/Dashboard" "GET /Administration/Dashboard" log "" info "── Administration – Artiste ──────────────────────────────" -check_endpoint GET "$BASE_URL/Administration/Artiste" "GET /Administration/Artiste (Index)" -check_endpoint GET "$BASE_URL/Administration/Artiste/Create" "GET /Administration/Artiste/Create" -check_endpoint GET "$BASE_URL/Administration/Artiste/Edit/1" "GET /Administration/Artiste/Edit/1" -check_endpoint GET "$BASE_URL/Administration/Artiste/Delete/1" "GET /Administration/Artiste/Delete/1" +verifier_endpoint GET "$BASE_URL/Administration/Artiste" "GET /Administration/Artiste (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Create" "GET /Administration/Artiste/Create" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Edit/1" "GET /Administration/Artiste/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Artiste/Delete/1" "GET /Administration/Artiste/Delete/1" log "" info "── Administration – Commentaire ──────────────────────────" -check_endpoint GET "$BASE_URL/Administration/Commentaire" "GET /Administration/Commentaire (Index)" -check_endpoint GET "$BASE_URL/Administration/Commentaire/Delete/1" \ - "GET /Administration/Commentaire/Delete/1" +verifier_endpoint GET "$BASE_URL/Administration/Commentaire" "GET /Administration/Commentaire (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Commentaire/Delete/1" "GET /Administration/Commentaire/Delete/1" log "" info "── Administration – Style ────────────────────────────────" -check_endpoint GET "$BASE_URL/Administration/Style" "GET /Administration/Style (Index)" -check_endpoint GET "$BASE_URL/Administration/Style/Create" "GET /Administration/Style/Create" -check_endpoint GET "$BASE_URL/Administration/Style/Edit/1" "GET /Administration/Style/Edit/1" -check_endpoint GET "$BASE_URL/Administration/Style/Delete/1" "GET /Administration/Style/Delete/1" +verifier_endpoint GET "$BASE_URL/Administration/Style" "GET /Administration/Style (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Style/Create" "GET /Administration/Style/Create" +verifier_endpoint GET "$BASE_URL/Administration/Style/Edit/1" "GET /Administration/Style/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Style/Delete/1" "GET /Administration/Style/Delete/1" log "" info "── Administration – Titre ────────────────────────────────" -check_endpoint GET "$BASE_URL/Administration/Titre" "GET /Administration/Titre (Index)" -check_endpoint GET "$BASE_URL/Administration/Titre/Create" "GET /Administration/Titre/Create" -check_endpoint GET "$BASE_URL/Administration/Titre/Edit/1" "GET /Administration/Titre/Edit/1" -check_endpoint GET "$BASE_URL/Administration/Titre/Delete/1" "GET /Administration/Titre/Delete/1" +verifier_endpoint GET "$BASE_URL/Administration/Titre" "GET /Administration/Titre (liste)" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Create" "GET /Administration/Titre/Create" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Edit/1" "GET /Administration/Titre/Edit/1" +verifier_endpoint GET "$BASE_URL/Administration/Titre/Delete/1" "GET /Administration/Titre/Delete/1" -# ── SUMMARY ─────────────────────────────────────────────────────────────────── -PASSED=$((TOTAL - FAILED)) +# ── RÉCAPITULATIF ───────────────────────────────────────────────────────────── +REUSSIS=$((TOTAL - ECHECS)) log "" info "╔══════════════════════════════════════════════════════════╗" -info "║ Results ║" +info "║ Résultats ║" info "╚══════════════════════════════════════════════════════════╝" -log " Total : ${TOTAL}" -log " ${GREEN}Passed${RESET} : ${PASSED}" +log " Total : ${TOTAL}" +log " ${VERT}Réussis${RESET} : ${REUSSIS}" -if [ "$FAILED" -gt 0 ]; then - log " ${RED}Failed${RESET} : ${FAILED}" +if [ "$ECHECS" -gt 0 ]; then + log " ${ROUGE}Échecs${RESET} : ${ECHECS}" log "" - log "${RED}${BOLD}❌ FAILED ENDPOINTS:${RESET}" - grep -E "^\[(FAIL|SLOW)\]" "$REPORT_FILE" | while IFS= read -r line; do - log " ${RED}→${RESET} $line" + log "${ROUGE}${GRAS}❌ ENDPOINTS EN ÉCHEC :${RESET}" + # Affichage de tous les endpoints ayant échoué ou dépassé le seuil + grep -E "^\[(ÉCHEC|LENT)\]" "$RAPPORT_FICHIER" | while IFS= read -r ligne; do + log " ${ROUGE}→${RESET} $ligne" done log "" - log "${RED}PR should be rejected. Fix the endpoints above.${RESET}" + log "${ROUGE}La PR doit être rejetée. Corrigez les endpoints ci-dessus.${RESET}" else - log " ${GREEN}Failed${RESET} : 0" + log " ${VERT}Échecs${RESET} : 0" log "" - log "${GREEN}${BOLD}✅ All endpoints are within the ${MAX_MS}ms threshold.${RESET}" + log "${VERT}${GRAS}✅ Tous les endpoints respectent le seuil de ${MAX_MS}ms.${RESET}" fi log "" -log "Full report saved to: ${REPORT_FILE}" +log "Rapport complet enregistré dans : ${RAPPORT_FICHIER}" log "" -# Write summary to report -cat >> "$REPORT_FILE" <> "$RAPPORT_FICHIER" <