477 lines
17 KiB
Bash
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 |