feature: Barcode/QR Cover Sheet System

This commit is contained in:
voltsrage
2026-06-27 21:50:32 +08:00
parent 5db60f46eb
commit 756cff332c
43 changed files with 8203 additions and 21 deletions
+751
View File
@@ -0,0 +1,751 @@
#!/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 "$@"