Files
vigilcare-records/scripts/run-vigilcare-records-verification-p9.sh

477 lines
17 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# VigilCare Records — End-to-End Verification Script
# Tests the full digitization workflow: upload → entry → verify → approve → promote
#
# Prerequisites:
# - API running on http://localhost:5217
# - docker compose up -d (PostgreSQL, Redis, MinIO, Seq)
# - Seed data applied (dotnet run applies seed on startup)
# - A test PDF file at ./scripts/test-scan.pdf (create with: echo "test" | enscript -o - | ps2pdf - scripts/test-scan.pdf)
#
# Usage:
# chmod +x scripts/run-vigilcare-records-verification.sh
# ./scripts/run-vigilcare-records-verification.sh
API="http://localhost:5217/api/v1"
PASS=0
FAIL=0
TEST_FILE="${1:-scripts/test-scan.pdf}"
# Colors for terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_pass() { PASS=$((PASS + 1)); echo -e "${GREEN} PASS${NC} $1"; }
log_fail() { FAIL=$((FAIL + 1)); echo -e "${RED} FAIL${NC} $1"; }
log_step() { echo -e "\n${YELLOW}── $1 ──${NC}"; }
# Create a minimal test PDF if it doesn't exist
if [ ! -f "$TEST_FILE" ]; then
echo "Creating test PDF at $TEST_FILE..."
mkdir -p "$(dirname "$TEST_FILE")"
echo "%PDF-1.0
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj 2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj 3 0 obj<</Type/Page/MediaBox[0 0 612 792]/Parent 2 0 R/Resources<<>>>>endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer<</Size 4/Root 1 0 R>>
startxref
206
%%EOF" > "$TEST_FILE"
fi
# ============================================================================
# Phase 1: Authentication
# ============================================================================
log_step "1. Authentication"
# Login as intake clerk
INTAKE_LOGIN=$(curl -s -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"intake1","password":"password"}')
INTAKE_TOKEN=$(echo "$INTAKE_LOGIN" | jq -r '.data.token')
INTAKE_REFRESH=$(echo "$INTAKE_LOGIN" | jq -r '.data.refreshToken')
if [ "$INTAKE_TOKEN" != "null" ] && [ -n "$INTAKE_TOKEN" ]; then
log_pass "Intake clerk login returned JWT"
else
log_fail "Intake clerk login failed"
exit 1
fi
if [ "$INTAKE_REFRESH" != "null" ] && [ -n "$INTAKE_REFRESH" ]; then
log_pass "Intake clerk login returned refresh token"
else
log_fail "Intake clerk login missing refresh token"
fi
# Login as entry clerk
ENTRY_TOKEN=$(curl -s -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"entry1","password":"password"}' | jq -r '.data.token')
if [ "$ENTRY_TOKEN" != "null" ] && [ -n "$ENTRY_TOKEN" ]; then
log_pass "Entry clerk login returned JWT"
else
log_fail "Entry clerk login failed"
fi
# Login as verifier (must be different from entry clerk for separation of duties)
VERIFIER_TOKEN=$(curl -s -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"verifier1","password":"password"}' | jq -r '.data.token')
if [ "$VERIFIER_TOKEN" != "null" ] && [ -n "$VERIFIER_TOKEN" ]; then
log_pass "Verifier login returned JWT"
else
log_fail "Verifier login failed"
fi
# Login as approver
APPROVER_TOKEN=$(curl -s -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"approver1","password":"password"}' | jq -r '.data.token')
if [ "$APPROVER_TOKEN" != "null" ] && [ -n "$APPROVER_TOKEN" ]; then
log_pass "Approver login returned JWT"
else
log_fail "Approver login failed"
fi
# Verify /me endpoint
ME_ROLE=$(curl -s "$API/auth/me" \
-H "Authorization: Bearer $INTAKE_TOKEN" | jq -r '.data.role')
if [ "$ME_ROLE" = "INTAKE_CLERK" ]; then
log_pass "/me returns correct role: INTAKE_CLERK"
else
log_fail "/me returned unexpected role: $ME_ROLE"
fi
# Verify 401 without token
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API/auth/me")
if [ "$HTTP_CODE" = "401" ]; then
log_pass "Unauthenticated /me returns 401"
else
log_fail "Unauthenticated /me returned $HTTP_CODE (expected 401)"
fi
# Refresh token rotation
REFRESH_RESPONSE=$(curl -s -X POST "$API/auth/refresh" \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$INTAKE_REFRESH\"}")
NEW_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.token')
NEW_REFRESH=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refreshToken')
if [ "$NEW_TOKEN" != "null" ] && [ -n "$NEW_TOKEN" ] && [ "$NEW_REFRESH" != "$INTAKE_REFRESH" ]; then
log_pass "Refresh rotated tokens successfully"
INTAKE_TOKEN="$NEW_TOKEN"
INTAKE_REFRESH="$NEW_REFRESH"
else
log_fail "Token refresh failed: $REFRESH_RESPONSE"
fi
# Logout revokes refresh token
LOGOUT_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API/auth/logout" \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$INTAKE_REFRESH\"}")
if [ "$LOGOUT_CODE" = "204" ]; then
log_pass "Logout revoked refresh token (204)"
else
log_fail "Logout returned $LOGOUT_CODE (expected 204)"
fi
# Re-login for subsequent test phases
INTAKE_LOGIN=$(curl -s -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"intake1","password":"password"}')
INTAKE_TOKEN=$(echo "$INTAKE_LOGIN" | jq -r '.data.token')
# ============================================================================
# Phase 2: Upload and Batch Creation
# ============================================================================
log_step "2. Upload and Batch Creation"
UPLOAD_RESPONSE=$(curl -s -X POST "$API/digitization-batches" \
-H "Authorization: Bearer $INTAKE_TOKEN" \
-F "file=@$TEST_FILE" \
-F "batchType=VITALS_SHEET" \
-F "track=BACKFILL")
BATCH_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.data.id')
BATCH_STATUS=$(echo "$UPLOAD_RESPONSE" | jq -r '.data.status')
if [ "$BATCH_ID" != "null" ] && [ -n "$BATCH_ID" ]; then
log_pass "Batch created: $BATCH_ID"
else
log_fail "Batch creation failed: $UPLOAD_RESPONSE"
exit 1
fi
if [ "$BATCH_STATUS" = "UPLOADED" ]; then
log_pass "Initial status is UPLOADED"
else
log_fail "Initial status is $BATCH_STATUS (expected UPLOADED)"
fi
# Verify presigned URL
DOC_URL=$(curl -s "$API/digitization-batches/$BATCH_ID" \
-H "Authorization: Bearer $INTAKE_TOKEN" | jq -r '.data.documentUrl')
if [ "$DOC_URL" != "null" ] && [ -n "$DOC_URL" ]; then
log_pass "Presigned URL returned for document"
else
log_fail "No presigned URL returned"
fi
# ============================================================================
# Phase 3: Assign and Begin Entry
# ============================================================================
log_step "3. Assign and Begin Entry"
# Get entry clerk user ID
ENTRY_USER_ID=$(curl -s "$API/auth/me" \
-H "Authorization: Bearer $ENTRY_TOKEN" | jq -r '.data.id')
# Assign batch to entry clerk
ASSIGN_RESPONSE=$(curl -s -X PATCH "$API/digitization-batches/$BATCH_ID/assign" \
-H "Authorization: Bearer $INTAKE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"entryClerkUserId\":\"$ENTRY_USER_ID\"}")
ASSIGN_SUCCESS=$(echo "$ASSIGN_RESPONSE" | jq -r '.success')
if [ "$ASSIGN_SUCCESS" = "true" ]; then
log_pass "Batch assigned to entry clerk"
else
log_fail "Batch assignment failed: $(echo "$ASSIGN_RESPONSE" | jq -r '.error.message')"
fi
# ============================================================================
# Phase 4: Draft Data Entry
# ============================================================================
log_step "4. Draft Data Entry"
# Save draft patient
PATIENT_RESPONSE=$(curl -s -X PUT "$API/digitization-batches/$BATCH_ID/draft/patient" \
-H "Authorization: Bearer $ENTRY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fullName": "E2E Test Patient",
"dateOfBirth": "1985-06-15",
"sex": "male",
"bloodType": "A+",
"emergencyContact": "Test Contact - 555-9999",
"noKnownAllergies": true
}')
if [ "$(echo "$PATIENT_RESPONSE" | jq -r '.success')" = "true" ]; then
log_pass "Draft patient saved"
else
log_fail "Draft patient save failed: $(echo "$PATIENT_RESPONSE" | jq -r '.error.message')"
fi
# Save draft encounter
ENCOUNTER_RESPONSE=$(curl -s -X PUT "$API/digitization-batches/$BATCH_ID/draft/encounter" \
-H "Authorization: Bearer $ENTRY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"admissionDate": "2026-06-20T08:00:00Z",
"department": "Emergency Department",
"roomBed": "ED-12",
"admissionReason": "Chest pain, rule out MI"
}')
if [ "$(echo "$ENCOUNTER_RESPONSE" | jq -r '.success')" = "true" ]; then
log_pass "Draft encounter saved"
else
log_fail "Draft encounter save failed"
fi
# Add observations
for OBS in \
'{"observationCode":"HEART_RATE","value":95,"unit":"bpm","recordedAt":"2026-06-20T08:15:00Z"}' \
'{"observationCode":"TEMP_C","value":37.1,"unit":"C","recordedAt":"2026-06-20T08:15:00Z"}' \
'{"observationCode":"BP_SYSTOLIC","value":142,"unit":"mmHg","recordedAt":"2026-06-20T08:15:00Z"}' \
'{"observationCode":"BP_DIASTOLIC","value":88,"unit":"mmHg","recordedAt":"2026-06-20T08:15:00Z"}' \
'{"observationCode":"RESP_RATE","value":20,"unit":"breaths/min","recordedAt":"2026-06-20T08:15:00Z"}' \
'{"observationCode":"SPO2","value":97,"unit":"%","recordedAt":"2026-06-20T08:15:00Z"}'
do
OBS_RESULT=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/draft/observations" \
-H "Authorization: Bearer $ENTRY_TOKEN" \
-H "Content-Type: application/json" \
-d "$OBS")
OBS_CODE=$(echo "$OBS" | jq -r '.observationCode')
if [ "$(echo "$OBS_RESULT" | jq -r '.success')" = "true" ]; then
log_pass "Observation $OBS_CODE added"
else
log_fail "Observation $OBS_CODE failed: $(echo "$OBS_RESULT" | jq -r '.error.message')"
fi
done
# Verify draft is complete
DRAFT_RESPONSE=$(curl -s "$API/digitization-batches/$BATCH_ID/draft" \
-H "Authorization: Bearer $ENTRY_TOKEN")
OBS_COUNT=$(echo "$DRAFT_RESPONSE" | jq -r '.data.observations | length')
if [ "$OBS_COUNT" = "6" ]; then
log_pass "Draft has 6 observations"
else
log_fail "Draft has $OBS_COUNT observations (expected 6)"
fi
# Submit for verification
SUBMIT_RESPONSE=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/submit-for-verification" \
-H "Authorization: Bearer $ENTRY_TOKEN")
if [ "$(echo "$SUBMIT_RESPONSE" | jq -r '.success')" = "true" ]; then
log_pass "Submitted for verification"
else
log_fail "Submit failed: $(echo "$SUBMIT_RESPONSE" | jq -r '.error.message')"
fi
# Verify status is now PENDING_VERIFICATION
CURRENT_STATUS=$(curl -s "$API/digitization-batches/$BATCH_ID" \
-H "Authorization: Bearer $ENTRY_TOKEN" | jq -r '.data.batch.status')
if [ "$CURRENT_STATUS" = "PENDING_VERIFICATION" ]; then
log_pass "Status is PENDING_VERIFICATION"
else
log_fail "Status is $CURRENT_STATUS (expected PENDING_VERIFICATION)"
fi
# ============================================================================
# Phase 5: Separation of Duties Check
# ============================================================================
log_step "5. Separation of Duties"
# Entry clerk tries to verify their own batch — should get 409
SOD_RESPONSE=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/verify" \
-H "Authorization: Bearer $ENTRY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fieldChecks":[],"passed":true}')
SOD_CODE=$(echo "$SOD_RESPONSE" | jq -r '.error.code')
if [ "$SOD_CODE" = "SEPARATION_OF_DUTIES_VIOLATION" ]; then
log_pass "Entry clerk cannot verify own batch (409 SEPARATION_OF_DUTIES_VIOLATION)"
else
log_fail "Separation of duties not enforced: $SOD_CODE"
fi
# ============================================================================
# Phase 6: Verification
# ============================================================================
log_step "6. Verification (by different user)"
VERIFY_RESPONSE=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/verify" \
-H "Authorization: Bearer $VERIFIER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fieldChecks": [
{"fieldPath": "patient.fullName", "passed": true},
{"fieldPath": "patient.dateOfBirth", "passed": true},
{"fieldPath": "observations[0].value", "passed": true},
{"fieldPath": "observations[1].value", "passed": true},
{"fieldPath": "observations[2].value", "passed": true},
{"fieldPath": "observations[3].value", "passed": true},
{"fieldPath": "observations[4].value", "passed": true},
{"fieldPath": "observations[5].value", "passed": true}
],
"passed": true
}')
if [ "$(echo "$VERIFY_RESPONSE" | jq -r '.success')" = "true" ]; then
log_pass "Verification passed"
else
log_fail "Verification failed: $(echo "$VERIFY_RESPONSE" | jq -r '.error.message')"
fi
# Check status
VERIFIED_STATUS=$(curl -s "$API/digitization-batches/$BATCH_ID" \
-H "Authorization: Bearer $VERIFIER_TOKEN" | jq -r '.data.batch.status')
if [ "$VERIFIED_STATUS" = "VERIFIED" ] || [ "$VERIFIED_STATUS" = "AWAITING_CLINICAL_APPROVAL" ]; then
log_pass "Status after verification: $VERIFIED_STATUS"
else
log_fail "Status after verification: $VERIFIED_STATUS (expected VERIFIED or AWAITING_CLINICAL_APPROVAL)"
fi
# ============================================================================
# Phase 7: Approval and Promotion
# ============================================================================
log_step "7. Approval and Promotion"
APPROVE_RESPONSE=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/approve" \
-H "Authorization: Bearer $APPROVER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: e2e-test-$(date +%s)")
if [ "$(echo "$APPROVE_RESPONSE" | jq -r '.success')" = "true" ]; then
log_pass "Batch approved and promoted"
else
log_fail "Approval failed: $(echo "$APPROVE_RESPONSE" | jq -r '.error.message')"
fi
# Verify final status is PROMOTED
FINAL_STATUS=$(curl -s "$API/digitization-batches/$BATCH_ID" \
-H "Authorization: Bearer $APPROVER_TOKEN" | jq -r '.data.batch.status')
if [ "$FINAL_STATUS" = "PROMOTED" ]; then
log_pass "Final status is PROMOTED"
else
log_fail "Final status is $FINAL_STATUS (expected PROMOTED)"
fi
# Verify promotedAt is set
PROMOTED_AT=$(curl -s "$API/digitization-batches/$BATCH_ID" \
-H "Authorization: Bearer $APPROVER_TOKEN" | jq -r '.data.batch.promotedAt')
if [ "$PROMOTED_AT" != "null" ] && [ -n "$PROMOTED_AT" ]; then
log_pass "promotedAt timestamp set: $PROMOTED_AT"
else
log_fail "promotedAt is null"
fi
# ============================================================================
# Phase 8: Illegal Transition Check
# ============================================================================
log_step "8. Illegal Transitions"
# Try to re-approve a promoted batch — should get 409
ILLEGAL_RESPONSE=$(curl -s -X POST "$API/digitization-batches/$BATCH_ID/approve" \
-H "Authorization: Bearer $APPROVER_TOKEN" \
-H "Content-Type: application/json")
ILLEGAL_CODE=$(echo "$ILLEGAL_RESPONSE" | jq -r '.error.code')
if [ "$ILLEGAL_CODE" = "ILLEGAL_STATUS_TRANSITION" ]; then
log_pass "Cannot approve promoted batch (409 ILLEGAL_STATUS_TRANSITION)"
else
log_fail "Illegal transition not blocked: $ILLEGAL_CODE"
fi
# ============================================================================
# Phase 9: Work Queue Overview
# ============================================================================
log_step "9. Work Queue Overview"
OVERVIEW_RESPONSE=$(curl -s "$API/work-queue/overview" \
-H "Authorization: Bearer $APPROVER_TOKEN")
OVERVIEW_SUCCESS=$(echo "$OVERVIEW_RESPONSE" | jq -r '.success')
if [ "$OVERVIEW_SUCCESS" = "true" ]; then
log_pass "Work queue overview returned successfully"
echo " Status counts: $(echo "$OVERVIEW_RESPONSE" | jq -c '.data.statusCounts')"
echo " Reject rate: $(echo "$OVERVIEW_RESPONSE" | jq -r '.data.rejectRate')"
else
log_fail "Work queue overview failed"
fi
# ============================================================================
# Phase 10: Metrics Endpoint
# ============================================================================
log_step "10. Prometheus Metrics"
METRICS=$(curl -s http://localhost:5217/metrics)
if echo "$METRICS" | grep -q "digitization_batches_by_status"; then
log_pass "digitization_batches_by_status metric present"
else
log_fail "digitization_batches_by_status metric missing"
fi
if echo "$METRICS" | grep -q "digitization_promotion_duration_seconds"; then
log_pass "digitization_promotion_duration_seconds metric present"
else
log_fail "digitization_promotion_duration_seconds metric missing"
fi
if echo "$METRICS" | grep -q "digitization_rejection_total"; then
log_pass "digitization_rejection_total metric present"
else
log_fail "digitization_rejection_total metric missing"
fi
# ============================================================================
# Summary
# ============================================================================
echo ""
echo "============================================"
echo " E2E Verification Complete"
echo "============================================"
echo -e " ${GREEN}Passed: $PASS${NC}"
echo -e " ${RED}Failed: $FAIL${NC}"
echo ""
if [ "$FAIL" -eq 0 ]; then
echo -e " ${GREEN}ALL TESTS PASSED${NC}"
exit 0
else
echo -e " ${RED}$FAIL TEST(S) FAILED${NC}"
exit 1
fi