From fbb2ebcb721e8f41c2126166d170875dbcbdd002 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Thu, 10 Sep 2026 13:14:30 -0300 Subject: [PATCH] ci(F6.1): create a real inspection after territory preload --- .../workflows/inspection-planning-smoke.yml | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 .github/workflows/inspection-planning-smoke.yml diff --git a/.github/workflows/inspection-planning-smoke.yml b/.github/workflows/inspection-planning-smoke.yml new file mode 100644 index 0000000..36b4015 --- /dev/null +++ b/.github/workflows/inspection-planning-smoke.yml @@ -0,0 +1,247 @@ +name: Inspection planning smoke + +on: + pull_request: + branches: [main] + paths: + - 'api-v3/src/inspection-visits/**' + - 'api-v3/src/database/migrations/**' + - 'api-v3/src/reference-data/**' + - 'web-v2/src/pages/InspectionVisitCreateF61Page.tsx' + - 'web-v2/src/pages/FieldBriefingsPage.tsx' + - 'web-v2/src/layout/AppLayout.tsx' + - '.github/workflows/inspection-planning-smoke.yml' + +permissions: + contents: read + +jobs: + create-inspection: + name: F6.1 · real inspection create + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Migrate presentation database + run: | + set -Eeuo pipefail + cleanup() { + docker compose --env-file .env.example --profile tools down -v --remove-orphans >/dev/null 2>&1 || true + } + trap cleanup EXIT + cleanup + docker compose --env-file .env.example up -d db + + bootstrap_log="$(mktemp)" + set +e + docker compose --env-file .env.example --profile tools run --build --rm migrate 2>&1 | tee "$bootstrap_log" + bootstrap_status=${PIPESTATUS[0]} + set -e + if [ "$bootstrap_status" -eq 0 ]; then + echo 'ERROR: expected historical one-shot reset guard on clean database.' >&2 + exit 1 + fi + grep -Fq 'Production reset aborted: expected exactly one username admin, found 0' "$bootstrap_log" || { + echo 'ERROR: migration chain failed before the expected historical reset guard.' >&2 + exit 1 + } + rm -f "$bootstrap_log" + + docker compose --env-file .env.example exec -T db \ + psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' + INSERT INTO typeorm_migrations ("timestamp", name) + SELECT 1788652800000, 'ResetProductionOperationalData1788652800000' + WHERE NOT EXISTS ( + SELECT 1 FROM typeorm_migrations + WHERE name='ResetProductionOperationalData1788652800000' + ); + SQL + + docker compose --env-file .env.example --profile tools run --rm migrate + + docker compose --env-file .env.example exec -T db \ + psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' + DO $$ + DECLARE invalid_areas integer; invalid_yacimientos integer; + BEGIN + SELECT COUNT(*) INTO invalid_areas + FROM assets area + JOIN asset_types type ON type.id=area.asset_type_id + LEFT JOIN assets department ON department.id=area.parent_id + LEFT JOIN asset_types department_type ON department_type.id=department.asset_type_id + WHERE lower(type.code)='area' + AND (department.id IS NULL OR lower(department_type.code)<>'departamento'); + IF invalid_areas <> 0 THEN + RAISE EXCEPTION 'Invalid Area→Departamento relations: %', invalid_areas; + END IF; + + SELECT COUNT(*) INTO invalid_yacimientos + FROM assets yacimiento + JOIN asset_types type ON type.id=yacimiento.asset_type_id + LEFT JOIN assets area ON area.id=yacimiento.parent_id + LEFT JOIN asset_types area_type ON area_type.id=area.asset_type_id + WHERE lower(type.code)='yacimiento' + AND (area.id IS NULL OR lower(area_type.code)<>'area' + OR yacimiento.operational_area_id IS DISTINCT FROM area.id); + IF invalid_yacimientos <> 0 THEN + RAISE EXCEPTION 'Invalid Yacimiento→Area relations: %', invalid_yacimientos; + END IF; + END $$; + SQL + + export JWT_ACCESS_SECRET='CI_ACCESS_SECRET_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + export REFRESH_TOKEN_PEPPER='CI_REFRESH_PEPPER_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + export SMTP_SETTINGS_MASTER_KEY='' + docker compose --env-file .env.example build api + + # Seed two ephemeral identities only in this disposable CI database: + # one caller with planning permissions and one executable Inspector. + docker compose --env-file .env.example exec -T db \ + psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' + INSERT INTO users(id,username,password_hash,first_name,last_name,status,must_change_password) + VALUES + ('11111111-1111-4111-8111-111111111111','ci_planner','ci-unused','CI','Planner','ACTIVE',false), + ('22222222-2222-4222-8222-222222222222','ci_inspector','ci-unused','CI','Inspector','ACTIVE',false); + + INSERT INTO user_roles(user_id,role_id,assigned_by) + SELECT '11111111-1111-4111-8111-111111111111'::uuid, role.id, + '11111111-1111-4111-8111-111111111111'::uuid + FROM roles role + WHERE 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='inspections.manage' + ) + AND 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='inspections.assign' + ) + ORDER BY role.is_system DESC,role.code + LIMIT 1; + + INSERT INTO user_roles(user_id,role_id,assigned_by) + SELECT '22222222-2222-4222-8222-222222222222'::uuid, role.id, + '11111111-1111-4111-8111-111111111111'::uuid + FROM roles role + WHERE 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='inspections.execute' + ) + ORDER BY (lower(role.code)='inspector') DESC,role.is_system DESC,role.code + LIMIT 1; + + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM user_roles WHERE user_id='11111111-1111-4111-8111-111111111111'::uuid) THEN + RAISE EXCEPTION 'CI planner role with manage+assign permissions not found'; + END IF; + IF NOT EXISTS (SELECT 1 FROM user_roles WHERE user_id='22222222-2222-4222-8222-222222222222'::uuid) THEN + RAISE EXCEPTION 'CI inspector role with execute permission not found'; + END IF; + END $$; + + INSERT INTO auth_sessions( + id,user_id,refresh_token_hash,expires_at,last_used_at,revoked_at,replaced_by_session_id,ip,user_agent,device_label + ) VALUES ( + '33333333-3333-4333-8333-333333333333', + '11111111-1111-4111-8111-111111111111','ci-unused', + CURRENT_TIMESTAMP + INTERVAL '10 minutes',CURRENT_TIMESTAMP,NULL,NULL,NULL,'CI','inspection-planning-smoke' + ); + SQL + + docker compose --env-file .env.example up -d api + for _ in $(seq 1 30); do + curl -fsS http://127.0.0.1:3101/api/v3/health >/dev/null 2>&1 && break + sleep 2 + done + curl -fsS http://127.0.0.1:3101/api/v3/health >/dev/null + + TOKEN="$(docker compose --env-file .env.example exec -T api node - <<'NODE' + const jwt = require('jsonwebtoken'); + process.stdout.write(jwt.sign({ + sub:'11111111-1111-4111-8111-111111111111', + sid:'33333333-3333-4333-8333-333333333333', + username:'ci_planner',typ:'access' + }, process.env.JWT_ACCESS_SECRET, { + algorithm:'HS256',expiresIn:300,issuer:'dhv2-api',audience:'dhv2' + })); + NODE + )" + + CONTEXT="$(docker compose --env-file .env.example exec -T db \ + psql -At -F '|' -U dhv2_owner -d dhv2 -c " + SELECT department.id,area.id,yacimiento.id,relation.company_id + FROM assets department + JOIN asset_types department_type ON department_type.id=department.asset_type_id + JOIN assets area ON area.parent_id=department.id + JOIN asset_types area_type ON area_type.id=area.asset_type_id + JOIN assets yacimiento ON yacimiento.parent_id=area.id + JOIN asset_types yacimiento_type ON yacimiento_type.id=yacimiento.asset_type_id + JOIN area_company_relations relation ON relation.area_id=area.id + AND relation.relation_role='OPERATOR' AND relation.valid_until IS NULL + WHERE lower(department_type.code)='departamento' + AND lower(area_type.code)='area' + AND lower(yacimiento_type.code)='yacimiento' + ORDER BY department.name,area.name,yacimiento.name + LIMIT 1; + ")" + IFS='|' read -r DEPARTMENT_ID AREA_ID YACIMIENTO_ID COMPANY_ID <<< "$CONTEXT" + test -n "$DEPARTMENT_ID" && test -n "$AREA_ID" && test -n "$YACIMIENTO_ID" && test -n "$COMPANY_ID" + + curl -fsS -H "Authorization: Bearer $TOKEN" \ + http://127.0.0.1:3101/api/v3/inspection-visits/planning-context/departments \ + >/tmp/dhv2-departments.json + grep -Fq "$DEPARTMENT_ID" /tmp/dhv2-departments.json + + curl -fsS -H "Authorization: Bearer $TOKEN" \ + "http://127.0.0.1:3101/api/v3/inspection-visits/planning-context/departments/$DEPARTMENT_ID/areas" \ + >/tmp/dhv2-areas.json + grep -Fq "$AREA_ID" /tmp/dhv2-areas.json + + curl -fsS -H "Authorization: Bearer $TOKEN" \ + "http://127.0.0.1:3101/api/v3/inspection-visits/planning-context/areas/$AREA_ID/yacimientos" \ + >/tmp/dhv2-yacimientos.json + grep -Fq "$YACIMIENTO_ID" /tmp/dhv2-yacimientos.json + + START_AT="$(date -u -d '+1 day' +'%Y-%m-%dT%H:%M:%S.000Z')" + HTTP_CODE="$(curl -sS -o /tmp/dhv2-created.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json' \ + -d "{\"operationalAreaId\":\"$AREA_ID\",\"scopeAssetId\":\"$YACIMIENTO_ID\",\"operatorCompanyId\":\"$COMPANY_ID\",\"plannedStartAt\":\"$START_AT\",\"leadInspectorUserId\":\"22222222-2222-4222-8222-222222222222\"}" \ + http://127.0.0.1:3101/api/v3/inspection-visits)" + if [ "$HTTP_CODE" != 201 ]; then + echo "ERROR: real inspection create returned HTTP $HTTP_CODE" >&2 + cat /tmp/dhv2-created.json >&2 + docker compose --env-file .env.example logs --no-color api >&2 || true + exit 1 + fi + + VISIT_ID="$(python - <<'PY' + import json + with open('/tmp/dhv2-created.json',encoding='utf-8') as f: + print(json.load(f)['id']) + PY + )" + + docker compose --env-file .env.example exec -T db \ + psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 \ + -v visit_id="$VISIT_ID" -v area_id="$AREA_ID" -v yacimiento_id="$YACIMIENTO_ID" -v company_id="$COMPANY_ID" <<'SQL' + DO $$ + DECLARE ok integer; + BEGIN + SELECT COUNT(*) INTO ok + FROM inspection_visits visit + JOIN assets yacimiento ON yacimiento.id=visit.scope_asset_id + JOIN asset_types yacimiento_type ON yacimiento_type.id=yacimiento.asset_type_id + WHERE visit.id=:'visit_id'::uuid + AND visit.operational_area_id=:'area_id'::uuid + AND visit.scope_asset_id=:'yacimiento_id'::uuid + AND visit.operator_company_id=:'company_id'::uuid + AND yacimiento.parent_id=visit.operational_area_id + AND lower(yacimiento_type.code)='yacimiento'; + IF ok <> 1 THEN + RAISE EXCEPTION 'Created Inspection did not preserve Area/Yacimiento hierarchy'; + END IF; + END $$; + SQL + + echo "F6.1 real inspection create smoke: OK ($VISIT_ID)"