#!/usr/bin/env bash # Pruebas de cuentas.py y de la marca «contraseña inicial» (23-oct). Todo en carpetas temporales: no toca nada real. # Uso: bash tests-cuentas.sh [puerto] set -u REPO=${1:?ruta a hbox-docs}; PORT=${2:-8094} B=$(cd "$REPO/backend" && pwd) T=$(mktemp -d) export ACTAS_DIR=$T/actas HTPASSWD_FILE=$T/htpasswd HTPASSWD_LOCK=$T/.lock ACTAS_ADMINS=leo PORT mkdir -p "$ACTAS_DIR" "$T/equipo" ok=0; fail=0 check() { if [[ "$3" == *"$2"* ]]; then ok=$((ok+1)); echo " ✓ $1"; else fail=$((fail+1)); echo " ✕ $1 — esperaba «$2», obtuve: ${3:0:240}"; fi; } py() { python3 -c "import sys; sys.path.insert(0, '$B'); import app as A, json, os; $1"; } # punto de partida: 7 cuentas con contraseñas conocidas; luz sin miembro; adriana ya tiene correo py " open('$HTPASSWD_FILE', 'w').write(''.join(u + ':' + A.apr1('vieja-' + u, 'salt' + u[:4]) + '\n' for u in ('leo','adriana','alan','jesus','daniel','claude','luz'))) for u, n, a, c in (('leo','Leopoldo Rodríguez','direccion',''), ('adriana','Adriana','operaciones','adriana@otro.mx'), ('alan','Alán Basurto','campo',''), ('jesus','Jesús','pruebas',''), ('daniel','Daniel Martínez','campo',''), ('claude','Claude','asistente','')): json.dump({'id': u, 'nombre': n, 'area': a, 'rol': '', 'correo': c, 'telefono': '', 'activo': True}, open('$T/equipo/' + u + '.json', 'w'), ensure_ascii=False) " ANTES=$(sha256sum "$HTPASSWD_FILE" | cut -c1-16) LEO_ANTES=$(grep '^leo:' "$HTPASSWD_FILE") ALTA=$B/cuentas/2026-10-23_alta-sitios.json verifica() { py " l = dict(x.split(':', 1) for x in open('$HTPASSWD_FILE').read().split()) r = [] for u in '$1'.split(): h = l.get(u, ''); p = h.split('\$') r.append(u + ('=ok' if h and A.apr1('$2', p[2]) == h else '=NO')) print(' '.join(r))"; } echo "## plan y «no»" OUT=$(echo no | python3 "$B/cuentas.py" todo "$ALTA" 2>&1) check "muestra las 5 altas" "Altas nuevas (5)" "$OUT" check "restablece a 4 (leo y claude excepto; luz sin miembro)" "Contraseña restablecida (4): adriana, alan, daniel, jesus" "$OUT" check "avisa de luz" "sin miembro activo" "$OUT" check "jesus pasa a Jesús Romero" "nombre → Jesús Romero" "$OUT" check "con «no» no escribe" "No se cambió nada" "$OUT" check "el archivo sigue igual" "$ANTES" "$(sha256sum "$HTPASSWD_FILE" | cut -c1-16)" echo "## todo, de verdad" OUT=$(printf 'Helgen2026!\n' | python3 "$B/cuentas.py" todo "$ALTA" --si --password-stdin 2>&1) check "termina" "Listo." "$OUT" check "creados" "creados: jesush, miguel, joel, antonio, danielg" "$OUT" check "restablecidos y nuevos entran con la nueva" "adriana=ok alan=ok jesus=ok daniel=ok jesush=ok miguel=ok joel=ok antonio=ok danielg=ok" "$(verifica 'adriana alan jesus daniel jesush miguel joel antonio danielg' 'Helgen2026!')" check "leo no cambia" "$LEO_ANTES" "$(grep '^leo:' "$HTPASSWD_FILE")" check "claude y luz siguen con la vieja" "claude=ok luz=ok" "$(verifica 'claude' 'vieja-claude') $(verifica 'luz' 'vieja-luz')" check "compatible con htpasswd/nginx (openssl)" "ok" "$(H=$(grep '^miguel:' "$HTPASSWD_FILE" | cut -d: -f2); S=$(echo "$H" | cut -d'$' -f3); [ "$(openssl passwd -apr1 -salt "$S" 'Helgen2026!')" = "$H" ] && echo ok || echo NO)" check "sin líneas repetidas" "12" "$(cut -d: -f1 "$HTPASSWD_FILE" | sort -u | wc -l | tr -d ' ')" check "respaldo con modo 600" "600" "$(stat -c %a "$T"/equipo/_respaldos/htpasswd-* | head -1)" M=$(py "print(json.dumps({u: json.load(open('$T/equipo/' + u + '.json')) for u in ('jesush','miguel','jesus','adriana','leo','alan')}, ensure_ascii=False))") check "jesush: TIMSA, campo, correo" '"sitio": "timsa"' "$(echo "$M" | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["jesush"], ensure_ascii=False))')" check "nuevos con contraseña inicial" "True True" "$(echo "$M" | python3 -c 'import sys,json; m=json.load(sys.stdin); print(m["jesush"]["contrasena_inicial"], m["miguel"]["contrasena_inicial"])')" check "jesus: nombre y correo" "Jesús Romero jesusr@helgen.tech True" "$(echo "$M" | python3 -c 'import sys,json; m=json.load(sys.stdin)["jesus"]; print(m["nombre"], m["correo"], m["contrasena_inicial"])')" check "adriana conserva su correo" "adriana@otro.mx" "$(echo "$M" | python3 -c 'import sys,json; print(json.load(sys.stdin)["adriana"]["correo"])')" check "leo: correos, sin marca" "leo@helgen.tech ceo@helgen.tech None" "$(echo "$M" | python3 -c 'import sys,json; m=json.load(sys.stdin)["leo"]; print(m["correo"], m["correo_alt"], m.get("contrasena_inicial"))')" check "alan: sitio ICAVE" "icave" "$(echo "$M" | python3 -c 'import sys,json; print(json.load(sys.stdin)["alan"]["sitio"])')" check "bitácora de accesos" "contraseña restablecida,adriana" "$(cat "$T/equipo/_accesos.csv")" check "lista sin contraseñas" "inicial" "$(python3 "$B/cuentas.py" lista 2>&1)" check "lista no muestra hashes" "0" "$(python3 "$B/cuentas.py" lista 2>&1 | grep -c 'apr1')" echo "## otra vez (no duplica) y errores" OUT=$(printf 'Helgen2026!\n' | python3 "$B/cuentas.py" todo "$ALTA" --si --password-stdin 2>&1) check "segunda corrida: ya no hay altas" "Contraseña restablecida (9)" "$OUT" check "sigue sin repetidos (12 cuentas, 0 dobles)" "12/0" "$(cut -d: -f1 "$HTPASSWD_FILE" | wc -l | tr -d ' ')/$(cut -d: -f1 "$HTPASSWD_FILE" | sort | uniq -d | wc -l | tr -d ' ')" check "contraseña corta → no cambia nada" "al menos 8" "$(printf 'corta\n' | python3 "$B/cuentas.py" restablecer --solo alan --si --password-stdin 2>&1)" check "--solo" "Contraseña restablecida (1): alan" "$(echo no | python3 "$B/cuentas.py" restablecer --solo alan 2>&1)" echo "## el servicio: whoami, Mi cuenta, Equipo" python3 "$B/app.py" > "$T/app.log" 2>&1 & APP=$!; trap "kill $APP 2>/dev/null" EXIT; sleep 1.5 U="http://127.0.0.1:$PORT" como() { local u=$1; shift; curl -s -H "X-Remote-User: $u" -H "Content-Type: application/json" "$@"; } check "whoami de adriana: inicial" '"contrasena_inicial":true' "$(como adriana $U/api/whoami)" check "whoami de leo: no" '"contrasena_inicial":false' "$(como leo $U/api/whoami)" check "adriana la cambia" '"reset":false' "$(como adriana -X POST $U/api/password -d '{"actual":"Helgen2026!","nueva":"una-frase-larga","confirma":"una-frase-larga"}')" check "ya no es la inicial" '"contrasena_inicial":false' "$(como adriana $U/api/whoami)" check "historial dice que la cambió" "cambi" "$(py "print(json.load(open('$T/equipo/adriana.json'))['historial'][-1]['cambio'])")" check "leo se la restablece" '"reset":true' "$(como leo -X POST $U/api/password -d '{"usuario":"adriana","nueva":"Helgen2026!","confirma":"Helgen2026!"}')" check "vuelve a ser la inicial" '"contrasena_inicial":true' "$(como adriana $U/api/whoami)" check "Equipo (no admin) no muestra la marca" "0" "$(como joel $U/api/equipo | grep -c contrasena_inicial)" check "Equipo (admin) sí" "contrasena_inicial" "$(como leo $U/api/equipo)" check "sitio por alias (Ensenada → eit)" '"sitio":"eit"' "$(como leo -X PATCH "$U/api/equipo/miembro?id=joel" -d '{"sitio":"Ensenada","correo":"Joel@Helgen.tech"}')" check "correo en minúsculas" '"correo":"joel@helgen.tech"' "$(como leo $U/api/equipo)" check "correo inválido → 400" "Correo inv" "$(como leo -X PATCH "$U/api/equipo/miembro?id=joel" -d '{"correo":"joel"}')" check "sitio inválido → 400" "Sitio inv" "$(como leo -X PATCH "$U/api/equipo/miembro?id=joel" -d '{"sitio":"marte"}')" check "la marca no se cambia por PATCH" "True" "$(como leo -X PATCH "$U/api/equipo/miembro?id=miguel" -d '{"contrasena_inicial":false,"rol":"Responsable principal de instalación en EIT"}' >/dev/null; py "print(json.load(open('$T/equipo/miguel.json'))['contrasena_inicial'])")" check "alta desde Equipo: marca inicial y sitio" '"contrasena_inicial":true' "$(como leo -X POST $U/api/usuarios -d '{"usuario":"prueba","password_inicial":"Helgen2026!","nombre":"Prueba","area":"campo","sitio":"lct","correo":"prueba@helgen.tech"}')" check "índice CSV con las columnas nuevas" "sitio" "$(head -1 "$T/equipo/_indice.csv")" echo "## alguien que estaba como texto libre cuenta en cuanto se da de alta" check "pendiente con «Pepe» (todavía no es miembro)" "sin registrar" "$(como leo -X POST $U/api/tareas -d '{"id":"t-pepe","titulo":"Prueba Pepe","pista":"campo","responsable":"adriana, Pepe"}')" check "alta de Pepe" '"usuario":"pepe"' "$(como leo -X POST $U/api/usuarios -d '{"usuario":"pepe","password_inicial":"Helgen2026!","nombre":"Pepe","area":"campo"}')" check "el tablero ya se lo cuenta a Pepe" "t-pepe" "$(como leo "$U/api/tareas?responsable=pepe")" check "Equipo: Pepe con 1 abierto" "1" "$(como leo $U/api/equipo | python3 -c 'import sys,json; print([m["pendientes"]["abiertos"] for m in json.load(sys.stdin)["miembros"] if m["id"]=="pepe"][0])')" echo echo "RESULTADO: $ok correctas · $fail fallidas · datos en $T"