239 lines
9.8 KiB
Bash
Executable File
239 lines
9.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
|
|
dhv2_base_url="${dhv2_base_url%/}"
|
|
dhv2_nil_uuid="00000000-0000-4000-8000-000000000000"
|
|
|
|
fail() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "========== D5 · MIGRACIONES =========="
|
|
dhv2_migration_status="$(docker compose --profile tools run --rm migrate npm run migration:show 2>&1)"
|
|
grep -q 'Pending migrations: no' <<<"$dhv2_migration_status" \
|
|
|| fail "Quedaron migraciones pendientes"
|
|
echo "Migraciones pendientes: no"
|
|
|
|
echo
|
|
echo "========== D5 · HEALTH =========="
|
|
dhv2_internal="$(curl -fsS --connect-timeout 10 --max-time 30 http://127.0.0.1:3101/api/v3/health)"
|
|
dhv2_external="$(curl -fsS --connect-timeout 10 --max-time 30 "$dhv2_base_url/api/v3/health")"
|
|
grep -q '"version":"0.19.0"' <<<"$dhv2_internal" || fail "Health interno no informa v0.19.0"
|
|
grep -q '"phase":"D5"' <<<"$dhv2_internal" || fail "Health interno no informa D5"
|
|
grep -q '"version":"0.19.0"' <<<"$dhv2_external" || fail "Health externo no informa v0.19.0"
|
|
grep -q '"database":"ok"' <<<"$dhv2_external" || fail "La base no está saludable"
|
|
echo "$dhv2_external"
|
|
|
|
echo
|
|
echo "========== D5 · RUTAS PROTEGIDAS =========="
|
|
dhv2_get_paths=(
|
|
"inspection-acts/$dhv2_nil_uuid/closure"
|
|
"inspection-act-signatures/$dhv2_nil_uuid/content"
|
|
)
|
|
for dhv2_path in "${dhv2_get_paths[@]}"; do
|
|
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
|
|
-o /tmp/dhv2-d5-response.json -w '%{http_code}' \
|
|
"$dhv2_base_url/api/v3/$dhv2_path")"
|
|
test "$dhv2_code" = "401" \
|
|
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
|
|
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
|
|
done
|
|
dhv2_post_paths=(
|
|
"inspection-acts/$dhv2_nil_uuid/ready"
|
|
"inspection-acts/$dhv2_nil_uuid/company-outcome"
|
|
"inspection-acts/$dhv2_nil_uuid/close"
|
|
)
|
|
for dhv2_path in "${dhv2_post_paths[@]}"; do
|
|
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
|
|
-X POST -H 'Content-Type: application/json' -d '{}' \
|
|
-o /tmp/dhv2-d5-response.json -w '%{http_code}' \
|
|
"$dhv2_base_url/api/v3/$dhv2_path")"
|
|
test "$dhv2_code" = "401" \
|
|
|| fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
|
|
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
|
|
done
|
|
rm -f /tmp/dhv2-d5-response.json
|
|
|
|
echo
|
|
echo "========== D5 · MODELO E INTEGRIDAD =========="
|
|
dhv2_db_check="$(docker compose exec -T db sh -lc \
|
|
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -At' <<'SQL'
|
|
SELECT 'tables', COUNT(*)
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name IN (
|
|
'inspection_act_responsibles',
|
|
'inspection_act_closures',
|
|
'inspection_act_signatures'
|
|
);
|
|
|
|
SELECT 'act_closure_columns', COUNT(*)
|
|
FROM information_schema.columns
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'inspection_acts'
|
|
AND column_name IN ('closed_at', 'closed_by', 'closure_sha256');
|
|
|
|
SELECT 'responsible_checks', COUNT(*)
|
|
FROM pg_constraint
|
|
WHERE conname IN (
|
|
'chk_inspection_act_responsibles_attendance',
|
|
'chk_inspection_act_responsibles_document_type',
|
|
'chk_inspection_act_responsibles_details'
|
|
);
|
|
|
|
SELECT 'closure_checks', COUNT(*)
|
|
FROM pg_constraint
|
|
WHERE conname IN (
|
|
'chk_inspection_act_closures_schema',
|
|
'chk_inspection_act_closures_prepared_sha',
|
|
'chk_inspection_act_closures_final_sha',
|
|
'chk_inspection_act_closures_upload_mode',
|
|
'chk_inspection_act_closures_final'
|
|
);
|
|
|
|
SELECT 'signature_checks', COUNT(*)
|
|
FROM pg_constraint
|
|
WHERE conname IN (
|
|
'chk_inspection_act_signatures_signer_type',
|
|
'chk_inspection_act_signatures_status',
|
|
'chk_inspection_act_signatures_document_type',
|
|
'chk_inspection_act_signatures_source',
|
|
'chk_inspection_act_signatures_sha',
|
|
'chk_inspection_act_signatures_coordinates',
|
|
'chk_inspection_act_signatures_accuracy',
|
|
'chk_inspection_act_signatures_inspector',
|
|
'chk_inspection_act_signatures_company',
|
|
'chk_inspection_act_signatures_signed'
|
|
);
|
|
|
|
SELECT 'unique_guards', COUNT(*)
|
|
FROM pg_indexes
|
|
WHERE schemaname = 'public'
|
|
AND indexname IN (
|
|
'uq_inspection_act_inspector_signature',
|
|
'uq_inspection_act_company_outcome',
|
|
'uq_inspection_act_signatures_stored_name',
|
|
'uq_inspection_act_closures_final_sha'
|
|
);
|
|
|
|
SELECT 'integrity_triggers', COUNT(*)
|
|
FROM pg_trigger
|
|
WHERE NOT tgisinternal
|
|
AND tgname IN (
|
|
'trg_inspection_act_responsibles_draft',
|
|
'trg_inspection_act_signatures_ready',
|
|
'trg_inspection_acts_closed_immutable',
|
|
'trg_inspection_act_closures_immutable',
|
|
'trg_inspection_act_assets_draft',
|
|
'trg_inspection_findings_frozen_fields',
|
|
'trg_inspection_evidence_observation_draft'
|
|
);
|
|
|
|
SELECT 'permissions', COUNT(*)
|
|
FROM permissions
|
|
WHERE code IN (
|
|
'inspection_closure.read',
|
|
'inspection_closure.prepare',
|
|
'inspection_closure.sign',
|
|
'inspection_closure.close'
|
|
);
|
|
|
|
SELECT 'read_roles', COUNT(*)
|
|
FROM role_permissions rp
|
|
INNER JOIN roles role ON role.id = rp.role_id
|
|
INNER JOIN permissions permission ON permission.id = rp.permission_id
|
|
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector', 'auditor')
|
|
AND permission.code = 'inspection_closure.read';
|
|
|
|
SELECT 'prepare_roles', COUNT(*)
|
|
FROM role_permissions rp
|
|
INNER JOIN roles role ON role.id = rp.role_id
|
|
INNER JOIN permissions permission ON permission.id = rp.permission_id
|
|
WHERE role.code IN ('admin', 'supervisor', 'inspector')
|
|
AND permission.code = 'inspection_closure.prepare';
|
|
|
|
SELECT 'sign_roles', COUNT(*)
|
|
FROM role_permissions rp
|
|
INNER JOIN roles role ON role.id = rp.role_id
|
|
INNER JOIN permissions permission ON permission.id = rp.permission_id
|
|
WHERE role.code IN ('admin', 'supervisor', 'inspector')
|
|
AND permission.code = 'inspection_closure.sign';
|
|
|
|
SELECT 'close_roles', COUNT(*)
|
|
FROM role_permissions rp
|
|
INNER JOIN roles role ON role.id = rp.role_id
|
|
INNER JOIN permissions permission ON permission.id = rp.permission_id
|
|
WHERE role.code IN ('admin', 'director', 'supervisor', 'inspector')
|
|
AND permission.code = 'inspection_closure.close';
|
|
SQL
|
|
)"
|
|
grep -q 'tables|3' <<<"$dhv2_db_check" || fail "Faltan tablas D5"
|
|
grep -q 'act_closure_columns|3' <<<"$dhv2_db_check" || fail "Faltan columnas de cierre en el acta"
|
|
grep -q 'responsible_checks|3' <<<"$dhv2_db_check" || fail "Faltan controles del responsable"
|
|
grep -q 'closure_checks|5' <<<"$dhv2_db_check" || fail "Faltan controles del cierre"
|
|
grep -q 'signature_checks|10' <<<"$dhv2_db_check" || fail "Faltan controles de firmas"
|
|
grep -q 'unique_guards|4' <<<"$dhv2_db_check" || fail "Faltan claves únicas D5"
|
|
grep -q 'integrity_triggers|7' <<<"$dhv2_db_check" || fail "Faltan triggers de integridad D5"
|
|
grep -q 'permissions|4' <<<"$dhv2_db_check" || fail "Faltan permisos D5"
|
|
grep -q 'read_roles|5' <<<"$dhv2_db_check" || fail "Faltan roles de lectura"
|
|
grep -q 'prepare_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de preparación"
|
|
grep -q 'sign_roles|3' <<<"$dhv2_db_check" || fail "Faltan roles de firma"
|
|
grep -q 'close_roles|4' <<<"$dhv2_db_check" || fail "Faltan roles de cierre"
|
|
echo "$dhv2_db_check"
|
|
|
|
echo
|
|
echo "========== D5 · PRIVILEGIOS RUNTIME =========="
|
|
dhv2_privileges="$(docker compose exec -T db sh -lc \
|
|
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER" -At' <<'SQL'
|
|
SELECT 'responsible_select', has_table_privilege(:'app_user', 'inspection_act_responsibles', 'SELECT');
|
|
SELECT 'responsible_insert', has_table_privilege(:'app_user', 'inspection_act_responsibles', 'INSERT');
|
|
SELECT 'responsible_update', has_table_privilege(:'app_user', 'inspection_act_responsibles', 'UPDATE');
|
|
SELECT 'responsible_delete', has_table_privilege(:'app_user', 'inspection_act_responsibles', 'DELETE');
|
|
SELECT 'closure_select', has_table_privilege(:'app_user', 'inspection_act_closures', 'SELECT');
|
|
SELECT 'closure_insert', has_table_privilege(:'app_user', 'inspection_act_closures', 'INSERT');
|
|
SELECT 'closure_update', has_table_privilege(:'app_user', 'inspection_act_closures', 'UPDATE');
|
|
SELECT 'closure_delete', has_table_privilege(:'app_user', 'inspection_act_closures', 'DELETE');
|
|
SELECT 'signature_select', has_table_privilege(:'app_user', 'inspection_act_signatures', 'SELECT');
|
|
SELECT 'signature_insert', has_table_privilege(:'app_user', 'inspection_act_signatures', 'INSERT');
|
|
SELECT 'signature_update', has_table_privilege(:'app_user', 'inspection_act_signatures', 'UPDATE');
|
|
SELECT 'signature_delete', has_table_privilege(:'app_user', 'inspection_act_signatures', 'DELETE');
|
|
SQL
|
|
)"
|
|
for dhv2_expected in \
|
|
responsible_select responsible_insert responsible_update \
|
|
closure_select closure_insert closure_update \
|
|
signature_select signature_insert; do
|
|
grep -q "${dhv2_expected}|t" <<<"$dhv2_privileges" \
|
|
|| fail "Falta privilegio runtime ${dhv2_expected}"
|
|
done
|
|
for dhv2_forbidden in \
|
|
responsible_delete closure_delete signature_update signature_delete; do
|
|
grep -q "${dhv2_forbidden}|f" <<<"$dhv2_privileges" \
|
|
|| fail "Privilegio runtime indebido ${dhv2_forbidden}"
|
|
done
|
|
echo "$dhv2_privileges"
|
|
|
|
echo
|
|
echo "========== D5 · ALMACENAMIENTO =========="
|
|
docker compose exec -T api sh -lc \
|
|
'mkdir -p /app/storage/asset-media/inspection-signatures && chmod 700 /app/storage/asset-media/inspection-signatures && test -w /app/storage/asset-media/inspection-signatures'
|
|
echo "Directorio persistente de firmas protegido y escribible: OK"
|
|
|
|
echo
|
|
echo "========== D5 · FRONTEND =========="
|
|
docker compose exec -T web sh -lc \
|
|
'grep -R "0.19.0" /usr/share/nginx/html/assets >/dev/null && grep -R "Cierre y firmas" /usr/share/nginx/html/assets >/dev/null && grep -R "Cerrar y sellar" /usr/share/nginx/html/assets >/dev/null && grep -R "firma manuscrita" /usr/share/nginx/html/assets >/dev/null && grep -R "Los hallazgos" /usr/share/nginx/html/assets >/dev/null'
|
|
echo "Flujo de cierre, firma y footer v0.19.0: OK"
|
|
|
|
echo
|
|
echo "========== D5 · CONTROLADOR =========="
|
|
docker compose logs api | grep 'InspectionClosingController' >/dev/null \
|
|
|| fail "InspectionClosingController no aparece en las rutas cargadas"
|
|
echo "InspectionClosingController cargado: OK"
|
|
|
|
echo
|
|
echo "ACEPTACIÓN DE FASE D5: OK"
|