#!/bin/bash
# Test fonctionnel de bout en bout : socle, roles et permissions.
#
# Demande un MariaDB local. DETRUIT la base « conciergerie ».
# config.php est sauvegarde puis restaure en fin de course.
set -u
APP=$(cd "$(dirname "$0")" && pwd)
S=/run/mysqld/mysqld.sock
J=/tmp/cookies.txt
B=http://127.0.0.1:8080

# Identifiants du superadmin, tels qu'ils figureront dans config.php.
SA_MAIL=patron@conciergerie.fr
SA_PASSE=phrase-superadmin-solide

ok() { echo "  [OK]   $1"; }
ko() { echo "  [ECHEC] $1"; FAILED=1; }
FAILED=0

# Extrait le jeton CSRF d'une page passee sur l'entree standard.
csrf() { grep -o 'name="_csrf" value="[a-f0-9]*"' | head -1 | sed 's/.*value="//;s/"//'; }

sql() { mariadb --socket=$S conciergerie -N -e "$1"; }

# --- Sauvegarde de la configuration existante ---------------------------------
# Le script ecrit sa propre config.php. Sans cette precaution, un lancement sur
# un poste de developpement ecraserait les identifiants de production.
[ -f "$APP/config.php" ] && cp "$APP/config.php" /tmp/config.php.avant
restaurer() {
  if [ -f /tmp/config.php.avant ]; then
    mv /tmp/config.php.avant "$APP/config.php"
    echo "config.php restaure."
  fi
}
trap restaurer EXIT

# --- Serveur de base de donnees ---
mkdir -p /run/mysqld && chmod 777 /run/mysqld
mariadbd --user=root --datadir=/var/lib/mysql --socket=$S \
         --pid-file=/run/mysqld/mysqld.pid --skip-networking > /tmp/mdb.log 2>&1 &
MDB=$!
for i in $(seq 1 30); do mariadb --socket=$S -e "SELECT 1" >/dev/null 2>&1 && break; sleep 1; done
mariadb --socket=$S -e "DROP DATABASE IF EXISTS conciergerie; CREATE DATABASE conciergerie CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
echo "Base vide creee."

cat > $APP/config.php <<PHP
<?php
return [
    'env' => 'local',
    'url' => '$B',
    'db' => ['host'=>'localhost','port'=>3306,'database'=>'conciergerie',
             'username'=>'root','password'=>'','charset'=>'utf8mb4',
             'unix_socket'=>'$S'],
    'superadmin' => ['nom'=>'Patron','email'=>'$SA_MAIL','mot_de_passe'=>'$SA_PASSE'],
    'session' => ['name'=>'concierge_sid','secure'=>false,'duree'=>7200],
    'securite' => ['mot_de_passe_min'=>12,'jeton_validite_h'=>72,
                   'echecs_max'=>50,'fenetre_min'=>15,'verrou_max'=>3],
    'courriel' => ['expediteur'=>'no-reply@conciergerie.test','nom'=>'Conciergerie'],
];
PHP
# echecs_max a 50 et verrou_max a 3 : l'inverse de la production, volontairement.
# Le blocage temporaire arriverait sinon bien avant le verrou et empecherait de
# tester ce dernier. La section dediee remet les valeurs normales pour verifier le
# blocage temporaire lui-meme.

echo ""
echo "=== 0. Migrations ==="
(cd $APP && php tasks/migrer.php 2>&1 | sed 's/^/    /')
sql "SELECT COUNT(*) FROM migrations" | grep -q '^7$' \
  && ok "7 migrations enregistrees dans la table de suivi" || ko "table migrations incorrecte"
T=$(sql "SHOW TABLES" | tr '\n' ' ')
echo "$T" | grep -q utilisateurs && echo "$T" | grep -q jetons_acces \
  && echo "$T" | grep -q journal_audit && echo "$T" | grep -q tentatives_connexion \
  && ok "les 4 tables metier sont creees" || ko "tables manquantes : $T"
sql "SHOW COLUMNS FROM utilisateurs LIKE 'role'" | grep -q "proprietaire" \
  && ok "003 : l'enumeration porte admin et proprietaire" || ko "enumeration des roles non migree"
sql "SHOW COLUMNS FROM utilisateurs LIKE 'role'" | grep -qv "'client'" \
  && ok "003 : le role client a disparu" || ko "le role client subsiste"
sql "SHOW COLUMNS FROM journal_audit LIKE 'acteur_libelle'" | grep -q acteur_libelle \
  && ok "004 : journal_audit.acteur_libelle present" || ko "colonne acteur_libelle absente"
sql "SHOW COLUMNS FROM utilisateurs LIKE 'concierge_id'" | grep -q concierge_id \
  && ok "005 : utilisateurs.concierge_id present" || ko "colonne concierge_id absente"
sql "SHOW INDEX FROM utilisateurs WHERE Key_name='idx_utilisateurs_concierge'" | grep -q concierge_id \
  && ok "005 : index de rattachement present" || ko "index de rattachement absent"
sql "SHOW COLUMNS FROM utilisateurs LIKE 'verrouille_le'" | grep -q verrouille_le \
  && ok "006 : utilisateurs.verrouille_le present" || ko "colonne verrouille_le absente"
sql "SHOW COLUMNS FROM tentatives_connexion LIKE 'action'" | grep -q action \
  && ok "007 : tentatives_connexion.action present" || ko "colonne action absente"
OUT2=$(cd $APP && php tasks/migrer.php 2>&1)
echo "$OUT2" | grep -q "a jour" && ok "seconde execution idempotente" || ko "migrations rejouees"

# --- Serveur web ---
php -S 127.0.0.1:8080 -t $APP/public $APP/public/index.php > /tmp/web.log 2>&1 &
WEB=$!
sleep 2

echo ""
echo "=== 1. Le superadmin de config.php, sur une base sans aucun compte ==="
sql "SELECT COUNT(*) FROM utilisateurs" | grep -q '^0$' \
  && ok "aucun utilisateur en base au depart" || ko "la base n'est pas vierge"

rm -f $J
CSRF=$(curl -s -c $J "$B/connexion" | csrf)
LOC=$(curl -s -b $J -c $J -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=$SA_MAIL" \
  --data-urlencode "mot_de_passe=$SA_PASSE" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/utilisateurs" \
  && ok "connexion du superadmin sans aucun script d'amorcage" || ko "connexion refusee : $LOC"

sql "SELECT COUNT(*) FROM utilisateurs" | grep -q '^0$' \
  && ok "sa connexion n'a cree aucune ligne en base" || ko "une ligne a ete creee"

REGISTRE=$(curl -s -b $J -c $J "$B/utilisateurs")
echo "$REGISTRE" | grep -q "Defini dans config.php" \
  && ok "il se voit lui-meme, synthetise depuis config.php" || ko "absent de son propre registre"
echo "$REGISTRE" | grep -q "Hors interface" \
  && ok "sa ligne est signalee comme non actionnable" || ko "sa ligne propose des commandes"

CODE=$(curl -s -b $J -o /dev/null -w '%{http_code}' "$B/profil")
[ "$CODE" = "403" ] && ok "le superadmin n'a pas d'ecran de profil (403)" || ko "profil accessible ($CODE)"

echo ""
echo "=== 2. Mot de passe superadmin errone ==="
rm -f /tmp/j2
CSRF=$(curl -s -c /tmp/j2 "$B/connexion" | csrf)
curl -s -b /tmp/j2 -c /tmp/j2 -o /tmp/r_sa.html -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=$SA_MAIL" \
  --data-urlencode "mot_de_passe=pas-le-bon"
grep -q "incorrect" /tmp/r_sa.html && ok "mot de passe superadmin errone rejete" || ko "accepte a tort"

echo ""
echo "=== 3. Le superadmin cree un administrateur ==="
PAGE=$(curl -s -b $J -c $J "$B/utilisateurs/nouveau?role=admin")
echo "$PAGE" | grep -q "Administrateur" && ok "le formulaire propose le role administrateur" || ko "role admin absent du formulaire"
CSRF=$(echo "$PAGE" | csrf)
curl -s -b $J -c $J -o /dev/null -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=admin" \
  --data-urlencode "nom=Marie Durand" --data-urlencode "email=marie@conciergerie.fr"

LISTE=$(curl -s -b $J -c $J "$B/utilisateurs")
echo "$LISTE" | grep -q "Marie Durand" && ok "administrateur cree et liste" || ko "administrateur absent"
echo "$LISTE" | grep -q "En attente" && ok "etat « en attente d'activation » affiche" || ko "etat incorrect"
LIEN_ADMIN=$(echo "$LISTE" | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
[ -n "$LIEN_ADMIN" ] && ok "lien d'activation presente" || ko "aucun lien affiche"
sql "SELECT role FROM utilisateurs WHERE email='marie@conciergerie.fr'" | grep -q '^admin$' \
  && ok "le role enregistre est bien admin" || ko "role incorrect en base"
sql "SELECT cree_par IS NULL FROM utilisateurs WHERE email='marie@conciergerie.fr'" | grep -q '^1$' \
  && ok "cree_par est NULL : le superadmin n'a pas d'identifiant" || ko "cree_par renseigne a tort"
sql "SELECT acteur_libelle FROM journal_audit WHERE action='utilisateur.cree' LIMIT 1" | grep -q "config" \
  && ok "le journal identifie le superadmin par son libelle" || ko "acteur non identifie au journal"

echo ""
echo "=== 4. L'administrateur active son compte et se connecte ==="
rm -f /tmp/j_admin
CSRF=$(curl -s -c /tmp/j_admin "$LIEN_ADMIN" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$LIEN_ADMIN" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=phrase-admin-solide" \
  --data-urlencode "confirmation=phrase-admin-solide"
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=marie@conciergerie.fr" \
  --data-urlencode "mot_de_passe=phrase-admin-solide" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/utilisateurs" && ok "l'administrateur se connecte" || ko "connexion admin refusee : $LOC"

echo ""
echo "=== 5. Un administrateur ne cree pas d'administrateur ==="
PAGE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/nouveau")
echo "$PAGE" | grep -q "Administrateur</option>" \
  && ko "le formulaire lui propose le role administrateur" \
  || ok "le formulaire ne propose pas le role administrateur"
CSRF=$(echo "$PAGE" | csrf)
CODE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=admin" \
  --data-urlencode "nom=Usurpateur" --data-urlencode "email=usurpateur@x.fr")
[ "$CODE" = "403" ] && ok "role=admin force dans le POST : refuse (403)" || ko "elevation acceptee ($CODE)"
sql "SELECT COUNT(*) FROM utilisateurs WHERE email='usurpateur@x.fr'" | grep -q '^0$' \
  && ok "aucune ecriture malgre la tentative" || ko "compte cree malgre le refus"

echo ""
echo "=== 6. L'administrateur cree un concierge ==="
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/nouveau?role=concierge" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=concierge" \
  --data-urlencode "nom=Paul Martin" --data-urlencode "email=paul@conciergerie.fr" \
  --data-urlencode "telephone=06 12 34 56 78"
LISTE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs")
echo "$LISTE" | grep -q "Paul Martin" && ok "concierge cree par l'administrateur" || ko "concierge absent"
LIEN_CONC=$(echo "$LISTE" | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
sql "SELECT cree_par IS NOT NULL FROM utilisateurs WHERE email='paul@conciergerie.fr'" | grep -q '^1$' \
  && ok "cree_par renseigne : l'administrateur, lui, a un identifiant" || ko "cree_par vide"

rm -f /tmp/j_conc
CSRF=$(curl -s -c /tmp/j_conc "$LIEN_CONC" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$LIEN_CONC" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=phrase-concierge-solide" \
  --data-urlencode "confirmation=phrase-concierge-solide"
CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=paul@conciergerie.fr" \
  --data-urlencode "mot_de_passe=phrase-concierge-solide" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/utilisateurs" && ok "le concierge se connecte" || ko "connexion concierge refusee : $LOC"

echo ""
echo "=== 7. Un concierge ne cree ni concierge ni administrateur ==="
PAGE=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs/nouveau")
echo "$PAGE" | grep -q "Concierge</option>" \
  && ko "le formulaire lui propose le role concierge" \
  || ok "le formulaire ne propose que prestataire et proprietaire"
CSRF=$(echo "$PAGE" | csrf)
for R in admin concierge superadmin; do
  CODE=$(curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs" \
    --data-urlencode "_csrf=$CSRF" --data-urlencode "role=$R" \
    --data-urlencode "nom=Tentative $R" --data-urlencode "email=tentative-$R@x.fr")
  [ "$CODE" = "403" ] && ok "concierge -> role=$R : refuse (403)" || ko "concierge a cree un $R ($CODE)"
done
sql "SELECT COUNT(*) FROM utilisateurs WHERE email LIKE 'tentative-%'" | grep -q '^0$' \
  && ok "aucun de ces comptes n'existe" || ko "des comptes ont ete crees"

echo ""
echo "=== 8. Un concierge ne charge pas la fiche d'un administrateur ==="
REF_ADMIN=$(sql "SELECT reference FROM utilisateurs WHERE email='marie@conciergerie.fr'")
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' "$B/utilisateurs/$REF_ADMIN/modifier")
[ "$CODE" = "404" ] \
  && ok "reference hors perimetre : 404, l'existence n'est pas revelee" \
  || ko "code $CODE au lieu de 404"
CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs" | csrf)
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs/$REF_ADMIN/revoquer" \
  --data-urlencode "_csrf=$CSRF")
[ "$CODE" = "404" ] && ok "revocation hors perimetre : 404" || ko "code $CODE au lieu de 404"
sql "SELECT actif FROM utilisateurs WHERE reference='$REF_ADMIN'" | grep -q '^1$' \
  && ok "l'administrateur est toujours actif" || ko "l'administrateur a ete revoque"

echo ""
echo "=== 9. Le registre d'un concierge est reduit a son perimetre ==="
# A ce stade Paul n'a encore aucune fiche rattachee : son registre doit donc etre
# vide, et non pas peuple des comptes qu'il ne gere pas.
LISTE=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs")
echo "$LISTE" | grep -q "Marie Durand" \
  && ko "le concierge voit l'administrateur" \
  || ok "le concierge ne voit pas les administrateurs"
echo "$LISTE" | grep -q "Administrateurs</a>" \
  && ko "l'onglet de filtre administrateurs lui est propose" \
  || ok "ses onglets de role se limitent a prestataires et proprietaires"
echo "$LISTE" | grep -q "Hors de votre perimetre" \
  && ko "des lignes inactionnables figurent dans son registre" \
  || ok "aucune ligne inactionnable : registre et perimetre coincident"
echo "$LISTE" | grep -q "registre est vide" \
  && ok "sans rattachement, son registre est vide" \
  || ko "son registre contient des comptes non rattaches"

# Le superadmin est invisible aux autres roles. On cherche son adresse et non
# son nom : le bandeau affiche le nom de l'utilisateur connecte, pas le sien.
echo "$LISTE" | grep -q "$SA_MAIL" \
  && ko "le concierge voit l'adresse du superadmin" \
  || ok "le superadmin est invisible au concierge"

LISTE_ADMIN=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs")
echo "$LISTE_ADMIN" | grep -q "$SA_MAIL" \
  && ko "l'administrateur voit l'adresse du superadmin" \
  || ok "le superadmin est invisible a l'administrateur"
echo "$LISTE_ADMIN" | grep -q "Defini dans config.php" \
  && ko "la ligne superadmin apparait a l'administrateur" \
  || ok "aucune ligne superadmin dans le registre de l'administrateur"
echo "$LISTE_ADMIN" | grep -q "Hors de votre perimetre" \
  && ok "l'administrateur voit ses pairs sans pouvoir y toucher" \
  || ko "mention 'hors perimetre' absente cote administrateur"

echo ""
echo "=== 10. Prestataire : un compte, borne au planning ==="
# Les prestataires et proprietaires se connectent desormais. Ils recoivent donc un
# lien d'activation comme les autres, mais n'atteignent QUE le planning.
CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs/nouveau?role=prestataire" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=prestataire" \
  --data-urlencode "nom=Plomberie Dupont" --data-urlencode "email=plombier@exemple.fr"
LISTE=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs")
echo "$LISTE" | grep -q "Plomberie Dupont" && ok "prestataire cree" || ko "prestataire absent"
LIEN_PRES=$(echo "$LISTE" | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
[ -n "$LIEN_PRES" ] && ok "un lien d'activation lui est emis" || ko "aucun lien pour le prestataire"
sql "SELECT COUNT(*) FROM jetons_acces j JOIN utilisateurs u ON u.id=j.utilisateur_id WHERE u.email='plombier@exemple.fr'" \
  | grep -vq '^0$' && ok "jeton enregistre en base" || ko "aucun jeton"

# Il active son compte et se connecte : il doit atterrir sur le PLANNING.
rm -f /tmp/j_pres
CSRF=$(curl -s -c /tmp/j_pres "$LIEN_PRES" | csrf)
curl -s -b /tmp/j_pres -c /tmp/j_pres -o /dev/null -X POST "$LIEN_PRES" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=phrase-prestataire-ok" \
  --data-urlencode "confirmation=phrase-prestataire-ok"
CSRF=$(curl -s -b /tmp/j_pres -c /tmp/j_pres "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_pres -c /tmp/j_pres -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=plombier@exemple.fr" \
  --data-urlencode "mot_de_passe=phrase-prestataire-ok" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/planning" \
  && ok "le prestataire se connecte et arrive au planning" || ko "redirection inattendue : $LOC"

# Et rien d'autre ne lui est ouvert.
for U in /utilisateurs /biens /admin/types-piece /admin/migrations; do
  CODE=$(curl -s -b /tmp/j_pres -o /dev/null -w '%{http_code}' "$B$U")
  [ "$CODE" = "403" ] && ok "prestataire : $U refuse (403)" || ko "prestataire atteint $U ($CODE)"
done
CODE=$(curl -s -b /tmp/j_pres -o /dev/null -w '%{http_code}' "$B/planning")
[ "$CODE" = "200" ] && ok "prestataire : le planning lui est ouvert" || ko "planning refuse ($CODE)"

echo ""
echo "=== 11. Proprietaire : meme regime ==="
CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs/nouveau?role=proprietaire" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=proprietaire" \
  --data-urlencode "nom=Sophie Bernard" --data-urlencode "email=sophie@exemple.fr"
sql "SELECT role FROM utilisateurs WHERE email='sophie@exemple.fr'" | grep -q '^proprietaire$' \
  && ok "proprietaire cree par le concierge" || ko "proprietaire absent ou role incorrect"
REF_PROP=$(sql "SELECT reference FROM utilisateurs WHERE email='sophie@exemple.fr'")
LIEN_PROP=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs" \
  | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
rm -f /tmp/j_prop
CSRF=$(curl -s -c /tmp/j_prop "$LIEN_PROP" | csrf)
curl -s -b /tmp/j_prop -c /tmp/j_prop -o /dev/null -X POST "$LIEN_PROP" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=phrase-proprietaire-ok" \
  --data-urlencode "confirmation=phrase-proprietaire-ok"
CSRF=$(curl -s -b /tmp/j_prop -c /tmp/j_prop "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_prop -c /tmp/j_prop -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=sophie@exemple.fr" \
  --data-urlencode "mot_de_passe=phrase-proprietaire-ok" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/planning" \
  && ok "le proprietaire se connecte et arrive au planning" || ko "redirection inattendue : $LOC"
CODE=$(curl -s -b /tmp/j_prop -o /dev/null -w '%{http_code}' "$B/utilisateurs")
[ "$CODE" = "403" ] && ok "proprietaire : le registre lui est ferme" || ko "registre expose ($CODE)"

# Ils peuvent changer leur mot de passe : ils en ont un.
CODE=$(curl -s -b /tmp/j_prop -o /dev/null -w '%{http_code}' "$B/profil")
[ "$CODE" = "200" ] && ok "proprietaire : son profil lui est ouvert" || ko "profil refuse ($CODE)"

echo ""
echo "=== 12. Isolation entre concierges ==="
# Le coeur de la fonctionnalite. Deux concierges, chacun ses fiches, et aucune
# visibilite croisee — ni par la liste, ni par une URL forgee.

# Les fiches creees par Paul lui sont rattachees d'office.
sql "SELECT COUNT(*) FROM utilisateurs
      WHERE concierge_id = (SELECT id FROM utilisateurs WHERE email='paul@conciergerie.fr')" \
  | grep -q '^2$' \
  && ok "les 2 fiches creees par le concierge lui sont rattachees" || ko "rattachement automatique absent"

# L'administrateur cree un second concierge, Luc.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/nouveau?role=concierge" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/utilisateurs" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "role=concierge" \
  --data-urlencode "nom=Luc Bertin" --data-urlencode "email=luc@conciergerie.fr"
LIEN_LUC=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs" \
  | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
rm -f /tmp/j_luc
CSRF=$(curl -s -c /tmp/j_luc "$LIEN_LUC" | csrf)
curl -s -b /tmp/j_luc -c /tmp/j_luc -o /dev/null -X POST "$LIEN_LUC" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=phrase-de-luc-solide" \
  --data-urlencode "confirmation=phrase-de-luc-solide"
CSRF=$(curl -s -b /tmp/j_luc -c /tmp/j_luc "$B/connexion" | csrf)
curl -s -b /tmp/j_luc -c /tmp/j_luc -o /dev/null -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr" \
  --data-urlencode "mot_de_passe=phrase-de-luc-solide"

# Luc ne doit rien voir : aucune fiche ne lui est rattachee.
LISTE_LUC=$(curl -s -b /tmp/j_luc -c /tmp/j_luc "$B/utilisateurs")
echo "$LISTE_LUC" | grep -q "Plomberie Dupont" \
  && ko "Luc voit le prestataire de Paul dans sa liste" \
  || ok "Luc ne voit pas le prestataire de Paul"
echo "$LISTE_LUC" | grep -q "Sophie Bernard" \
  && ko "Luc voit le proprietaire de Paul" \
  || ok "Luc ne voit pas le proprietaire de Paul"
echo "$LISTE_LUC" | grep -q "Paul M." \
  && ko "Luc voit son confrere Paul" || ok "Luc ne voit pas son confrere"

# Et pas davantage par une URL forgee : 404, la ligne n'est meme pas lue.
REF_PRESTA=$(sql "SELECT reference FROM utilisateurs WHERE email='plombier@exemple.fr'")
CODE=$(curl -s -b /tmp/j_luc -o /dev/null -w '%{http_code}' "$B/utilisateurs/$REF_PRESTA/modifier")
[ "$CODE" = "404" ] \
  && ok "fiche d'un autre concierge par URL forgee : 404" || ko "code $CODE au lieu de 404"
CSRF=$(echo "$LISTE_LUC" | csrf)
CODE=$(curl -s -b /tmp/j_luc -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs/$REF_PRESTA/revoquer" \
  --data-urlencode "_csrf=$CSRF")
[ "$CODE" = "404" ] && ok "revocation croisee refusee : 404" || ko "code $CODE au lieu de 404"
sql "SELECT actif FROM utilisateurs WHERE reference='$REF_PRESTA'" | grep -q '^1$' \
  && ok "le prestataire de Paul est intact" || ko "il a ete revoque par Luc"

# Un concierge ne peut pas se reaffecter une fiche : le champ n'est pas affiche,
# et une valeur forcee dans le POST est ignoree.
PAGE_PRESTA=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs/$REF_PRESTA/modifier")
echo "$PAGE_PRESTA" | grep -q 'name="concierge"' \
  && ko "le concierge dispose du selecteur de rattachement" \
  || ok "aucun selecteur de rattachement pour un concierge"
REF_LUC=$(sql "SELECT reference FROM utilisateurs WHERE email='luc@conciergerie.fr'")
CSRF=$(echo "$PAGE_PRESTA" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$B/utilisateurs/$REF_PRESTA/modifier" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Plomberie Dupont" \
  --data-urlencode "email=plombier@exemple.fr" --data-urlencode "telephone=" \
  --data-urlencode "concierge=$REF_LUC"
sql "SELECT concierge_id = (SELECT id FROM utilisateurs WHERE email='paul@conciergerie.fr')
     FROM utilisateurs WHERE reference='$REF_PRESTA'" | grep -q '^1$' \
  && ok "rattachement force dans le POST par un concierge : ignore" \
  || ko "un concierge a reaffecte une fiche"

echo ""
echo "=== 13. L'administrateur consulte et change le rattachement ==="
# Le nom du concierge figure dans la liste, cote administrateur.
LISTE_ADMIN=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs")
echo "$LISTE_ADMIN" | grep -q "class=\"rattachement\"" \
  && ok "le rattachement est affiche dans la liste" || ko "rattachement absent de la liste"

# La fiche du concierge liste ce qui releve de lui.
REF_PAUL=$(sql "SELECT reference FROM utilisateurs WHERE email='paul@conciergerie.fr'")
FICHE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/$REF_PAUL/modifier")
echo "$FICHE" | grep -q "Prestataires et proprietaires rattaches" \
  && ok "la fiche concierge porte la liste de ses rattaches" || ko "bloc des rattaches absent"
echo "$FICHE" | grep -q "Plomberie Dupont" \
  && ok "le prestataire y figure" || ko "prestataire absent de la fiche concierge"
echo "$FICHE" | grep -q "Sophie Bernard" \
  && ok "le proprietaire y figure" || ko "proprietaire absent de la fiche concierge"

# La fiche du prestataire propose le selecteur, et le changement prend effet.
FICHE_P=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/$REF_PRESTA/modifier")
echo "$FICHE_P" | grep -q 'name="concierge"' \
  && ok "l'administrateur dispose du selecteur de rattachement" || ko "selecteur absent"
echo "$FICHE_P" | grep -q "Luc Bertin" \
  && ok "les concierges actifs y sont proposes" || ko "liste des concierges vide"
CSRF=$(echo "$FICHE_P" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/utilisateurs/$REF_PRESTA/modifier" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Plomberie Dupont" \
  --data-urlencode "email=plombier@exemple.fr" --data-urlencode "telephone=" \
  --data-urlencode "concierge=$REF_LUC"
sql "SELECT concierge_id = (SELECT id FROM utilisateurs WHERE email='luc@conciergerie.fr')
     FROM utilisateurs WHERE reference='$REF_PRESTA'" | grep -q '^1$' \
  && ok "l'administrateur a reaffecte la fiche a Luc" || ko "reaffectation sans effet"
sql "SELECT COUNT(*) FROM journal_audit WHERE action='utilisateur.modifie'
      AND details LIKE '%concierge_id%'" | grep -vq '^0$' \
  && ok "le changement de rattachement est consigne au journal" || ko "changement non journalise"

# Le perimetre suit immediatement : Luc voit, Paul ne voit plus.
curl -s -b /tmp/j_luc -c /tmp/j_luc "$B/utilisateurs" | grep -q "Plomberie Dupont" \
  && ok "Luc voit desormais la fiche" || ko "Luc ne voit pas la fiche reaffectee"
curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs" | grep -q "Plomberie Dupont" \
  && ko "Paul voit encore une fiche qui ne lui appartient plus" \
  || ok "Paul ne voit plus la fiche reaffectee"
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' "$B/utilisateurs/$REF_PRESTA/modifier")
[ "$CODE" = "404" ] && ok "Paul obtient 404 sur la fiche reaffectee" || ko "code $CODE au lieu de 404"

# Detacher est possible, et signale comme tel.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/$REF_PRESTA/modifier" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/utilisateurs/$REF_PRESTA/modifier" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Plomberie Dupont" \
  --data-urlencode "email=plombier@exemple.fr" --data-urlencode "telephone=" \
  --data-urlencode "concierge="
sql "SELECT concierge_id IS NULL FROM utilisateurs WHERE reference='$REF_PRESTA'" | grep -q '^1$' \
  && ok "une fiche peut etre detachee" || ko "detachement sans effet"
curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs" | grep -q "Non rattache" \
  && ok "une fiche detachee est signalee comme non suivie" || ko "detachement non signale"

# Une reference de concierge inventee est une erreur de saisie, pas une 500.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs/$REF_PRESTA/modifier" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /tmp/r_rat.html -X POST "$B/utilisateurs/$REF_PRESTA/modifier" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Plomberie Dupont" \
  --data-urlencode "email=plombier@exemple.fr" --data-urlencode "telephone=" \
  --data-urlencode "concierge=ZZZZZZZZZZZZZZZZZZZZZZZZZZ"
grep -q "introuvable ou revoque" /tmp/r_rat.html \
  && ok "reference de concierge invalide : message de saisie" || ko "erreur non geree"

echo ""
echo "=== 14. Le concierge edite son profil ==="
PAGE=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/profil")
echo "$PAGE" | grep -q "paul@conciergerie.fr" && ok "le profil affiche ses propres donnees" || ko "profil illisible"
CSRF=$(echo "$PAGE" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$B/profil" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Paul M." \
  --data-urlencode "email=paul@conciergerie.fr" --data-urlencode "telephone=07 00 00 00 00"
sql "SELECT nom FROM utilisateurs WHERE email='paul@conciergerie.fr'" | grep -q "Paul M." \
  && ok "nom modifie depuis le profil" || ko "modification non enregistree"

CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/profil" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /tmp/r_mdp.html -X POST "$B/profil" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Paul M." \
  --data-urlencode "email=paul@conciergerie.fr" --data-urlencode "telephone=" \
  --data-urlencode "mot_de_passe_actuel=pas-le-bon" \
  --data-urlencode "mot_de_passe=nouvelle-phrase-solide" \
  --data-urlencode "mot_de_passe_confirme=nouvelle-phrase-solide"
grep -q "actuel est incorrect" /tmp/r_mdp.html \
  && ok "changement refuse sans le mot de passe actuel" || ko "changement accepte sans preuve"

CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/profil" | csrf)
curl -s -b /tmp/j_conc -c /tmp/j_conc -o /dev/null -X POST "$B/profil" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Paul M." \
  --data-urlencode "email=paul@conciergerie.fr" --data-urlencode "telephone=" \
  --data-urlencode "mot_de_passe_actuel=phrase-concierge-solide" \
  --data-urlencode "mot_de_passe=nouvelle-phrase-solide" \
  --data-urlencode "mot_de_passe_confirme=nouvelle-phrase-solide"
rm -f /tmp/j_conc2
CSRF=$(curl -s -c /tmp/j_conc2 "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_conc2 -c /tmp/j_conc2 -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=paul@conciergerie.fr" \
  --data-urlencode "mot_de_passe=nouvelle-phrase-solide" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/utilisateurs" && ok "le nouveau mot de passe fonctionne" || ko "nouveau mot de passe inoperant"
sql "SELECT COUNT(*) FROM journal_audit WHERE action='profil.modifie'" | grep -vq '^0$' \
  && ok "modification de profil consignee au journal" || ko "profil non journalise"

echo ""
echo "=== 15. Controle d'acces anonyme ==="
LOC=$(curl -s -o /dev/null -D - "$B/utilisateurs" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" && ok "anonyme redirige vers la connexion" || ko "registre expose : $LOC"
LOC=$(curl -s -o /dev/null -D - "$B/profil" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" && ok "profil protege" || ko "profil expose : $LOC"

echo ""
echo "=== 16. CSRF ==="
CODE=$(curl -s -b $J -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs" \
  --data-urlencode "role=concierge" --data-urlencode "nom=Intrus" --data-urlencode "email=intrus@x.fr")
[ "$CODE" = "419" ] && ok "POST sans jeton CSRF bloque (419)" || ko "CSRF non verifie ($CODE)"
sql "SELECT COUNT(*) FROM utilisateurs WHERE email='intrus@x.fr'" | grep -q '^0$' \
  && ok "aucune ecriture en base malgre la tentative" || ko "ecriture passee"

echo ""
echo "=== 17. Revocation par le superadmin ==="
REF_CONC=$(sql "SELECT reference FROM utilisateurs WHERE email='paul@conciergerie.fr'")
CSRF=$(curl -s -b $J -c $J "$B/utilisateurs" | csrf)
curl -s -b $J -c $J -o /dev/null -X POST "$B/utilisateurs/$REF_CONC/revoquer" --data-urlencode "_csrf=$CSRF"
sql "SELECT actif, revoque_le IS NOT NULL, revoque_par IS NULL FROM utilisateurs WHERE reference='$REF_CONC'" \
  | grep -q "^0	1	1$" \
  && ok "revoque : actif=0, date consignee, revoque_par NULL (superadmin sans ligne)" \
  || ko "revocation incomplete ou revoque_par renseigne a tort"
sql "SELECT COUNT(*) FROM jetons_acces WHERE utilisateur_id=(SELECT id FROM utilisateurs WHERE reference='$REF_CONC') AND utilise_le IS NULL" \
  | grep -q '^0$' && ok "liens en circulation invalides" || ko "lien encore valide apres revocation"
APRES=$(curl -s -b $J -c $J "$B/utilisateurs")
echo "$APRES" | grep -q "Revoque" && ok "affiche comme revoque" || ko "etat non reflete"

# Revoquer un concierge ne detache pas ses fiches : elles resteraient invisibles
# a tous les concierges sans que rien ne le signale. L'avertissement est donc
# necessaire, et le rattachement doit survivre pour rester reaffectable.
echo "$APRES" | grep -q "restent rattachees" \
  && ok "l'orphelinage des fiches rattachees est signale" || ko "aucun avertissement"
sql "SELECT concierge_id IS NOT NULL FROM utilisateurs WHERE email='sophie@exemple.fr'" | grep -q '^1$' \
  && ok "le rattachement survit a la revocation du concierge" || ko "fiche detachee automatiquement"
curl -s -b $J -c $J "$B/utilisateurs" | grep -q "concierge revoque" \
  && ok "la fiche signale que son concierge est revoque" || ko "situation non signalee dans la liste"

echo ""
echo "=== 18. Revocation immediate d'une session ouverte ==="
LOC=$(curl -s -b /tmp/j_conc2 -o /dev/null -D - "$B/utilisateurs" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" \
  && ok "le concierge revoque perd la main a la requete suivante" \
  || ko "session encore active apres revocation : $LOC"

echo ""
echo "=== 19. Changer l'email du superadmin invalide sa session ==="
sed -i "s/'email'=>'$SA_MAIL'/'email'=>'autre@conciergerie.fr'/" $APP/config.php
LOC=$(curl -s -b $J -o /dev/null -D - "$B/utilisateurs" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" \
  && ok "l'identite du superadmin est relue dans config.php a chaque requete" \
  || ko "session superadmin survit a un changement de configuration : $LOC"
sed -i "s/'email'=>'autre@conciergerie.fr'/'email'=>'$SA_MAIL'/" $APP/config.php

echo ""
echo "=== 20. Superadmin mal configure : porte close ==="
sed -i "s/'mot_de_passe'=>'$SA_PASSE'/'mot_de_passe'=>'court'/" $APP/config.php
rm -f /tmp/j3
CSRF=$(curl -s -c /tmp/j3 "$B/connexion" | csrf)
curl -s -b /tmp/j3 -c /tmp/j3 -o /tmp/r_court.html -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=$SA_MAIL" \
  --data-urlencode "mot_de_passe=court"
grep -q "incorrect" /tmp/r_court.html \
  && ok "mot de passe trop court dans config.php : connexion refusee" || ko "porte factice ouverte"
sed -i "s/'mot_de_passe'=>'court'/'mot_de_passe'=>'$SA_PASSE'/" $APP/config.php

echo ""
echo "=== 21. Verrouillage apres echecs de mot de passe ==="
# verrou_max vaut 3 dans la config de test. Luc est actif et non verrouille.
REF_LUC=$(sql "SELECT reference FROM utilisateurs WHERE email='luc@conciergerie.fr'")
sql "DELETE FROM tentatives_connexion"
rm -f /tmp/j_v
for i in 1 2 3; do
  CSRF=$(curl -s -b /tmp/j_v -c /tmp/j_v "$B/connexion" | csrf)
  curl -s -b /tmp/j_v -c /tmp/j_v -o /dev/null -X POST "$B/connexion" \
    --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr" \
    --data-urlencode "mot_de_passe=faux$i"
done
sql "SELECT verrouille_le IS NOT NULL FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "compte verrouille apres 3 echecs cumules" || ko "verrouillage non declenche"
sql "SELECT actif FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "verrouille MAIS PAS revoque : actif reste a 1" || ko "la revocation a ete confondue avec le verrou"
sql "SELECT revoque_le IS NULL FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "revoque_le reste NULL" || ko "revoque_le renseigne a tort"
sql "SELECT COUNT(*) FROM journal_audit WHERE action='compte.verrouille'" | grep -vq '^0$' \
  && ok "verrouillage consigne au journal" || ko "verrouillage non journalise"

# Meme avec le BON mot de passe, l'entree est refusee.
CSRF=$(curl -s -b /tmp/j_v -c /tmp/j_v "$B/connexion" | csrf)
curl -s -b /tmp/j_v -c /tmp/j_v -o /tmp/r_v.html -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr" \
  --data-urlencode "mot_de_passe=phrase-de-luc-solide"
grep -q "incorrect" /tmp/r_v.html \
  && ok "bon mot de passe refuse tant que le compte est verrouille" || ko "connexion acceptee"

# Marteler une adresse inconnue ne cree rien.
for i in 1 2 3 4; do
  CSRF=$(curl -s -b /tmp/j_v -c /tmp/j_v "$B/connexion" | csrf)
  curl -s -b /tmp/j_v -c /tmp/j_v -o /dev/null -X POST "$B/connexion" \
    --data-urlencode "_csrf=$CSRF" --data-urlencode "email=inconnu@nulle-part.fr" \
    --data-urlencode "mot_de_passe=essai$i"
done
sql "SELECT COUNT(*) FROM utilisateurs WHERE email='inconnu@nulle-part.fr'" | grep -q '^0$' \
  && ok "marteler une adresse inconnue ne cree aucun compte" || ko "compte fantome cree"

echo ""
echo "=== 22. Deverrouillage ==="
# L'administrateur deverrouille le concierge.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs" | csrf)
LISTE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/utilisateurs")
echo "$LISTE" | grep -q "Verrouille" \
  && ok "l'etat verrouille est affiche au registre" || ko "etat verrouille invisible"
echo "$LISTE" | grep -q "Deverrouiller" \
  && ok "la commande de deverrouillage est proposee" || ko "commande absente"
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/utilisateurs/$REF_LUC/deverrouiller" \
  --data-urlencode "_csrf=$CSRF"
sql "SELECT verrouille_le IS NULL FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "l'administrateur a leve le verrou" || ko "verrou toujours en place"
sql "SELECT COUNT(*) FROM tentatives_connexion WHERE email='luc@conciergerie.fr' AND reussie=0 AND action='connexion'" \
  | grep -q '^0$' \
  && ok "le compteur d'echecs est remis a zero" || ko "compteur non purge : reverrouillage immediat"

# Et il peut de nouveau se connecter.
rm -f /tmp/j_luc2
CSRF=$(curl -s -c /tmp/j_luc2 "$B/connexion" | csrf)
LOC=$(curl -s -b /tmp/j_luc2 -c /tmp/j_luc2 -o /dev/null -D - -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr" \
  --data-urlencode "mot_de_passe=phrase-de-luc-solide" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/utilisateurs" \
  && ok "le concierge deverrouille se reconnecte" || ko "connexion toujours refusee : $LOC"

# Un concierge ne deverrouille pas un confrere : la reference est hors perimetre.
sql "UPDATE utilisateurs SET verrouille_le = NOW() WHERE email='luc@conciergerie.fr'"
CSRF=$(curl -s -b /tmp/j_conc -c /tmp/j_conc "$B/utilisateurs" | csrf)
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' -X POST "$B/utilisateurs/$REF_LUC/deverrouiller" \
  --data-urlencode "_csrf=$CSRF")
[ "$CODE" = "404" ] && ok "un concierge ne deverrouille pas un confrere (404)" || ko "code $CODE au lieu de 404"
sql "SELECT verrouille_le IS NOT NULL FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "le verrou de Luc est intact" || ko "un concierge a leve un verrou"

# Un verrou sur un ADMIN : son pair peut le lever, sans pouvoir le modifier.
sql "UPDATE utilisateurs SET verrouille_le = NOW() WHERE email='marie@conciergerie.fr'"
CSRF=$(curl -s -b $J -c $J "$B/utilisateurs" | csrf)
REF_MARIE=$(sql "SELECT reference FROM utilisateurs WHERE email='marie@conciergerie.fr'")
curl -s -b $J -c $J -o /dev/null -X POST "$B/utilisateurs/$REF_MARIE/deverrouiller" \
  --data-urlencode "_csrf=$CSRF"
sql "SELECT verrouille_le IS NULL FROM utilisateurs WHERE email='marie@conciergerie.fr'" | grep -q '^1$' \
  && ok "le superadmin deverrouille un administrateur" || ko "verrou admin non leve"

# La session ouverte d'un compte verrouille tombe a la requete suivante.
sql "UPDATE utilisateurs SET verrouille_le = NOW() WHERE email='luc@conciergerie.fr'"
LOC=$(curl -s -b /tmp/j_luc2 -o /dev/null -D - "$B/utilisateurs" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" \
  && ok "le verrouillage ferme la session en cours" || ko "session survit au verrouillage : $LOC"

echo ""
echo "=== 23. Mot de passe oublie ==="
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$B/mot-de-passe-oublie")
[ "$CODE" = "200" ] && ok "formulaire public accessible (200)" || ko "formulaire inaccessible ($CODE)"

sql "DELETE FROM tentatives_connexion"
rm -f /tmp/j_o
CSRF=$(curl -s -c /tmp/j_o "$B/mot-de-passe-oublie" | csrf)
curl -s -b /tmp/j_o -c /tmp/j_o -o /tmp/r_o1.html -X POST "$B/mot-de-passe-oublie" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr"
grep -q "Si un compte existe" /tmp/r_o1.html \
  && ok "reponse invariable pour une adresse connue" || ko "message inattendu"
grep -q "activation/" /tmp/r_o1.html \
  && ko "LE LIEN EST AFFICHE A L'ECRAN : contournement complet de l'authentification" \
  || ok "le lien n'apparait pas a l'ecran"
sql "SELECT COUNT(*) FROM jetons_acces j JOIN utilisateurs u ON u.id=j.utilisateur_id
      WHERE u.email='luc@conciergerie.fr' AND j.type='reinitialisation' AND j.utilise_le IS NULL" \
  | grep -vq '^0$' && ok "un jeton de reinitialisation a ete emis" || ko "aucun jeton emis"

# Meme reponse, au caractere pres, pour une adresse inconnue.
CSRF=$(curl -s -b /tmp/j_o -c /tmp/j_o "$B/mot-de-passe-oublie" | csrf)
curl -s -b /tmp/j_o -c /tmp/j_o -o /tmp/r_o2.html -X POST "$B/mot-de-passe-oublie" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=personne@nulle-part.fr"
grep -q "Si un compte existe" /tmp/r_o2.html \
  && ok "reponse invariable pour une adresse inconnue" || ko "l'existence du compte fuit"
sql "SELECT COUNT(*) FROM utilisateurs WHERE email='personne@nulle-part.fr'" | grep -q '^0$' \
  && ok "aucun compte cree par la demande" || ko "compte fantome"

# Une demande de reinitialisation ne doit PAS rapprocher du verrouillage.
sql "SELECT COUNT(*) FROM tentatives_connexion WHERE action='connexion'" | grep -q '^0$' \
  && ok "les demandes ne comptent pas comme des echecs de connexion" || ko "compteurs non etanches"
sql "SELECT COUNT(*) FROM tentatives_connexion WHERE action='reinitialisation'" | grep -vq '^0$' \
  && ok "les demandes sont comptees a part" || ko "demandes non tracees"

# Le jeton recu leve le verrou en meme temps qu'il change le mot de passe.
JETON=$(sql "SELECT j.id FROM jetons_acces j JOIN utilisateurs u ON u.id=j.utilisateur_id
             WHERE u.email='luc@conciergerie.fr' AND j.utilise_le IS NULL ORDER BY j.id DESC LIMIT 1")
[ -n "$JETON" ] && ok "jeton retrouve en base pour la suite du test" || ko "jeton introuvable"
# Le jeton en clair n'existe que dans le courriel : on en emet donc un nouveau par
# la voie administrateur, qui l'affiche, pour verifier la levee du verrou.
sql "UPDATE utilisateurs SET verrouille_le = NOW() WHERE email='luc@conciergerie.fr'"
CSRF=$(curl -s -b $J -c $J "$B/utilisateurs" | csrf)
curl -s -b $J -c $J -o /dev/null -X POST "$B/utilisateurs/$REF_LUC/reinitialiser" \
  --data-urlencode "_csrf=$CSRF"
LIEN_R=$(curl -s -b $J -c $J "$B/utilisateurs" | grep -o "$B/activation/[A-Za-z0-9_-]*" | head -1)
[ -n "$LIEN_R" ] && ok "lien de reinitialisation obtenu cote administrateur" || ko "aucun lien"
rm -f /tmp/j_r
CSRF=$(curl -s -c /tmp/j_r "$LIEN_R" | csrf)
curl -s -b /tmp/j_r -c /tmp/j_r -o /dev/null -X POST "$LIEN_R" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "mot_de_passe=troisieme-phrase-solide" \
  --data-urlencode "confirmation=troisieme-phrase-solide"
sql "SELECT verrouille_le IS NULL FROM utilisateurs WHERE email='luc@conciergerie.fr'" | grep -q '^1$' \
  && ok "definir un mot de passe leve le verrouillage" || ko "compte encore verrouille apres reinitialisation"
sql "SELECT COUNT(*) FROM tentatives_connexion WHERE email='luc@conciergerie.fr' AND action='connexion' AND reussie=0" \
  | grep -q '^0$' && ok "compteur d'echecs purge par la reinitialisation" || ko "compteur non purge"

# Le superadmin n'est pas reinitialisable : son mot de passe est dans config.php.
CSRF=$(curl -s -b /tmp/j_o -c /tmp/j_o "$B/mot-de-passe-oublie" | csrf)
curl -s -b /tmp/j_o -c /tmp/j_o -o /tmp/r_o3.html -X POST "$B/mot-de-passe-oublie" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=$SA_MAIL"
grep -q "Si un compte existe" /tmp/r_o3.html \
  && ok "demande sur l'adresse du superadmin : meme reponse neutre" || ko "traitement distinct visible"
sql "SELECT COUNT(*) FROM journal_audit WHERE action='mot_de_passe.demande_superadmin'" | grep -vq '^0$' \
  && ok "la tentative sur le superadmin est journalisee" || ko "tentative non tracee"

# Un prestataire n'a pas de mot de passe a reinitialiser.
CSRF=$(curl -s -b /tmp/j_o -c /tmp/j_o "$B/mot-de-passe-oublie" | csrf)
curl -s -b /tmp/j_o -c /tmp/j_o -o /dev/null -X POST "$B/mot-de-passe-oublie" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=plombier@exemple.fr"
sql "SELECT COUNT(*) FROM jetons_acces j JOIN utilisateurs u ON u.id=j.utilisateur_id
      WHERE u.email='plombier@exemple.fr'" | grep -q '^0$' \
  && ok "aucun jeton emis pour un prestataire" || ko "jeton emis pour un role sans acces"

echo ""
echo "=== 24. Ecran des migrations ==="
PAGE=$(curl -s -b $J -c $J "$B/admin/migrations")
echo "$PAGE" | grep -q "Appliquee" && ok "l'inventaire des migrations s'affiche" || ko "inventaire absent"
echo "$PAGE" | grep -q "ne remplace pas" \
  && ok "l'ecran rappelle que migrer.php reste necessaire" || ko "avertissement absent"
echo "$PAGE" | grep -q "007_tentatives_action" \
  && ok "les migrations sont listees nommement" || ko "noms de fichiers absents"
# Schema deja a jour : appliquer ne doit rien casser et le dire.
CSRF=$(echo "$PAGE" | csrf)
curl -s -b $J -c $J -o /tmp/r_mig.html -X POST "$B/admin/migrations" --data-urlencode "_csrf=$CSRF"
grep -q "a jour" /tmp/r_mig.html \
  && ok "appliquer sur un schema a jour : sans effet, et signale" || ko "compte rendu inattendu"
sql "SELECT COUNT(*) FROM journal_audit WHERE action='migrations.appliquees'" | grep -vq '^0$' \
  && ok "l'application des migrations est journalisee" || ko "action non journalisee"
# Reserve au superadmin.
CODE=$(curl -s -b /tmp/j_admin -o /dev/null -w '%{http_code}' "$B/admin/migrations")
[ "$CODE" = "403" ] && ok "un administrateur n'y a pas acces (403)" || ko "ecran expose ($CODE)"
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' "$B/admin/migrations")
[ "$CODE" = "403" ] && ok "un concierge n'y a pas acces (403)" || ko "ecran expose ($CODE)"
LOC=$(curl -s -o /dev/null -D - "$B/admin/migrations" | grep -i '^location:' | tr -d '\r')
echo "$LOC" | grep -q "/connexion" && ok "anonyme redirige" || ko "ecran expose aux anonymes"

echo ""
echo "=== 25. Limitation temporaire des tentatives ==="
# On remet les seuils de production pour ce test : echecs_max bas, verrou haut.
sed -i "s/'echecs_max'=>50/'echecs_max'=>5/;s/'verrou_max'=>3/'verrou_max'=>99/" $APP/config.php
sql "DELETE FROM tentatives_connexion"
rm -f /tmp/j4; curl -s -c /tmp/j4 "$B/connexion" > /dev/null
for i in 1 2 3 4 5 6; do
  CSRF=$(curl -s -b /tmp/j4 -c /tmp/j4 "$B/connexion" | csrf)
  curl -s -b /tmp/j4 -c /tmp/j4 -o /tmp/rl.html -X POST "$B/connexion" \
    --data-urlencode "_csrf=$CSRF" --data-urlencode "email=cible@conciergerie.fr" \
    --data-urlencode "mot_de_passe=essai$i" > /dev/null
done
grep -q "Trop de tentatives" /tmp/rl.html && ok "blocage temporaire apres 5 echecs" || ko "aucune limitation"
grep -q "Mot de passe oublie" /tmp/rl.html \
  && ok "le message oriente vers le mot de passe oublie" || ko "aucune sortie proposee"
# Le blocage temporaire est aussi limite sur le formulaire d'oubli.
sql "DELETE FROM tentatives_connexion"
rm -f /tmp/j5
for i in 1 2 3 4 5 6; do
  CSRF=$(curl -s -b /tmp/j5 -c /tmp/j5 "$B/mot-de-passe-oublie" | csrf)
  curl -s -b /tmp/j5 -c /tmp/j5 -o /tmp/ro.html -X POST "$B/mot-de-passe-oublie" \
    --data-urlencode "_csrf=$CSRF" --data-urlencode "email=cible2@conciergerie.fr"
done
grep -q "Trop de demandes" /tmp/ro.html \
  && ok "le formulaire d'oubli est limite en debit" || ko "inondation possible"

echo ""
echo "=== 26. Biens : creation, pieces, associations ==="
# Le concierge Paul cree un bien et l'associe a SA proprietaire Sophie.
REF_PAUL=$(sql "SELECT reference FROM utilisateurs WHERE email='paul@conciergerie.fr'")
sql "UPDATE utilisateurs SET actif = 1, verrouille_le = NULL WHERE email='paul@conciergerie.fr'"
rm -f /tmp/j_p2
CSRF=$(curl -s -c /tmp/j_p2 "$B/connexion" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=paul@conciergerie.fr" \
  --data-urlencode "mot_de_passe=nouvelle-phrase-solide"

PAGE=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/nouveau")
echo "$PAGE" | grep -q "Sophie Bernard" \
  && ok "le concierge se voit proposer SA proprietaire" || ko "proprietaire absent du formulaire"
echo "$PAGE" | grep -q "Mobil-home" \
  && ok "les trois natures de bien sont proposees" || ko "natures incompletes"
CSRF=$(echo "$PAGE" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -X POST "$B/biens" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Chalet des Cimes" \
  --data-urlencode "type=maison" --data-urlencode "adresse=1 rue des Cimes" \
  --data-urlencode "code_postal=65120" --data-urlencode "ville=Bareges" \
  --data-urlencode "surface_m2=74,5" --data-urlencode "capacite_max=6" \
  --data-urlencode "proprietaires[]=$REF_PROP"

sql "SELECT nom FROM biens WHERE nom='Chalet des Cimes'" | grep -q "Chalet" \
  && ok "bien cree" || ko "bien absent en base"
REF_BIEN=$(sql "SELECT reference FROM biens WHERE nom='Chalet des Cimes'")
sql "SELECT surface_m2 FROM biens WHERE reference='$REF_BIEN'" | grep -q '^74.50$' \
  && ok "la virgule decimale est acceptee (74,5 -> 74.50)" || ko "surface mal convertie"
sql "SELECT COUNT(*) FROM bien_proprietaire WHERE bien_id=(SELECT id FROM biens WHERE reference='$REF_BIEN')" \
  | grep -q '^1$' && ok "association au proprietaire enregistree" || ko "association absente"

# Ajout d'une piece de type chambre, avec couchages.
REF_CHAMBRE=$(sql "SELECT reference FROM types_piece WHERE libelle='Chambre'")
CSRF=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -X POST "$B/biens/$REF_BIEN/pieces" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "type_piece=$REF_CHAMBRE" \
  --data-urlencode "piece_nom=Chambre parentale" --data-urlencode "piece_surface=14" \
  --data-urlencode "douche=1" --data-urlencode "wc=1" --data-urlencode "lits_160=1"
sql "SELECT lits_160, douche, wc FROM pieces WHERE nom='Chambre parentale'" | grep -q "^1	1	1$" \
  && ok "piece chambre enregistree avec couchage et sanitaires" || ko "piece incorrecte"
FICHE=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier")
echo "$FICHE" | grep -q "grand lit" && ok "le couchage est decrit dans la fiche" || ko "couchage non affiche"
echo "$FICHE" | grep -q "2 couchage" \
  && ok "capacite theorique calculee depuis les pieces" || ko "capacite theorique absente"

# Une piece d'un autre bien ne se supprime pas depuis celui-ci.
CSRF=$(echo "$FICHE" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /tmp/r_pc.html -X POST "$B/biens/$REF_BIEN/pieces/supprimer" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "piece=ZZZZZZZZZZZZZZZZZZZZZZZZZZ"
sql "SELECT COUNT(*) FROM pieces WHERE nom='Chambre parentale'" | grep -q '^1$' \
  && ok "reference de piece inconnue : rien supprime" || ko "suppression aberrante"

echo ""
echo "=== 27. Biens : isolation par les proprietaires ==="
# Luc n'a aucun proprietaire rattache : il ne doit voir aucun bien.
sql "UPDATE utilisateurs SET verrouille_le = NULL WHERE email='luc@conciergerie.fr'"
rm -f /tmp/j_l2
CSRF=$(curl -s -c /tmp/j_l2 "$B/connexion" | csrf)
curl -s -b /tmp/j_l2 -c /tmp/j_l2 -o /dev/null -X POST "$B/connexion" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "email=luc@conciergerie.fr" \
  --data-urlencode "mot_de_passe=troisieme-phrase-solide"
curl -s -b /tmp/j_l2 -c /tmp/j_l2 "$B/biens" | grep -q "Chalet des Cimes" \
  && ko "Luc voit le bien d'un confrere" || ok "Luc ne voit pas le bien de Paul"
CODE=$(curl -s -b /tmp/j_l2 -o /dev/null -w '%{http_code}' "$B/biens/$REF_BIEN/modifier")
[ "$CODE" = "404" ] && ok "bien hors perimetre : 404" || ko "code $CODE au lieu de 404"

# L'administrateur voit tout, et voit de qui releve le bien.
LISTE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/biens")
echo "$LISTE" | grep -q "Chalet des Cimes" && ok "l'administrateur voit le bien" || ko "bien invisible cote admin"
echo "$LISTE" | grep -q "Sophie Bernard" \
  && ok "le proprietaire du bien est affiche" || ko "proprietaire non affiche"

# Un bien sans proprietaire est signale comme invisible des concierges.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/biens/nouveau" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/biens" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Bien orphelin" \
  --data-urlencode "type=appartement" --data-urlencode "adresse=2 rue Vide" \
  --data-urlencode "code_postal=65100" --data-urlencode "ville=Lourdes"
curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/biens" | grep -q "invisible des concierges" \
  && ok "bien sans proprietaire signale" || ko "orphelinage non signale"

echo ""
echo "=== 28. Planning ==="
# Le proprietaire ne voit QUE son bien, et le prestataire aucun tant qu'il n'est
# associe a rien.
curl -s -b /tmp/j_prop -c /tmp/j_prop "$B/planning" | grep -q "Chalet des Cimes" \
  && ok "le proprietaire voit son bien au planning" || ko "bien absent du planning proprietaire"
curl -s -b /tmp/j_prop -c /tmp/j_prop "$B/planning" | grep -q "Bien orphelin" \
  && ko "le proprietaire voit un bien qui n'est pas le sien" || ok "aucun bien etranger"
curl -s -b /tmp/j_pres -c /tmp/j_pres "$B/planning" | grep -q "Chalet des Cimes" \
  && ko "le prestataire voit un bien sans y etre associe" \
  || ok "prestataire non associe : aucun bien"

# On l'associe, il doit alors le voir.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/biens/$REF_BIEN/modifier" | csrf)
REF_PRES=$(sql "SELECT reference FROM utilisateurs WHERE email='plombier@exemple.fr'")
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/biens/$REF_BIEN/modifier" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "nom=Chalet des Cimes" \
  --data-urlencode "type=maison" --data-urlencode "adresse=1 rue des Cimes" \
  --data-urlencode "code_postal=65120" --data-urlencode "ville=Bareges" \
  --data-urlencode "proprietaires[]=$REF_PROP" --data-urlencode "prestataires[]=$REF_PRES"
curl -s -b /tmp/j_pres -c /tmp/j_pres "$B/planning" | grep -q "Chalet des Cimes" \
  && ok "le prestataire associe voit le bien" || ko "association sans effet sur le planning"

# Les trois echelles repondent, et la navigation aussi.
for VUE in semaine mois trimestre; do
  CODE=$(curl -s -b /tmp/j_admin -o /dev/null -w '%{http_code}' \
    "$B/planning?bien=$REF_BIEN&vue=$VUE&date=2026-07-15")
  [ "$CODE" = "200" ] && ok "echelle $VUE : 200" || ko "echelle $VUE : $CODE"
done
PAGE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/planning?bien=$REF_BIEN&vue=trimestre&date=2026-07-15")
[ "$(echo "$PAGE" | grep -c '<table>')" = "3" ] \
  && ok "le trimestre affiche trois grilles" || ko "nombre de grilles incorrect"
echo "$PAGE" | grep -q "Juillet 2026" && ok "les mois sont titres en francais" || ko "titre de mois absent"
# Une date fantaisiste ne doit pas casser la page.
CODE=$(curl -s -b /tmp/j_admin -o /dev/null -w '%{http_code}' "$B/planning?bien=$REF_BIEN&date=pas-une-date&vue=zzz")
[ "$CODE" = "200" ] && ok "parametres d'URL fantaisistes : page rendue quand meme" || ko "code $CODE"
# Le filtre par client n'est propose qu'aux gestionnaires.
curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/planning" | grep -q 'name="client"' \
  && ok "filtre par client propose a l'administrateur" || ko "filtre client absent"
curl -s -b /tmp/j_prop -c /tmp/j_prop "$B/planning" | grep -q 'name="client"' \
  && ko "filtre par client propose a un proprietaire" || ok "pas de filtre client pour un proprietaire"
# Aucune route POST sur le planning : c'est un ecran de lecture.
CODE=$(curl -s -b /tmp/j_admin -o /dev/null -w '%{http_code}' -X POST "$B/planning" --data-urlencode "x=1")
[ "$CODE" = "405" ] || [ "$CODE" = "419" ] && ok "le planning refuse les POST ($CODE)" || ko "POST accepte ($CODE)"

echo ""
echo "=== 29. Referentiel des types de piece ==="
CODE=$(curl -s -b /tmp/j_conc -o /dev/null -w '%{http_code}' "$B/admin/types-piece")
[ "$CODE" = "403" ] && ok "un concierge ne configure pas le referentiel (403)" || ko "referentiel expose ($CODE)"
PAGE=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/admin/types-piece")
echo "$PAGE" | grep -q "Chambre" && ok "le referentiel s'affiche" || ko "referentiel vide"
CSRF=$(echo "$PAGE" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/admin/types-piece" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "libelle=Cave a ski" \
  --data-urlencode "ordre=160"
sql "SELECT libelle FROM types_piece WHERE libelle='Cave a ski'" | grep -q "Cave" \
  && ok "type ajoute par l'administrateur" || ko "type non cree"
# Doublon refuse.
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/admin/types-piece" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/admin/types-piece" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "libelle=Cave a ski"
sql "SELECT COUNT(*) FROM types_piece WHERE libelle='Cave a ski'" | grep -q '^1$' \
  && ok "libelle en doublon refuse" || ko "doublon cree"
# Desactivation : le type disparait des choix sans casser les pieces.
REF_TYPE=$(sql "SELECT reference FROM types_piece WHERE libelle='Cave a ski'")
CSRF=$(curl -s -b /tmp/j_admin -c /tmp/j_admin "$B/admin/types-piece" | csrf)
curl -s -b /tmp/j_admin -c /tmp/j_admin -o /dev/null -X POST "$B/admin/types-piece/$REF_TYPE/basculer" \
  --data-urlencode "_csrf=$CSRF"
sql "SELECT actif FROM types_piece WHERE reference='$REF_TYPE'" | grep -q '^0$' \
  && ok "type retire des choix" || ko "bascule sans effet"
curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | grep -q "Cave a ski" \
  && ko "un type inactif est encore propose a la saisie" || ok "type inactif absent des choix"
sql "SELECT COUNT(*) FROM pieces WHERE nom='Chambre parentale'" | grep -q '^1$' \
  && ok "les pieces existantes ne sont pas touchees" || ko "pieces perdues"

echo ""
echo "=== 30. Import iCal ==="
CSRF=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -X POST "$B/biens/$REF_BIEN/sources" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "source_libelle=Airbnb" \
  --data-urlencode "source_url=https://exemple.invalid/calendrier.ics"
sql "SELECT libelle FROM sources_ical WHERE bien_id=(SELECT id FROM biens WHERE reference='$REF_BIEN')" \
  | grep -q "Airbnb" && ok "source iCal enregistree" || ko "source absente"
# URL non http refusee.
CSRF=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | csrf)
curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -X POST "$B/biens/$REF_BIEN/sources" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "source_libelle=Mauvaise" \
  --data-urlencode "source_url=file:///etc/passwd"
sql "SELECT COUNT(*) FROM sources_ical WHERE libelle='Mauvaise'" | grep -q '^0$' \
  && ok "URL non http refusee" || ko "URL dangereuse acceptee"
# La synchronisation d'une URL injoignable doit echouer PROPREMENT et le consigner.
CSRF=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | csrf)
CODE=$(curl -s -b /tmp/j_p2 -c /tmp/j_p2 -o /dev/null -w '%{http_code}' \
  -X POST "$B/biens/$REF_BIEN/synchroniser" --data-urlencode "_csrf=$CSRF")
{ [ "$CODE" = "302" ] || [ "$CODE" = "200" ]; } \
  && ok "URL injoignable : pas de 500, la page repond" || ko "code inattendu $CODE"
sql "SELECT dernier_resultat IS NOT NULL FROM sources_ical WHERE libelle='Airbnb'" | grep -q '^1$' \
  && ok "l'echec est consigne sur la source" || ko "echec non consigne"
curl -s -b /tmp/j_p2 -c /tmp/j_p2 "$B/biens/$REF_BIEN/modifier" | grep -q "En echec" \
  && ok "la fiche signale la source en echec" || ko "echec non signale dans la fiche"
# La tache planifiee tourne sans exploser, meme avec une source morte.
(cd $APP && php tasks/synchroniser_ical.php 2>&1 | sed 's/^/    /')
[ -f $APP/storage/logs/ical.log ] && ok "la tache iCal journalise" || ko "aucun journal iCal"

echo ""
echo "=== 31. Analyseur iCal et calendrier (calcul pur) ==="
(cd $APP && php tests_ical.php 2>&1 | sed 's/^/    /')
(cd $APP && php tests_calendrier.php 2>&1 | sed 's/^/    /')

echo ""
echo "=== 32. Reference invalide et journal d'audit ==="
CODE=$(curl -s -b $J -o /dev/null -w '%{http_code}' "$B/utilisateurs/PAS_UN_ULID/modifier")
{ [ "$CODE" = "404" ] || [ "$CODE" = "302" ]; } && ok "reference malformee : $CODE, pas d'erreur SQL" || ko "code inattendu $CODE"
N=$(sql "SELECT COUNT(*) FROM journal_audit")
[ "$N" -ge 8 ] && ok "journal d'audit alimente ($N entrees)" || ko "journal trop pauvre ($N)"
sql "SELECT COUNT(*) FROM journal_audit WHERE acteur_id IS NULL AND acteur_libelle IS NOT NULL" \
  | grep -vq '^0$' && ok "actions du superadmin tracees sans acteur_id" || ko "aucune action superadmin tracee"
echo ""
echo "Actions consignees :"
mariadb --socket=$S conciergerie -e \
  "SELECT action, acteur_libelle, cible_type FROM journal_audit ORDER BY id" 2>/dev/null | sed 's/^/    /'

echo ""
echo "=== 33. Porte web de migration ==="
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$B/migrer.php")
[ "$CODE" = "404" ] && ok "fermee par defaut, sans cle configuree (404)" || ko "porte ouverte ($CODE)"
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$B/migrer.php?cle=devinette")
[ "$CODE" = "404" ] && ok "mauvaise cle : 404 indifferencie" || ko "fuite d'information ($CODE)"
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$B/diagnostic.php")
[ "$CODE" = "404" ] && ok "diagnostic ferme tant que la cle n'est pas remplacee" || ko "diagnostic ouvert ($CODE)"

echo ""
echo "=== 34. Matrice de permissions, tarifs et requetes (calcul pur) ==="
# Ces trois suites tournent sans base. Les relancer ici garantit qu'un parcours HTTP
# vert ne masque pas une regle de permission cassee : la table de routage est
# confrontee aux predicats, et les requetes du code sont relues.
for suite in tests_permissions tests_prestations tests_sql; do
  (cd $APP && php $suite.php 2>&1 | sed 's/^/    /')
  [ "${PIPESTATUS[0]:-0}" = "0" ] || ko "$suite.php en echec"
done

echo ""
echo "=== 35. Cron de maintenance ==="
(cd $APP && php tasks/cron.php 2>&1 | sed 's/^/    /')
[ -f $APP/storage/logs/cron.log ] && ok "cron execute et journalise" || ko "cron sans trace"

kill $WEB $MDB 2>/dev/null
echo ""
if [ "$FAILED" = "0" ]; then echo "===> TOUS LES TESTS PASSENT"; else echo "===> DES TESTS ONT ECHOUE"; fi
