Files
dh-inspeccion-v2/scripts/phase-d5-3-7-1-acceptance.sh
T

137 lines
7.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
ROOT="${ROOT:-/var/www/dhv2.korexlabs.com}"
API_URL="${API_URL:-http://127.0.0.1:3101/api/v3}"
DUMMY_UUID="00000000-0000-4000-8000-000000000000"
TMP_MIGRATION="/tmp/dhv2-d537-accept-migration-$$.log"
trap 'rm -f "$TMP_MIGRATION"' EXIT
cd "$ROOT"
echo "========== D5.3.7.1 · VERSIONES =========="
[[ "$(node -p "require('./api-v3/package.json').version")" == "0.19.3-7.1" ]]
[[ "$(node -p "require('./web-v2/package.json').version")" == "0.19.3-7.1" ]]
grep -Fq "API_PHASE = 'D5.3.7.1'" api-v3/src/version.ts
echo "API: 0.19.3-7.1"
echo "WEB: 0.19.3-7.1"
echo
echo "========== D5.3.7.1 · DISCO Y DB =========="
FREE_KB="$(df -Pk "$ROOT" | awk 'NR==2 {print $4}')"
(( FREE_KB >= 2 * 1024 * 1024 ))
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
docker compose exec -T db sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
echo "Libre: $(( FREE_KB / 1024 / 1024 )) GB · PostgreSQL healthy"
echo
echo "========== D5.3.7.1 · MIGRACIONES =========="
docker compose --profile tools run --rm migrate npm run migration:show 2>&1 | tee "$TMP_MIGRATION"
grep -Fq 'Pending migrations: no' "$TMP_MIGRATION"
echo
echo "========== D5.3.7.1 · HEALTH =========="
HEALTH="$(curl -fsS "$API_URL/health")"
echo "$HEALTH"
grep -Fq '"version":"0.19.3-7.1"' <<<"$HEALTH"
grep -Fq '"phase":"D5.3.7.1"' <<<"$HEALTH"
grep -Fq '"database":"ok"' <<<"$HEALTH"
echo
echo "========== D5.3.7.1 · RUTAS PROTEGIDAS =========="
CODE="$(curl -sS -o /tmp/dhv2-d537-orgs-$$.txt -w '%{http_code}' "$API_URL/asset-imports/context/organizations")"
[[ "$CODE" == "401" ]]
CODE="$(curl -sS -o /tmp/dhv2-d537-plan-$$.txt -w '%{http_code}' "$API_URL/asset-imports/$DUMMY_UUID/plan")"
[[ "$CODE" == "401" ]]
CODE="$(curl -sS -o /tmp/dhv2-d537-apply-$$.txt -w '%{http_code}' -X POST -H 'Content-Type: application/json' -d '{"planHash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' "$API_URL/asset-imports/$DUMMY_UUID/apply")"
[[ "$CODE" == "401" ]]
rm -f /tmp/dhv2-d537-orgs-$$.txt /tmp/dhv2-d537-plan-$$.txt /tmp/dhv2-d537-apply-$$.txt
echo "Contexto, plan y aplicación requieren sesión: 401 OK"
echo
echo "========== D5.3.7.1 · MODELO Y PERMISOS =========="
DB_CHECK="$(
cat <<'SQL' | docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -v ON_ERROR_STOP=1 -v app_user="$APP_DB_USER"'
SELECT CASE WHEN to_regclass('public.asset_import_plans') IS NOT NULL AND to_regclass('public.asset_import_plan_items') IS NOT NULL THEN 'TABLES_OK' ELSE 'TABLES_BAD' END;
SELECT CASE WHEN EXISTS (SELECT 1 FROM permissions WHERE code='asset_imports.apply') THEN 'PERMISSION_OK' ELSE 'PERMISSION_BAD' END;
SELECT CASE WHEN NOT EXISTS (
SELECT role.code FROM roles role
WHERE role.code IN ('admin','director')
AND NOT EXISTS (
SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id
WHERE rp.role_id=role.id AND p.code='asset_imports.apply'
)
) THEN 'ROLE_GRANTS_OK' ELSE 'ROLE_GRANTS_BAD' END;
SELECT CASE WHEN
has_table_privilege(:'app_user','asset_import_plans','SELECT') AND
has_table_privilege(:'app_user','asset_import_plans','INSERT') AND
has_table_privilege(:'app_user','asset_import_plans','UPDATE') AND
NOT has_table_privilege(:'app_user','asset_import_plans','DELETE') AND
has_table_privilege(:'app_user','asset_import_plan_items','SELECT') AND
has_table_privilege(:'app_user','asset_import_plan_items','INSERT') AND
has_table_privilege(:'app_user','asset_import_plan_items','UPDATE') AND
NOT has_table_privilege(:'app_user','asset_import_plan_items','DELETE')
THEN 'APP_PRIVILEGES_OK' ELSE 'APP_PRIVILEGES_BAD' END;
SELECT CASE WHEN EXISTS (SELECT 1 FROM typeorm_migrations WHERE name='PhaseD537ImportPlanningApplication1787680800000') THEN 'MIGRATION_OK' ELSE 'MIGRATION_BAD' END;
SQL
)"
echo "$DB_CHECK"
grep -Fq 'TABLES_OK' <<<"$DB_CHECK"
grep -Fq 'PERMISSION_OK' <<<"$DB_CHECK"
grep -Fq 'ROLE_GRANTS_OK' <<<"$DB_CHECK"
grep -Fq 'APP_PRIVILEGES_OK' <<<"$DB_CHECK"
grep -Fq 'MIGRATION_OK' <<<"$DB_CHECK"
echo
echo "========== D5.3.7.1 · PLAN SEGURO =========="
grep -Fq 'export interface PlanItemRow' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "status === 'CONFLICT' || status === 'WARNING'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'PLAN_SOURCE_GROUP_REQUIRES_DECISION' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "organizationKind:/^UTE\\b/i" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "operationalAreaEntityKey:explicitlyUnassigned?null:areaItem.entityKey" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "const operatorUnassigned = isExplicitlyUnassignedOperator(operator)" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "operatorUnassigned ? '' : plainImportKey(operator)" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'generatedImportCode(plan.id, item.assetTypeCode, item.entityKey)' api-v3/src/asset-imports/asset-imports.service.ts
echo "Warnings bloqueantes, entidades únicas, UTE, operador no asignado y jerarquía controlada: OK"
echo
echo "========== D5.3.7.1 · APLICACIÓN TRANSACCIONAL =========="
grep -Fq 'async applyPlan' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'this.dataSource.transaction' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_PLAN_STALE' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_PARENT_RULE_INVALID' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'ASSET_IMPORT_OPERATIONAL_CONTEXT_INCOMPLETE' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'const areaKey=String(item.payload.operationalAreaEntityKey' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'PLAN_MATCH_OPERATIONAL_CONTEXT_MISMATCH' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'revalidateMatchedOperationalContext' api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq "permission: 'asset_imports.apply'" web-v2/src 2>/dev/null || grep -Fq "asset_imports.apply" web-v2/src/pages/AssetImportsPage.tsx
echo "Hash, transacción, jerarquía, contexto operativo y permiso de aplicación: OK"
echo
echo "========== D5.3.7.1 · REVERSIÓN LÓGICA =========="
grep -Fq "information_status='INACTIVE'" api-v3/src/asset-imports/asset-imports.service.ts
grep -Fq 'imported_asset_id=NULL' api-v3/src/asset-imports/asset-imports.service.ts
if grep -Fq 'DELETE FROM assets' api-v3/src/asset-imports/asset-imports.service.ts; then
echo "ERROR: la reversión no debe borrar assets"; exit 1
fi
echo "Rollback lógico, trazable y sin DELETE de activos: OK"
echo
echo "========== D5.3.7.1 · FRONTEND =========="
grep -Fq '4 · Plan' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '5 · Confirmación' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq '6 · Aplicación' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'APLICAR' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'operatorAssetId' web-v2/src/pages/AssetImportsPage.tsx
grep -Fq 'Revertir lote' web-v2/src/pages/AssetImportsPage.tsx
echo "Plan, confirmación, operador obligatorio y reversión visibles: OK"
echo
echo "========== D5.3.7.1 · CONTENEDORES =========="
docker compose ps -a
[[ "$(docker inspect dhv2-db --format '{{.State.Health.Status}}')" == "healthy" ]]
[[ "$(docker inspect dhv2-api --format '{{.State.Status}}')" == "running" ]]
[[ "$(docker inspect dhv2-web --format '{{.State.Status}}')" == "running" ]]
echo
echo "ACEPTACIÓN DE FASE D5.3.7.1: OK"