137 lines
7.3 KiB
Bash
Executable File
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"
|