Files
vigilcare-records/scripts/run-vigilcare-records-phase-10-verification.sh
voltsrage c871dc4842
CI / backend (push) Failing after 2m26s
CI / frontend (push) Failing after 53s
Add deployment files
2026-08-11 20:17:53 +08:00

752 lines
23 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Runs Phase 10 verification checks from docs/plans/phase-10-plan.md.
#
# Covers cover sheet generation, lookup, list filters, PDF endpoints,
# barcode-assisted batch upload, redeem/auto-assign, and reuse prevention.
#
# Prerequisites:
# docker compose up -d (PostgreSQL, Redis, MinIO)
# dotnet ef database update --project VigilCareRecordsAPI
# dotnet run --project VigilCareRecordsAPI
# Phase 19 seed data (intake1, entry1, admin1)
#
# Environment overrides:
# VIGILCARE_API_URL default: http://localhost:5217
# VIGILCARE_COMPOSE_FILE default: <repo>/docker-compose.yml
# VIGILCARE_PG_HOST default: localhost
# VIGILCARE_PG_PORT default: 5437
# VIGILCARE_PG_DB default: vigilcare_records
# VIGILCARE_PG_USER default: postgres
# VIGILCARE_PG_PASSWORD default: password
# VIGILCARE_SKIP_DB_CHECKS set to 1 to skip PostgreSQL assertions
# VIGILCARE_SKIP_TEST_CHECKS set to 1 to skip dotnet integration tests
#
# Usage:
# chmod +x scripts/run-vigilcare-records-phase-10-verification.sh
# ./scripts/run-vigilcare-records-phase-10-verification.sh
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FIXTURE_PDF="$SCRIPT_DIR/fixtures/test-scan.pdf"
API_URL="${VIGILCARE_API_URL:-http://localhost:5217}"
COMPOSE_FILE="${VIGILCARE_COMPOSE_FILE:-$REPO_ROOT/docker-compose.yml}"
COMPOSE=(docker compose -f "$COMPOSE_FILE")
PG_HOST="${VIGILCARE_PG_HOST:-localhost}"
PG_PORT="${VIGILCARE_PG_PORT:-5437}"
PG_DB="${VIGILCARE_PG_DB:-vigilcare_records}"
PG_USER="${VIGILCARE_PG_USER:-postgres}"
PG_PASSWORD="${VIGILCARE_PG_PASSWORD:-password}"
SKIP_DB_CHECKS="${VIGILCARE_SKIP_DB_CHECKS:-0}"
SKIP_TEST_CHECKS="${VIGILCARE_SKIP_TEST_CHECKS:-0}"
# Resolved at runtime from the live API (seed IDs vary per environment)
ENTRY_CLERK1_ID=""
PATIENT1_ID=""
PASS_COUNT=0
FAIL_COUNT=0
FAILED_TESTS=()
# Shared state populated during the run
INTAKE_TOKEN=""
ADMIN_TOKEN=""
ENTRY_TOKEN=""
UPLOAD_CODE=""
UPLOAD_COVER_SHEET_ID=""
UPLOAD_BATCH_ID=""
ASSIGN_CODE=""
PDF_SHEET_IDS=()
log() {
printf '%s\n' "$*"
}
section() {
log ""
log "== $1 =="
}
pass() {
PASS_COUNT=$((PASS_COUNT + 1))
log " PASS: $1"
}
fail() {
FAIL_COUNT=$((FAIL_COUNT + 1))
FAILED_TESTS+=("$1")
log " FAIL: $1"
}
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
log "ERROR: required command not found: $cmd"
exit 1
fi
}
ensure_fixture_pdf() {
if [[ -f "$FIXTURE_PDF" ]]; then
return 0
fi
mkdir -p "$(dirname "$FIXTURE_PDF")"
cat >"$FIXTURE_PDF" <<'EOF'
%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
EOF
}
unique_pdf_path() {
local suffix="$1"
local path="/tmp/vigilcare-p10-${suffix}-${RANDOM}.pdf"
printf '%%PDF-1.4\nphase10-%s-%s\n%%%%EOF\n' "$suffix" "$(date +%s%N)" >"$path"
printf '%s' "$path"
}
http_code() {
curl -sS -o /dev/null -w '%{http_code}' "$@"
}
json_post() {
local url="$1"
local body="$2"
local token="${3:-}"
if [[ -n "$token" ]]; then
curl -sS -X POST "$url" \
-H "Authorization: Bearer $token" \
-H 'Content-Type: application/json' \
-d "$body"
else
curl -sS -X POST "$url" \
-H 'Content-Type: application/json' \
-d "$body"
fi
}
json_get() {
local url="$1"
local token="$2"
curl -sS "$url" -H "Authorization: Bearer $token"
}
login() {
local username="$1"
local password="${2:-password}"
json_post "$API_URL/api/v1/auth/login" \
"{\"username\":\"$username\",\"password\":\"$password\"}"
}
extract_data_field() {
local json="$1"
local field="$2"
jq -er ".data.$field // empty" <<<"$json"
}
extract_bool_field() {
local json="$1"
local field="$2"
jq -er ".data.$field | if . == null then empty else tostring end" <<<"$json"
}
resolve_directory_ids() {
local users_json patients_json
users_json="$(json_get "$API_URL/api/v1/users?role=DATA_ENTRY_CLERK" "$INTAKE_TOKEN")"
patients_json="$(json_get "$API_URL/api/v1/patients/search?q=Patient" "$INTAKE_TOKEN")"
ENTRY_CLERK1_ID="$(jq -er '.data[0].id // empty' <<<"$users_json" 2>/dev/null || true)"
PATIENT1_ID="$(jq -er '.data[0].id // empty' <<<"$patients_json" 2>/dev/null || true)"
if [[ -n "$ENTRY_CLERK1_ID" ]]; then
pass "resolved entry clerk ID for auto-assign test"
else
fail "resolved entry clerk ID for auto-assign test"
fi
if [[ -n "$PATIENT1_ID" ]]; then
pass "resolved patient ID for patient-linked cover sheet test"
else
fail "resolved patient ID for patient-linked cover sheet test"
fi
}
extract_error_code() {
local json="$1"
jq -er '.error.code // empty' <<<"$json" 2>/dev/null || true
}
assert_api_reachable() {
local code
code="$(http_code "$API_URL/swagger/index.html" || true)"
if [[ "$code" != "200" ]]; then
log "ERROR: API not reachable at $API_URL (HTTP $code)."
log "Start infrastructure with: docker compose up -d"
log "Apply migrations with: dotnet ef database update --project VigilCareRecordsAPI"
log "Start API with: dotnet run --project VigilCareRecordsAPI"
exit 1
fi
}
compose_service_running() {
local service="$1"
"${COMPOSE[@]}" ps --status running --services 2>/dev/null | grep -qx "$service"
}
psql_available() {
[[ "$SKIP_DB_CHECKS" == "1" ]] && return 1
compose_service_running postgres && return 0
command -v psql >/dev/null 2>&1 && return 0
return 1
}
psql_query() {
if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
return 1
fi
if compose_service_running postgres; then
"${COMPOSE[@]}" exec -T postgres \
psql -U "$PG_USER" -d "$PG_DB" -Atqc "$1"
elif command -v psql >/dev/null 2>&1; then
PGPASSWORD="$PG_PASSWORD" psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -Atqc "$1"
else
return 1
fi
}
upload_with_cover_sheet() {
local token="$1"
local code="$2"
local pdf="$3"
curl -sS -X POST "$API_URL/api/v1/digitization-batches" \
-H "Authorization: Bearer $token" \
-F "file=@${pdf};type=application/pdf" \
-F "coverSheetCode=$code"
}
upload_with_cover_sheet_status() {
local token="$1"
local code="$2"
local pdf="$3"
local body_file http_code_val
body_file="$(mktemp)"
http_code_val="$(curl -sS -o "$body_file" -w '%{http_code}' -X POST "$API_URL/api/v1/digitization-batches" \
-H "Authorization: Bearer $token" \
-F "file=@${pdf};type=application/pdf" \
-F "coverSheetCode=$code")"
cat "$body_file"
rm -f "$body_file"
printf '\n__HTTP_STATUS__:%s' "$http_code_val"
}
generate_cover_sheets() {
local token="$1"
local body="$2"
json_post "$API_URL/api/v1/cover-sheets/generate" "$body" "$token"
}
test_authentication() {
section "0. Authentication"
local intake_json admin_json entry_json
intake_json="$(login intake1)"
admin_json="$(login admin1)"
entry_json="$(login entry1)"
INTAKE_TOKEN="$(extract_data_field "$intake_json" token)"
ADMIN_TOKEN="$(extract_data_field "$admin_json" token)"
ENTRY_TOKEN="$(extract_data_field "$entry_json" token)"
if [[ -z "$INTAKE_TOKEN" ]]; then
log "ERROR: intake1 login failed."
log "Response: ${intake_json:-<empty>}"
exit 1
fi
if [[ -n "$INTAKE_TOKEN" ]]; then
pass "intake1 login returns JWT"
else
fail "intake1 login returns JWT"
fi
if [[ -n "$ADMIN_TOKEN" ]]; then
pass "admin1 login returns JWT"
else
fail "admin1 login returns JWT"
fi
if [[ -n "$ENTRY_TOKEN" ]]; then
pass "entry1 login returns JWT"
else
fail "entry1 login returns JWT"
fi
resolve_directory_ids
}
test_database_schema() {
section "1. Database — cover_sheets table"
if ! psql_available; then
log " SKIP: PostgreSQL checks (set VIGILCARE_SKIP_DB_CHECKS=0 and start postgres)"
return
fi
local table_exists index_code index_unused
table_exists="$(psql_query "
SELECT count(*)
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cover_sheets';
")"
index_code="$(psql_query "
SELECT count(*)
FROM pg_indexes
WHERE tablename = 'cover_sheets' AND indexname = 'ix_cover_sheets_code';
")"
index_unused="$(psql_query "
SELECT count(*)
FROM pg_indexes
WHERE tablename = 'cover_sheets' AND indexname = 'ix_cover_sheets_unused';
")"
if [[ "$table_exists" == "1" ]]; then
pass "cover_sheets table exists"
else
fail "cover_sheets table exists"
fi
if [[ "$index_code" == "1" ]]; then
pass "unique index ix_cover_sheets_code exists"
else
fail "unique index ix_cover_sheets_code exists"
fi
if [[ "$index_unused" == "1" ]]; then
pass "filtered index ix_cover_sheets_unused exists"
else
fail "filtered index ix_cover_sheets_unused exists"
fi
}
test_cover_sheet_generation() {
section "2. Cover sheet generation (plan §1)"
local gen_json count unique_count bad_format
gen_json="$(generate_cover_sheets "$INTAKE_TOKEN" \
'{"count":5,"batchType":"VITALS_SHEET","track":"BACKFILL"}')"
if [[ "$(jq -er '.success' <<<"$gen_json")" == "true" ]]; then
pass "POST /cover-sheets/generate returns success for intake1"
else
fail "POST /cover-sheets/generate returns success for intake1"
log " response: $gen_json"
return
fi
count="$(jq '.data | length' <<<"$gen_json")"
unique_count="$(jq '[.data[].code] | unique | length' <<<"$gen_json")"
if [[ "$count" == "5" && "$unique_count" == "5" ]]; then
pass "generate creates 5 cover sheets with unique codes"
else
fail "generate creates 5 cover sheets with unique codes (count=$count unique=$unique_count)"
fi
bad_format="$(jq -r '[.data[].code | test("^VCR-CS-[0-9A-F]{8}$")] | all' <<<"$gen_json")"
if [[ "$bad_format" == "true" ]]; then
pass "all codes match VCR-CS-{8-hex} format"
else
fail "all codes match VCR-CS-{8-hex} format"
fi
UPLOAD_CODE="$(jq -r '.data[0].code' <<<"$gen_json")"
UPLOAD_COVER_SHEET_ID="$(jq -r '.data[0].id' <<<"$gen_json")"
PDF_SHEET_IDS=($(jq -r '.data[0].id, .data[1].id' <<<"$gen_json"))
local entry_json entry_token entry_code
entry_json="$(login entry1)"
entry_token="$(extract_data_field "$entry_json" token)"
entry_code="$(http_code -X POST "$API_URL/api/v1/cover-sheets/generate" \
-H "Authorization: Bearer $entry_token" \
-H 'Content-Type: application/json' \
-d '{"count":1,"batchType":"VITALS_SHEET","track":"BACKFILL"}')"
if [[ "$entry_code" == "403" ]]; then
pass "generate denied for DATA_ENTRY_CLERK (403)"
else
fail "generate denied for DATA_ENTRY_CLERK (403) — got HTTP $entry_code"
fi
local admin_json
admin_json="$(generate_cover_sheets "$ADMIN_TOKEN" \
'{"count":1,"batchType":"LAB_RESULTS","track":"BACKFILL"}')"
if [[ "$(jq -er '.success' <<<"$admin_json")" == "true" ]]; then
pass "generate allowed for ADMINISTRATOR"
else
fail "generate allowed for ADMINISTRATOR"
fi
}
test_cover_sheet_lookup_and_list() {
section "3. Cover sheet lookup and list (plan §2)"
if [[ -z "$UPLOAD_CODE" ]]; then
fail "lookup requires generated cover sheet code"
return
fi
local lookup_json is_used batch_type
lookup_json="$(json_get "$API_URL/api/v1/cover-sheets/lookup/$UPLOAD_CODE" "$INTAKE_TOKEN")"
if [[ "$(jq -er '.success' <<<"$lookup_json")" == "true" ]]; then
pass "GET /cover-sheets/lookup/{code} returns success"
else
fail "GET /cover-sheets/lookup/{code} returns success"
return
fi
is_used="$(extract_bool_field "$lookup_json" isUsed)"
batch_type="$(extract_data_field "$lookup_json" batchType)"
if [[ "$is_used" == "false" ]]; then
pass "lookup shows isUsed=false before upload"
else
fail "lookup shows isUsed=false before upload (isUsed=$is_used)"
fi
if [[ "$batch_type" == "VITALS_SHEET" ]]; then
pass "lookup returns batchType VITALS_SHEET"
else
fail "lookup returns batchType VITALS_SHEET (got $batch_type)"
fi
local unknown_json unknown_code
unknown_json="$(json_get "$API_URL/api/v1/cover-sheets/lookup/VCR-CS-DEADBEEF" "$INTAKE_TOKEN")"
unknown_code="$(extract_error_code "$unknown_json")"
if [[ "$unknown_code" == "COVER_SHEET_NOT_FOUND" ]]; then
pass "lookup unknown code returns COVER_SHEET_NOT_FOUND"
else
fail "lookup unknown code returns COVER_SHEET_NOT_FOUND (got ${unknown_code:-<none>})"
fi
local list_json unused_count
list_json="$(json_get "$API_URL/api/v1/cover-sheets?isUsed=false&page=1&pageSize=20" "$INTAKE_TOKEN")"
unused_count="$(jq '.data | length' <<<"$list_json")"
if [[ "$(jq -er '.success' <<<"$list_json")" == "true" && "$unused_count" -ge 5 ]]; then
pass "GET /cover-sheets?isUsed=false lists unused sheets"
else
fail "GET /cover-sheets?isUsed=false lists unused sheets (count=$unused_count)"
fi
}
test_pdf_generation() {
section "4. Cover sheet PDF generation (plan Step 5)"
if [[ -z "$UPLOAD_COVER_SHEET_ID" || ${#PDF_SHEET_IDS[@]} -lt 2 ]]; then
fail "PDF tests require generated cover sheet IDs"
return
fi
local pdf_tmp headers_file pdf_code content_type pdf_header
pdf_tmp="$(mktemp)"
headers_file="$(mktemp)"
pdf_code="$(curl -sS -D "$headers_file" -o "$pdf_tmp" -w '%{http_code}' -X POST \
"$API_URL/api/v1/cover-sheets/${UPLOAD_COVER_SHEET_ID}/pdf" \
-H "Authorization: Bearer $INTAKE_TOKEN")"
content_type="$(awk -F': ' 'tolower($1)=="content-type"{print $2}' "$headers_file" | tr -d '\r' | head -1)"
rm -f "$headers_file"
if [[ "$pdf_code" == "200" ]]; then
pass "POST /cover-sheets/{id}/pdf returns 200"
else
fail "POST /cover-sheets/{id}/pdf returns 200 (got HTTP $pdf_code)"
fi
if [[ "$content_type" == application/pdf* ]]; then
pass "single PDF response Content-Type is application/pdf"
else
fail "single PDF response Content-Type is application/pdf (got ${content_type:-<none>})"
fi
pdf_header="$(head -c 8 "$pdf_tmp" || true)"
if [[ "$pdf_header" == %PDF-1.* ]]; then
pass "single PDF body starts with %PDF header"
else
fail "single PDF body starts with %PDF header"
fi
if grep -aq "$UPLOAD_CODE" "$pdf_tmp" 2>/dev/null; then
pass "single PDF embeds cover sheet code text"
else
fail "single PDF embeds cover sheet code text"
fi
rm -f "$pdf_tmp"
local batch_pdf_tmp batch_pdf_code
batch_pdf_tmp="$(mktemp)"
batch_pdf_code="$(curl -sS -o "$batch_pdf_tmp" -w '%{http_code}' -X POST \
"$API_URL/api/v1/cover-sheets/batch-pdf" \
-H "Authorization: Bearer $INTAKE_TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"coverSheetIds\":[\"${PDF_SHEET_IDS[0]}\",\"${PDF_SHEET_IDS[1]}\"]}")"
if [[ "$batch_pdf_code" == "200" ]]; then
pass "POST /cover-sheets/batch-pdf returns 200"
else
fail "POST /cover-sheets/batch-pdf returns 200 (got HTTP $batch_pdf_code)"
fi
if grep -aq '/Count 2' "$batch_pdf_tmp" 2>/dev/null || strings "$batch_pdf_tmp" | grep -q '/Count 2'; then
pass "batch PDF contains two pages (/Count 2)"
else
fail "batch PDF contains two pages (/Count 2)"
fi
rm -f "$batch_pdf_tmp"
}
test_barcode_assisted_upload() {
section "5. Barcode-assisted upload (plan §3)"
if [[ -z "$UPLOAD_CODE" ]]; then
fail "barcode upload requires generated cover sheet code"
return
fi
local pdf upload_json batch_type track status entered_by
pdf="$(unique_pdf_path upload)"
upload_json="$(upload_with_cover_sheet "$INTAKE_TOKEN" "$UPLOAD_CODE" "$pdf")"
rm -f "$pdf"
if [[ "$(jq -er '.success' <<<"$upload_json")" == "true" ]]; then
pass "POST /digitization-batches with coverSheetCode creates batch"
else
fail "POST /digitization-batches with coverSheetCode creates batch"
log " response: $upload_json"
return
fi
batch_type="$(extract_data_field "$upload_json" batchType)"
track="$(extract_data_field "$upload_json" track)"
status="$(extract_data_field "$upload_json" status)"
UPLOAD_BATCH_ID="$(extract_data_field "$upload_json" id)"
if [[ "$batch_type" == "VITALS_SHEET" && "$track" == "BACKFILL" ]]; then
pass "batch inherits batchType and track from cover sheet"
else
fail "batch inherits batchType and track from cover sheet (type=$batch_type track=$track)"
fi
local redeemed_json redeemed_used redeemed_batch_id
redeemed_json="$(json_get "$API_URL/api/v1/cover-sheets/lookup/$UPLOAD_CODE" "$INTAKE_TOKEN")"
redeemed_used="$(extract_bool_field "$redeemed_json" isUsed)"
redeemed_batch_id="$(extract_data_field "$redeemed_json" batchId)"
if [[ "$redeemed_used" == "true" && "$redeemed_batch_id" == "$UPLOAD_BATCH_ID" ]]; then
pass "cover sheet redeemed and linked to batch after upload"
else
fail "cover sheet redeemed and linked to batch after upload"
fi
if psql_available && [[ -n "$UPLOAD_COVER_SHEET_ID" ]]; then
local db_used db_batch
db_used="$(psql_query "
SELECT is_used FROM cover_sheets WHERE id = '$UPLOAD_COVER_SHEET_ID';
")"
db_batch="$(psql_query "
SELECT batch_id FROM cover_sheets WHERE id = '$UPLOAD_COVER_SHEET_ID';
")"
if [[ "$db_used" == "t" && "$db_batch" == "$UPLOAD_BATCH_ID" ]]; then
pass "database row shows is_used=true with batch_id"
else
fail "database row shows is_used=true with batch_id"
fi
fi
}
test_cover_sheet_reuse_and_unknown() {
section "6. Reuse prevention and unknown code (plan §4)"
if [[ -z "$UPLOAD_CODE" ]]; then
fail "reuse test requires uploaded cover sheet code"
return
fi
local pdf reuse_response reuse_status reuse_code unknown_response unknown_status unknown_code
pdf="$(unique_pdf_path reuse)"
reuse_response="$(upload_with_cover_sheet_status "$INTAKE_TOKEN" "$UPLOAD_CODE" "$pdf")"
rm -f "$pdf"
reuse_status="${reuse_response##*__HTTP_STATUS__:}"
reuse_response="${reuse_response%$'\n'__HTTP_STATUS__:*}"
reuse_code="$(extract_error_code "$reuse_response")"
if [[ "$reuse_status" == "409" && "$reuse_code" == "COVER_SHEET_ALREADY_USED" ]]; then
pass "reused cover sheet code returns 409 COVER_SHEET_ALREADY_USED"
else
fail "reused cover sheet code returns 409 COVER_SHEET_ALREADY_USED (HTTP $reuse_status code=${reuse_code:-<none>})"
fi
pdf="$(unique_pdf_path unknown)"
unknown_response="$(upload_with_cover_sheet_status "$INTAKE_TOKEN" "VCR-CS-DEADBEEF" "$pdf")"
rm -f "$pdf"
unknown_status="${unknown_response##*__HTTP_STATUS__:}"
unknown_response="${unknown_response%$'\n'__HTTP_STATUS__:*}"
unknown_code="$(extract_error_code "$unknown_response")"
if [[ "$unknown_status" == "404" && "$unknown_code" == "COVER_SHEET_NOT_FOUND" ]]; then
pass "unknown cover sheet code on upload returns 404 COVER_SHEET_NOT_FOUND"
else
fail "unknown cover sheet code on upload returns 404 COVER_SHEET_NOT_FOUND (HTTP $unknown_status code=${unknown_code:-<none>})"
fi
}
test_auto_assign_cover_sheet() {
section "7. Pre-assigned cover sheet auto-assigns batch"
if [[ -z "$ENTRY_CLERK1_ID" ]]; then
fail "auto-assign test skipped — no entry clerk ID"
return
fi
local gen_json assign_code pdf upload_json status entered_by
gen_json="$(generate_cover_sheets "$INTAKE_TOKEN" \
"{\"count\":1,\"batchType\":\"LAB_RESULTS\",\"track\":\"BACKFILL\",\"assignToUserId\":\"$ENTRY_CLERK1_ID\"}")"
if [[ "$(jq -er '.success' <<<"$gen_json")" != "true" ]]; then
fail "generate pre-assigned cover sheet"
return
fi
assign_code="$(jq -r '.data[0].code' <<<"$gen_json")"
pdf="$(unique_pdf_path assign)"
upload_json="$(upload_with_cover_sheet "$INTAKE_TOKEN" "$assign_code" "$pdf")"
rm -f "$pdf"
status="$(extract_data_field "$upload_json" status)"
entered_by="$(extract_data_field "$upload_json" enteredByUserId)"
if [[ "$status" == "IN_ENTRY" ]]; then
pass "pre-assigned upload transitions batch to IN_ENTRY"
else
fail "pre-assigned upload transitions batch to IN_ENTRY (status=$status)"
fi
if [[ "$entered_by" == "$ENTRY_CLERK1_ID" ]]; then
pass "pre-assigned upload sets enteredByUserId to entry clerk"
else
fail "pre-assigned upload sets enteredByUserId to entry clerk (got $entered_by)"
fi
}
test_patient_linked_cover_sheet() {
section "8. Patient-linked cover sheet populates batch patient"
if [[ -z "$PATIENT1_ID" ]]; then
fail "patient-linked test skipped — no patient ID"
return
fi
local gen_json patient_code pdf upload_json patient_id
gen_json="$(generate_cover_sheets "$INTAKE_TOKEN" \
"{\"count\":1,\"batchType\":\"MEDICATION_LIST\",\"track\":\"BACKFILL\",\"patientId\":\"$PATIENT1_ID\"}")"
if [[ "$(jq -er '.success' <<<"$gen_json")" != "true" ]]; then
fail "generate patient-linked cover sheet"
return
fi
patient_code="$(jq -r '.data[0].code' <<<"$gen_json")"
pdf="$(unique_pdf_path patient)"
upload_json="$(upload_with_cover_sheet "$INTAKE_TOKEN" "$patient_code" "$pdf")"
rm -f "$pdf"
patient_id="$(extract_data_field "$upload_json" patientId)"
if [[ "$patient_id" == "$PATIENT1_ID" ]]; then
pass "patient-linked cover sheet sets batch patientId"
else
fail "patient-linked cover sheet sets batch patientId (got $patient_id)"
fi
}
test_integration_tests() {
section "9. dotnet integration tests — CoverSheetBatchTests, CoverSheetPdfTests"
if [[ "$SKIP_TEST_CHECKS" == "1" ]]; then
log " SKIP: dotnet integration tests (VIGILCARE_SKIP_TEST_CHECKS=1)"
return
fi
if ! command -v dotnet >/dev/null 2>&1; then
log " SKIP: dotnet not installed"
return
fi
if dotnet test "$REPO_ROOT/VigilCareRecordsAPI.Tests/VigilCareRecordsAPI.Tests.csproj" \
--filter "FullyQualifiedName~CoverSheet" \
--no-restore >/tmp/vigilcare-p10-tests.log 2>&1; then
pass "CoverSheet integration tests passed"
else
fail "CoverSheet integration tests passed"
log " see /tmp/vigilcare-p10-tests.log"
fi
}
print_manual_ui_checklist() {
section "10. Manual Vue UI checks (plan §5)"
log " Login as intake1 → /cover-sheets"
log " - Generate 5 VITALS_SHEET covers"
log " - Print Cover Sheets opens PDF in new tab"
log " - Cover sheet list shows unused sheets"
log " Navigate to /intake"
log " - Scan/type a cover sheet code (Enter triggers lookup)"
log " - Lookup auto-fills batch type, track, patient"
log " - Upload with Cover Sheet marks sheet as Used with linked batch ID"
}
main() {
require_cmd curl
require_cmd jq
ensure_fixture_pdf
log "VigilCare Records — Phase 10 verification"
log "API: $API_URL"
assert_api_reachable
test_authentication
test_database_schema
test_cover_sheet_generation
test_cover_sheet_lookup_and_list
test_pdf_generation
test_barcode_assisted_upload
test_cover_sheet_reuse_and_unknown
test_auto_assign_cover_sheet
test_patient_linked_cover_sheet
test_integration_tests
print_manual_ui_checklist
log ""
log "Results: $PASS_COUNT passed, $FAIL_COUNT failed"
if (( FAIL_COUNT > 0 )); then
log "Failed checks:"
for item in "${FAILED_TESTS[@]}"; do
log " - $item"
done
exit 1
fi
log "All Phase 10 API verification checks passed."
log "Complete the manual Vue UI checklist above if not already done."
}
main "$@"