107 lines
10 KiB
Bash
107 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# Pruebas del 14-oct: equipo, accesos (htpasswd temporal), responsable obligatorio, resumen por responsable,
|
|
# siembra idempotente con el equipo. Complementa a tests-1013.sh (que sigue valiendo tal cual).
|
|
# Uso: bash tests-1014.sh <ruta-a-hbox-docs> [puerto]
|
|
set -u
|
|
REPO=${1:?ruta a hbox-docs}; PORT=${2:-8097}
|
|
T=$(mktemp -d); export ACTAS_DIR=$T/actas PORT HTPASSWD_FILE=$T/htpasswd ACTAS_ADMINS=leo,alan
|
|
mkdir -p $ACTAS_DIR
|
|
# htpasswd de prueba con el mismo hash $apr1$ que escribe el backend (leo y bob; alan es admin SIN login todavía)
|
|
python3 - "$REPO" "$T/htpasswd" <<'EOF'
|
|
import sys; sys.path.insert(0, sys.argv[1] + "/backend"); import app
|
|
open(sys.argv[2], "w").write("leo:" + app.apr1("secreto123", "abcdefgh") + "\nbob:" + app.apr1("secreto123", "hgfedcba") + "\n")
|
|
EOF
|
|
chmod 640 $T/htpasswd
|
|
python3 $REPO/backend/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"
|
|
LEO=(-s -H "X-Remote-User: leo" -H "Content-Type: application/json")
|
|
ALAN=(-s -H "X-Remote-User: alan" -H "Content-Type: application/json")
|
|
BOB=(-s -H "X-Remote-User: bob" -H "Content-Type: application/json")
|
|
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:160}"; fi; }
|
|
|
|
echo "## siembra (incluye equipo) — con un pendiente viejo sin responsable y sin responsable_ids"
|
|
mkdir -p $T/tareas
|
|
cat > $T/tareas/t-caja-ethernet.json <<'EOF'
|
|
{"id":"t-caja-ethernet","titulo":"Caja plástica HBOX — viejo","pista":"mecanica","estado":"bloqueado","responsable":"","fecha_limite":null,
|
|
"enlaces":[],"bloqueada_por":[],"orden":0,"etiquetas":[],"version_tarjeta":"—","creado_por":"leo","creado_en":"2026-10-13T20:00:00",
|
|
"modificado_por":"leo","modificado_en":"2026-10-13T20:00:00","historial":[{"quien":"leo","cuando":"2026-10-13T20:00:00","cambio":"creado en bloqueado"}]}
|
|
EOF
|
|
S1=$(python3 $REPO/backend/seed_1013.py --usuario leo 2>&1)
|
|
check "equipo sembrado (9)" "equipo: 9 miembros creados" "$S1"
|
|
check "pendientes nuevos (12 de 13: t-caja-ethernet ya existía)" "pendientes (seed_1013): 12 creados" "$S1"
|
|
check "responsable vacío completado a leo" "1 responsables completados" "$S1"
|
|
check "responsable_ids recalculado" "con responsable_ids recalculado" "$S1"
|
|
check "t-caja-ethernet → leo con historial" '"responsable": "leo"' "$(python3 -c "import json;print(json.dumps(json.load(open('$T/tareas/t-caja-ethernet.json')),indent=1))")"
|
|
S2=$(python3 $REPO/backend/seed_1013.py --usuario leo 2>&1)
|
|
check "segunda corrida: equipo no duplica" "equipo: 0 miembros creados, 9 ya existían" "$S2"
|
|
check "segunda corrida: nada que completar" "0 responsables completados, 0 con responsable_ids" "$S2"
|
|
check "t-ferreteria-eit-timsa sembrada para adriana" '"responsable_ids":["adriana"]' "$(curl -s "$U/api/tareas?responsable=adriana")"
|
|
|
|
echo "## equipo"
|
|
R=$(curl "${BOB[@]}" $U/api/equipo)
|
|
check "GET equipo (cualquiera)" '"total":9' "$R"
|
|
check "tiene_login derivado del htpasswd (leo sí)" '"id": "leo"' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps([m for m in json.load(sys.stdin)["miembros"] if m["tiene_login"]]))')"
|
|
check "adriana sin login" '"tiene_login": false' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps([m for m in json.load(sys.stdin)["miembros"] if m["id"]=="adriana"]))')"
|
|
check "es_admin desde ACTAS_ADMINS" '"es_admin": true' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps([m for m in json.load(sys.stdin)["miembros"] if m["id"]=="alan"]))')"
|
|
check "pendientes abiertos por miembro" '"abiertos": 1' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps([m["pendientes"] for m in json.load(sys.stdin)["miembros"] if m["id"]=="adriana"]))')"
|
|
check "logins sin miembro solo para admin (bob no admin → [])" '"logins_sin_miembro":[]' "$R"
|
|
check "logins sin miembro para admin (bob)" '"logins_sin_miembro":["bob"]' "$(curl "${LEO[@]}" $U/api/equipo)"
|
|
check "POST equipo no admin → 403" "Solo un administrador" "$(curl "${BOB[@]}" -X POST $U/api/equipo -d '{"id":"x","nombre":"X"}')"
|
|
check "POST equipo (admin)" '"id":"carla"' "$(curl "${LEO[@]}" -X POST $U/api/equipo -d '{"id":"Carla","nombre":"Carla Ruiz","rol":"Compras","area":"operaciones"}')"
|
|
check "POST equipo duplicado → 409" '"existe":true' "$(curl "${LEO[@]}" -X POST $U/api/equipo -d '{"id":"carla","nombre":"dup"}')"
|
|
check "POST equipo área inválida → 400" "inv\\u00e1lida: ventas" "$(curl "${LEO[@]}" -X POST $U/api/equipo -d '{"id":"z","nombre":"Z","area":"ventas"}')"
|
|
check "PATCH miembro no admin → 403" "Solo un administrador" "$(curl "${BOB[@]}" -X PATCH "$U/api/equipo/miembro?id=carla" -d '{"rol":"x"}')"
|
|
check "PATCH miembro (admin) → historial" "rol: Compras" "$(curl "${LEO[@]}" -X PATCH "$U/api/equipo/miembro?id=carla" -d '{"rol":"Compras y logística"}')"
|
|
check "PATCH activo=false (desactivar, nunca borrar)" '"activo":false' "$(curl "${LEO[@]}" -X PATCH "$U/api/equipo/miembro?id=carla" -d '{"activo":false}')"
|
|
check "GET equipo oculta inactivos" '"total":9' "$(curl -s $U/api/equipo)"
|
|
check "GET equipo?todos=1 los muestra" '"total":10' "$(curl -s "$U/api/equipo?todos=1")"
|
|
check "PATCH inexistente → 404" "No existe el miembro" "$(curl "${LEO[@]}" -X PATCH "$U/api/equipo/miembro?id=nadie" -d '{"rol":"x"}')"
|
|
|
|
echo "## accesos (htpasswd temporal)"
|
|
check "POST usuarios no admin → 403" "Solo un administrador" "$(curl "${BOB[@]}" -X POST $U/api/usuarios -d '{"usuario":"x","password_inicial":"12345678"}')"
|
|
check "contraseña corta → 400" "al menos 8" "$(curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"adriana","password_inicial":"corta"}')"
|
|
check "usuario con espacios/mayúsculas → 400" "sin espacios ni acentos" "$(curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"Adri ana","password_inicial":"12345678"}')"
|
|
check "crea acceso a miembro existente (adriana)" '"nuevo_miembro":false' "$(curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"adriana","password_inicial":"operaciones2026","correo":"adriana@helgen.tech"}')"
|
|
check "línea en el htpasswd con apr1" 'adriana:$apr1$' "$(cat $T/htpasswd)"
|
|
check "el hash valida con apr1 (misma rutina que /api/password)" "True" "$(python3 - "$REPO" "$T/htpasswd" <<'EOF'
|
|
import sys; sys.path.insert(0, sys.argv[1] + "/backend"); import app
|
|
l=[x for x in open(sys.argv[2]).read().splitlines() if x.startswith("adriana:")][0].split(":",1)[1]
|
|
print(app.apr1("operaciones2026", l.split("$")[2]) == l)
|
|
EOF
|
|
)"
|
|
check "permisos del archivo conservados (640)" "640" "$(stat -c %a $T/htpasswd)"
|
|
check "duplicado → 409" '"existe":true' "$(curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"adriana","password_inicial":"operaciones2026"}')"
|
|
check "crea usuario Y miembro nuevos" '"nuevo_miembro":true' "$(curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"nuevo1","password_inicial":"nuevo1pass","nombre":"Nuevo Uno","area":"campo"}')"
|
|
check "GET /api/usuarios sigue igual (lista del htpasswd)" '"usuarios":["adriana","bob","leo","nuevo1"]' "$(curl "${LEO[@]}" $U/api/usuarios)"
|
|
check "la cuenta nueva cambia su propia contraseña por /api/password" '"reset":false' "$(curl -s -H 'X-Remote-User: nuevo1' -H 'Content-Type: application/json' -X POST $U/api/password -d '{"actual":"nuevo1pass","nueva":"otraclave2026","confirma":"otraclave2026"}')"
|
|
check "bitácora _accesos.csv" "leo,alta,adriana" "$(cat $T/equipo/_accesos.csv)"
|
|
check "DELETE no admin → 403" "Solo un administrador" "$(curl "${BOB[@]}" -X DELETE "$U/api/usuario?u=nuevo1")"
|
|
check "DELETE a sí mismo → 400" "a ti mismo" "$(curl "${LEO[@]}" -X DELETE "$U/api/usuario?u=leo")"
|
|
check "DELETE inexistente → 404" "no tiene acceso" "$(curl "${LEO[@]}" -X DELETE "$U/api/usuario?u=nadie")"
|
|
check "último admin con acceso (alan admin sin login quita a leo) → 409" "ltimo administrador" "$(curl "${ALAN[@]}" -X DELETE "$U/api/usuario?u=leo")"
|
|
curl "${LEO[@]}" -X POST $U/api/usuarios -d '{"usuario":"alan","password_inicial":"alanclave2026"}' > /dev/null
|
|
check "con dos admins con acceso, alan sí puede quitar a leo" '"usuario":"leo"' "$(curl "${ALAN[@]}" -X DELETE "$U/api/usuario?u=leo")"
|
|
check "leo ya no está en el htpasswd" "" "$(grep -c '^leo:' $T/htpasswd | sed 's/^0$//')"
|
|
check "miembro leo desactivado (no borrado)" '"activo": false' "$(python3 -c "import json;print(json.dumps(json.load(open('$T/equipo/leo.json')),indent=1))")"
|
|
check "DELETE nuevo1 (admin alan)" '"miembro_desactivado":true' "$(curl "${ALAN[@]}" -X DELETE "$U/api/usuario?u=nuevo1")"
|
|
|
|
echo "## responsable obligatorio y resumen"
|
|
check "crear sin responsable → 400" "Falta el responsable" "$(curl "${BOB[@]}" -X POST $U/api/tareas -d '{"titulo":"x"}')"
|
|
check "crear con ids (y 'Jesús' con acento → jesus)" '"responsable_ids":["michelle","jesus"]' "$(curl "${BOB[@]}" -X POST $U/api/tareas -d '{"id":"t-x1","titulo":"x","responsable":"Michelle, Jesús"}')"
|
|
check "texto libre → se acepta con aviso" "sin registrar en el equipo: Proveedor X" "$(curl "${BOB[@]}" -X POST $U/api/tareas -d '{"id":"t-x2","titulo":"y","responsable":"leo / Proveedor X"}')"
|
|
check "lista como array también vale" '"responsable":"alan, daniel"' "$(curl "${BOB[@]}" -X POST $U/api/tareas -d '{"id":"t-x3","titulo":"z","responsable":["alan","daniel"]}')"
|
|
check "PATCH responsable vacío → 400" "Falta el responsable" "$(curl "${BOB[@]}" -X PATCH "$U/api/tarea?id=t-x1" -d '{"responsable":""}')"
|
|
check "PATCH sin tocar responsable sigue funcionando" '"estado":"en-curso"' "$(curl "${BOB[@]}" -X PATCH "$U/api/tarea?id=t-x1" -d '{"estado":"en-curso"}')"
|
|
check "filtro por id" '"id":"t-x1"' "$(curl -s "$U/api/tareas?responsable=jesus")"
|
|
check "filtro con acento" '"id":"t-x1"' "$(curl -s "$U/api/tareas?responsable=jes%C3%BAs")"
|
|
check "GET tareas trae el mapa equipo" '"michelle":{' "$(curl -s "$U/api/tareas?responsable=michelle" | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["equipo"], separators=(",",":")))')"
|
|
R=$(curl -s $U/api/tareas/resumen)
|
|
check "resumen por_responsable (jesus)" '"nombre": "Jes' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["por_responsable"]["jesus"], indent=1))')"
|
|
check "resumen por_responsable cuenta abiertos" '"abiertos": 3' "$(echo "$R" | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["por_responsable"]["jesus"], indent=1))')"
|
|
check "resumen conserva por_pista" '"por_pista"' "$R"
|
|
|
|
echo; echo "RESULTADO: $ok correctas · $fail fallidas · datos en $T"
|
|
[ $fail -eq 0 ]
|