Files
donut-gestprojet/tests/smoke.sh
T
Arnaud 3a32e93ab1 feat: initial API skeleton — routes, controllers, migrations, deploy docs
Implements the JSON API for Le Donut Infolab per INSTRUCTIONS.md.

Schema (migrations/001-002):
  - 8 tables: categories, members, projects, project_members, roadmap_steps,
    discussions, resources, activity_log
  - 6 categories seeded with colors / dimensions (social, ecologique, transversal)
  - FK contraintes + soft delete sur projects, CHECK sur progress_percent

Bare PHP framework (src/):
  - Front controller public/index.php + router :params + auth flag par route
  - PSR-4 autoload sans Composer (bootstrap.php)
  - Database PDO singleton + helpers fetchOne/All/insert/update/delete
  - Validator fluent -> 422 {error: validation_failed, fields: {...}}
  - Bearer token admin (hash_equals) sur POST/PATCH/DELETE
  - Slugger avec unicité (suffixe -2, -3...) via intl Transliterator
  - ActivityLogger best-effort sur activity_log
  - Erreurs uniformes : 400/401/404/405/409/422/500 + CORS configurable

10 contrôleurs (Health, Categories, Members, Projects, ProjectMembers,
Roadmap, Discussions, Resources, Activity, Stats). Transitions de statut
loggées (created_project, completed_step, joined, etc.).

Cible prod :
  - Dockerfile (php:8.3-apache + pdo_mysql + intl + opcache)
  - docker-compose.yml sur réseau donut-net, bind 127.0.0.1:8003
  - caddy.conf pour infolab.ledonut-marseille.org
  - migrations/apply.sh idempotent (table schema_migrations)
  - DEPLOY.md avec commandes SSH exactes

Tests :
  - tests/smoke.sh exerce 10 endpoints clés, body envoyé via stdin
    (--data-binary @-) pour gérer l UTF-8 sous Git Bash Windows.

Vérifié localement sous Laragon Apache + MySQL 8.4 : 10/10 OK + cas
négatifs (401, 404, 405, 409, 422) conformes au contrat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:55:28 +02:00

142 lines
4.9 KiB
Bash

#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# tests/smoke.sh
#
# Appelle les endpoints clés de l'API et vérifie les codes HTTP attendus.
# Sortie non-zéro si au moins un appel échoue.
#
# Usage :
# ADMIN_TOKEN=xxxxx [INFOLAB_URL=http://localhost:8003] ./tests/smoke.sh
#
# Idempotent : un cleanup best-effort en début de script supprime les ressources
# de test (projet "nos-ecoles" + membre "collectif-des-ecoles-de-marseille").
# -----------------------------------------------------------------------------
set -uo pipefail
URL="${INFOLAB_URL:-http://localhost:8003}"
TOKEN="${ADMIN_TOKEN:-}"
if [[ -z "$TOKEN" ]]; then
echo "ERR: variable d'env ADMIN_TOKEN requise" >&2
exit 2
fi
PASS=0
FAIL=0
BODY="$(mktemp -t smoke.XXXXXX 2>/dev/null || mktemp)"
trap 'rm -f "$BODY"' EXIT
# Appelle un endpoint et vérifie le code HTTP. Logue un extrait du body.
# Args : label expected_code method path [body_json]
call() {
local label="$1" expected="$2" method="$3" path="$4" body="${5:-}"
local -a headers=(-H 'Accept: application/json')
if [[ -n "$body" ]]; then
headers+=(-H 'Content-Type: application/json')
fi
if [[ "$method" != "GET" ]]; then
headers+=(-H "Authorization: Bearer $TOKEN")
fi
local code
if [[ -n "$body" ]]; then
# Important : on pipe le body via stdin avec --data-binary @-, pas via argv.
# Sur Windows/Git Bash, passer un body UTF-8 en argument à curl convertit
# les bytes multi-octets en cp1252 et casse le JSON. stdin = binary safe.
code=$(printf '%s' "$body" \
| curl -sS -o "$BODY" -w '%{http_code}' -X "$method" "${headers[@]}" --data-binary @- "$URL$path")
else
code=$(curl -sS -o "$BODY" -w '%{http_code}' -X "$method" "${headers[@]}" "$URL$path")
fi
local snip
snip=$(head -c 220 "$BODY" 2>/dev/null || echo '')
if [[ "$code" == "$expected" ]]; then
printf ' OK [%s] %-6s %s — %s\n' "$code" "$method" "$path" "$label"
printf ' > %s\n' "$snip"
PASS=$((PASS+1))
else
printf ' FAIL [%s, attendu %s] %-6s %s — %s\n' "$code" "$expected" "$method" "$path" "$label"
printf ' > %s\n' "$snip"
FAIL=$((FAIL+1))
fi
}
# Extrait le 1er "id":N du body courant.
extract_id() {
sed -n 's/.*"id":[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$BODY" | head -1
}
# Compte les occurrences de "id":N dans le body (approximation suffisante pour les listes).
count_ids() {
grep -oE '"id":[[:space:]]*[0-9]+' "$BODY" | wc -l | tr -d ' '
}
echo "== Smoke test infolab @ $URL =="
# Cleanup best-effort : autorise les ré-exécutions.
curl -sS -o /dev/null -X DELETE -H "Authorization: Bearer $TOKEN" "$URL/api/projects/nos-ecoles" >/dev/null 2>&1 || true
curl -sS -o /dev/null -X DELETE -H "Authorization: Bearer $TOKEN" "$URL/api/members/collectif-des-ecoles-de-marseille" >/dev/null 2>&1 || true
# 1) Sanity
call "Health" 200 GET /api/health
# 2) Catégories seedées
call "Categories list" 200 GET /api/categories
count=$(count_ids)
if [[ "$count" == "6" ]]; then
echo " OK categories.length = 6"
PASS=$((PASS+1))
else
echo " FAIL categories.length = $count (attendu 6)"
FAIL=$((FAIL+1))
fi
# Récupère l'id de "education-ecoles" pour ne pas dépendre de l'auto-increment.
curl -sS -o "$BODY" "$URL/api/categories/education-ecoles" >/dev/null
CAT_ID=$(extract_id)
CAT_ID="${CAT_ID:-6}"
# 3) Création du membre
MEMBER_BODY=$(cat <<'JSON'
{"display_name":"Collectif des écoles de Marseille","kind":"collective","bio":"Réseau citoyen pour les écoles publiques marseillaises."}
JSON
)
call "Create member" 201 POST /api/members "$MEMBER_BODY"
# 4) Création du projet (ré-utilise CAT_ID dynamique)
PROJECT_BODY=$(cat <<JSON
{"title":"Nos Écoles","category_id":$CAT_ID,"status":"active","short_description":"Plateforme citoyenne pour partager l'état des écoles publiques.","context_description":"Issu de la mobilisation citoyenne de 2024.","objectives":"Cartographier, partager, alerter.","progress_percent":25}
JSON
)
call "Create project" 201 POST /api/projects "$PROJECT_BODY"
# 5) Rattachement du membre au projet
call "Add team member" 201 POST /api/projects/nos-ecoles/members \
'{"member_slug":"collectif-des-ecoles-de-marseille","role":"admin"}'
# 6) Étape de roadmap (apostrophe via heredoc pour éviter les soucis de quoting)
ROADMAP_BODY=$(cat <<'JSON'
{"title":"Création de l'outil 15min","description":"Premier MVP en 15 minutes.","is_done":false}
JSON
)
call "Add roadmap step" 201 POST /api/projects/nos-ecoles/roadmap "$ROADMAP_BODY"
# 7) Vérifie le détail complet du projet
call "Project detail" 200 GET /api/projects/nos-ecoles
# 8) KPIs et flux d'activité
call "Stats" 200 GET /api/stats
call "Activity" 200 GET /api/activity
echo
echo "==== Résumé ===="
echo " OK : $PASS"
echo " FAIL : $FAIL"
if [[ "$FAIL" -gt 0 ]]; then
exit 1
fi
exit 0