fix: ran script test for Schema, Authentication, Roles, Batch CRUD, MinIO Upload, and Status Machine
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
%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]>>endobj
|
||||
xref
|
||||
0 4
|
||||
0000000000 65535 f
|
||||
0000000009 00000 n
|
||||
0000000052 00000 n
|
||||
0000000101 00000 n
|
||||
trailer<</Size 4/Root 1 0 R>>
|
||||
startxref
|
||||
149
|
||||
%%EOF
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs Phase 1 verification checks from docs/plans/phase-1-plan.md.
|
||||
#
|
||||
# Prerequisites:
|
||||
# docker compose up -d (PostgreSQL + Redis; used via docker compose exec)
|
||||
# dotnet run --project VigilCareRecordsAPI
|
||||
#
|
||||
# PostgreSQL and Redis checks use host psql/redis-cli when installed, otherwise
|
||||
# docker compose exec against the running compose services.
|
||||
#
|
||||
# Environment overrides:
|
||||
# VIGILCARE_API_URL default: http://localhost:5217
|
||||
# VIGILCARE_COMPOSE_FILE default: <repo>/docker-compose.yml
|
||||
# VIGILCARE_REDIS_PORT default: 6383 (host redis-cli only)
|
||||
# VIGILCARE_PG_HOST default: localhost (host psql only)
|
||||
# VIGILCARE_PG_PORT default: 5437 (host psql only)
|
||||
# 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
|
||||
|
||||
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")
|
||||
REDIS_PORT="${VIGILCARE_REDIS_PORT:-6383}"
|
||||
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}"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
FAILED_TESTS=()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
command -v psql >/dev/null 2>&1 && return 0
|
||||
compose_service_running postgres
|
||||
}
|
||||
|
||||
redis_available() {
|
||||
command -v redis-cli >/dev/null 2>&1 && return 0
|
||||
compose_service_running redis
|
||||
}
|
||||
|
||||
psql_query() {
|
||||
if 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"
|
||||
elif compose_service_running postgres; then
|
||||
"${COMPOSE[@]}" exec -T postgres \
|
||||
psql -U "$PG_USER" -d "$PG_DB" -Atqc "$1"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
redis_get() {
|
||||
local key="$1"
|
||||
if command -v redis-cli >/dev/null 2>&1; then
|
||||
redis-cli -p "$REDIS_PORT" GET "$key"
|
||||
elif compose_service_running redis; then
|
||||
"${COMPOSE[@]}" exec -T redis redis-cli GET "$key"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
http_code() {
|
||||
curl -sS -o /dev/null -w '%{http_code}' "$@"
|
||||
}
|
||||
|
||||
json_post() {
|
||||
local url="$1"
|
||||
local body="$2"
|
||||
curl -sS -X POST "$url" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$body"
|
||||
}
|
||||
|
||||
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_error_code() {
|
||||
local json="$1"
|
||||
jq -er '.error.code // empty' <<<"$json"
|
||||
}
|
||||
|
||||
upload_batch() {
|
||||
local token="$1"
|
||||
local patient_id="${2:-}"
|
||||
local file_path="${3:-$FIXTURE_PDF}"
|
||||
|
||||
local -a form_args=(
|
||||
-F "file=@${file_path};type=application/pdf"
|
||||
-F "batchType=VITALS_SHEET"
|
||||
)
|
||||
if [[ -n "$patient_id" ]]; then
|
||||
form_args+=(-F "patientId=$patient_id")
|
||||
fi
|
||||
|
||||
curl -sS -X POST "$API_URL/api/v1/digitization-batches" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
"${form_args[@]}"
|
||||
}
|
||||
|
||||
assign_batch() {
|
||||
local token="$1"
|
||||
local batch_id="$2"
|
||||
local entry_clerk_id="$3"
|
||||
|
||||
curl -sS -X PATCH "$API_URL/api/v1/digitization-batches/$batch_id/assign" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"entryClerkUserId\":\"$entry_clerk_id\"}"
|
||||
}
|
||||
|
||||
decode_jwt_claim() {
|
||||
local token="$1"
|
||||
local claim="$2"
|
||||
local payload
|
||||
payload="$(printf '%s' "$token" | cut -d. -f2 | tr '_-' '/+')"
|
||||
local pad=$(( (4 - ${#payload} % 4) % 4 ))
|
||||
payload="${payload}$(printf '=%.0s' $(seq 1 "$pad"))"
|
||||
jq -er --arg claim "$claim" '.[$claim] // empty' <<<"$(printf '%s' "$payload" | base64 -d 2>/dev/null || printf '%s' "$payload" | base64 -d)"
|
||||
}
|
||||
|
||||
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 "Start API with: dotnet run --project VigilCareRecordsAPI"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_auth_roles_and_me() {
|
||||
section "4. JWT role claims and /auth/me"
|
||||
|
||||
local intake_json verifier_json me_json me_code
|
||||
intake_json="$(login intake1)"
|
||||
if [[ "$(jq -er '.success' <<<"$intake_json")" != "true" ]]; then
|
||||
fail "intake1 login succeeds"
|
||||
return
|
||||
fi
|
||||
pass "intake1 login succeeds"
|
||||
|
||||
local intake_token intake_refresh intake_role
|
||||
intake_token="$(extract_data_field "$intake_json" token)"
|
||||
intake_refresh="$(extract_data_field "$intake_json" refreshToken)"
|
||||
intake_role="$(extract_data_field "$intake_json" role)"
|
||||
|
||||
if [[ "$intake_role" == "INTAKE_CLERK" ]]; then
|
||||
pass "intake1 role claim is INTAKE_CLERK"
|
||||
else
|
||||
fail "intake1 role claim is INTAKE_CLERK (got: $intake_role)"
|
||||
fi
|
||||
|
||||
if [[ -n "$intake_refresh" ]]; then
|
||||
pass "intake1 login includes refreshToken"
|
||||
else
|
||||
fail "intake1 login includes refreshToken"
|
||||
fi
|
||||
|
||||
verifier_json="$(login verifier1)"
|
||||
local verifier_role
|
||||
verifier_role="$(extract_data_field "$verifier_json" role)"
|
||||
if [[ "$verifier_role" == "VERIFIER" ]]; then
|
||||
pass "verifier1 role claim is VERIFIER"
|
||||
else
|
||||
fail "verifier1 role claim is VERIFIER (got: $verifier_role)"
|
||||
fi
|
||||
|
||||
me_code="$(http_code "$API_URL/api/v1/auth/me")"
|
||||
if [[ "$me_code" == "401" ]]; then
|
||||
pass "GET /auth/me without token returns 401"
|
||||
else
|
||||
fail "GET /auth/me without token returns 401 (got: $me_code)"
|
||||
fi
|
||||
|
||||
me_json="$(curl -sS "$API_URL/api/v1/auth/me" -H "Authorization: Bearer $intake_token")"
|
||||
local me_role me_username
|
||||
me_role="$(extract_data_field "$me_json" role)"
|
||||
me_username="$(extract_data_field "$me_json" username)"
|
||||
if [[ "$me_role" == "INTAKE_CLERK" && "$me_username" == "intake1" ]]; then
|
||||
pass "GET /auth/me returns current user with role"
|
||||
else
|
||||
fail "GET /auth/me returns current user with role"
|
||||
fi
|
||||
|
||||
local jwt_role
|
||||
jwt_role="$(decode_jwt_claim "$intake_token" "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" 2>/dev/null || true)"
|
||||
if [[ -z "$jwt_role" ]]; then
|
||||
jwt_role="$(decode_jwt_claim "$intake_token" role 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ "$jwt_role" == "INTAKE_CLERK" ]]; then
|
||||
pass "JWT access token contains INTAKE_CLERK role claim"
|
||||
else
|
||||
fail "JWT access token contains INTAKE_CLERK role claim (got: ${jwt_role:-<none>})"
|
||||
fi
|
||||
}
|
||||
|
||||
test_refresh_logout_and_audit() {
|
||||
section "4b. Refresh rotation, logout revocation, auth audit events"
|
||||
|
||||
local login_json refresh_token refresh_json new_refresh logout_code reuse_code
|
||||
login_json="$(login intake1)"
|
||||
refresh_token="$(extract_data_field "$login_json" refreshToken)"
|
||||
|
||||
refresh_json="$(json_post "$API_URL/api/v1/auth/refresh" "{\"refreshToken\":\"$refresh_token\"}")"
|
||||
if [[ "$(jq -er '.success' <<<"$refresh_json")" == "true" ]]; then
|
||||
pass "POST /auth/refresh returns new access token"
|
||||
else
|
||||
fail "POST /auth/refresh returns new access token"
|
||||
return
|
||||
fi
|
||||
|
||||
new_refresh="$(extract_data_field "$refresh_json" refreshToken)"
|
||||
if [[ -n "$new_refresh" && "$new_refresh" != "$refresh_token" ]]; then
|
||||
pass "POST /auth/refresh rotates refresh token"
|
||||
else
|
||||
fail "POST /auth/refresh rotates refresh token"
|
||||
fi
|
||||
|
||||
logout_code="$(http_code -X POST "$API_URL/api/v1/auth/logout" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"refreshToken\":\"$new_refresh\"}")"
|
||||
if [[ "$logout_code" == "204" ]]; then
|
||||
pass "POST /auth/logout returns 204"
|
||||
else
|
||||
fail "POST /auth/logout returns 204 (got: $logout_code)"
|
||||
fi
|
||||
|
||||
reuse_code="$(http_code -X POST "$API_URL/api/v1/auth/refresh" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"refreshToken\":\"$new_refresh\"}")"
|
||||
if [[ "$reuse_code" == "422" ]]; then
|
||||
pass "reused refresh token after logout returns 422"
|
||||
else
|
||||
fail "reused refresh token after logout returns 422 (got: $reuse_code)"
|
||||
fi
|
||||
|
||||
if ! psql_available; then
|
||||
if [[ "$SKIP_DB_CHECKS" == "1" ]]; then
|
||||
log " SKIP: PostgreSQL auth audit event checks (VIGILCARE_SKIP_DB_CHECKS=1)"
|
||||
else
|
||||
log " SKIP: PostgreSQL auth audit event checks (postgres not reachable)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
local audit_rows
|
||||
audit_rows="$(psql_query "SELECT event_type FROM auth_audit_events ORDER BY occurred_at DESC LIMIT 10;")"
|
||||
if grep -q 'TOKEN_REFRESHED' <<<"$audit_rows" && grep -q 'USER_LOGOUT' <<<"$audit_rows"; then
|
||||
pass "auth audit events include TOKEN_REFRESHED and USER_LOGOUT"
|
||||
else
|
||||
fail "auth audit events include TOKEN_REFRESHED and USER_LOGOUT"
|
||||
fi
|
||||
}
|
||||
|
||||
test_upload_and_presigned_url() {
|
||||
section "3. Upload batch and presigned document URL"
|
||||
|
||||
local intake_json intake_token upload_json batch_id status document_url head_code sha256
|
||||
intake_json="$(login intake1)"
|
||||
intake_token="$(extract_data_field "$intake_json" token)"
|
||||
|
||||
upload_json="$(upload_batch "$intake_token")"
|
||||
local upload_code
|
||||
upload_code="$(http_code -X POST "$API_URL/api/v1/digitization-batches" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-F "file=@${FIXTURE_PDF};type=application/pdf" \
|
||||
-F "batchType=VITALS_SHEET")"
|
||||
|
||||
if [[ "$upload_code" == "201" ]]; then
|
||||
pass "upload creates batch (201)"
|
||||
else
|
||||
fail "upload creates batch (201) (got: $upload_code)"
|
||||
return
|
||||
fi
|
||||
|
||||
batch_id="$(extract_data_field "$upload_json" id)"
|
||||
status="$(extract_data_field "$upload_json" status)"
|
||||
if [[ "$status" == "UPLOADED" ]]; then
|
||||
pass "uploaded batch status is UPLOADED"
|
||||
else
|
||||
fail "uploaded batch status is UPLOADED (got: $status)"
|
||||
fi
|
||||
|
||||
if psql_available; then
|
||||
sha256="$(psql_query "SELECT document_sha256 FROM digitization_batches WHERE id = '$batch_id';")"
|
||||
if [[ -n "$sha256" && ${#sha256} -eq 64 ]]; then
|
||||
pass "batch persisted with SHA-256 document hash"
|
||||
else
|
||||
fail "batch persisted with SHA-256 document hash"
|
||||
fi
|
||||
|
||||
local object_count
|
||||
object_count="$(psql_query "SELECT COUNT(*) FROM scanned_documents WHERE batch_id = '$batch_id';")"
|
||||
if [[ "$object_count" == "1" ]]; then
|
||||
pass "scanned document metadata persisted for batch"
|
||||
else
|
||||
fail "scanned document metadata persisted for batch"
|
||||
fi
|
||||
fi
|
||||
|
||||
local get_json
|
||||
get_json="$(curl -sS "$API_URL/api/v1/digitization-batches/$batch_id" \
|
||||
-H "Authorization: Bearer $intake_token")"
|
||||
document_url="$(extract_data_field "$get_json" documentUrl)"
|
||||
if [[ -n "$document_url" && "$document_url" == http* ]]; then
|
||||
pass "GET batch returns presigned documentUrl"
|
||||
else
|
||||
fail "GET batch returns presigned documentUrl"
|
||||
return
|
||||
fi
|
||||
|
||||
head_code="$(curl -sS -o /dev/null -w '%{http_code}' -I "$document_url" || true)"
|
||||
if [[ "$head_code" == "200" || "$head_code" == "403" ]]; then
|
||||
# MinIO may answer HEAD differently depending on signature params; GET is authoritative.
|
||||
head_code="$(curl -sS -o /dev/null -w '%{http_code}' "$document_url" || true)"
|
||||
fi
|
||||
if [[ "$head_code" == "200" ]]; then
|
||||
pass "presigned URL returns uploaded document (HTTP 200)"
|
||||
else
|
||||
fail "presigned URL returns uploaded document (HTTP 200) (got: $head_code)"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
test_duplicate_detection() {
|
||||
section "2. SHA-256 duplicate detection"
|
||||
|
||||
local intake_json intake_token patient_a patient_b first_code second_code second_json cross_code
|
||||
intake_json="$(login intake1)"
|
||||
intake_token="$(extract_data_field "$intake_json" token)"
|
||||
patient_a="$(uuidgen | tr '[:upper:]' '[:lower:]')"
|
||||
patient_b="$(uuidgen | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
first_code="$(http_code -X POST "$API_URL/api/v1/digitization-batches" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-F "file=@${FIXTURE_PDF};type=application/pdf" \
|
||||
-F "batchType=VITALS_SHEET" \
|
||||
-F "patientId=$patient_a")"
|
||||
if [[ "$first_code" == "201" ]]; then
|
||||
pass "first upload for patient succeeds (201)"
|
||||
else
|
||||
fail "first upload for patient succeeds (201) (got: $first_code)"
|
||||
return
|
||||
fi
|
||||
|
||||
second_json="$(upload_batch "$intake_token" "$patient_a")"
|
||||
second_code="$(http_code -X POST "$API_URL/api/v1/digitization-batches" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-F "file=@${FIXTURE_PDF};type=application/pdf" \
|
||||
-F "batchType=VITALS_SHEET" \
|
||||
-F "patientId=$patient_a")"
|
||||
local duplicate_code
|
||||
duplicate_code="$(extract_error_code "$second_json")"
|
||||
if [[ "$second_code" == "409" && "$duplicate_code" == "DUPLICATE_DOCUMENT" ]]; then
|
||||
pass "duplicate upload for same patient returns 409 DUPLICATE_DOCUMENT"
|
||||
else
|
||||
fail "duplicate upload for same patient returns 409 DUPLICATE_DOCUMENT (http=$second_code code=$duplicate_code)"
|
||||
fi
|
||||
|
||||
cross_code="$(http_code -X POST "$API_URL/api/v1/digitization-batches" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-F "file=@${FIXTURE_PDF};type=application/pdf" \
|
||||
-F "batchType=VITALS_SHEET" \
|
||||
-F "patientId=$patient_b")"
|
||||
if [[ "$cross_code" == "201" ]]; then
|
||||
pass "same file for different patient succeeds (201)"
|
||||
else
|
||||
fail "same file for different patient succeeds (201) (got: $cross_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
test_illegal_status_transition() {
|
||||
section "1. Status machine — illegal transition returns 409"
|
||||
|
||||
local intake_json intake_token upload_json batch_id assign_json assign_code error_code
|
||||
intake_json="$(login intake1)"
|
||||
intake_token="$(extract_data_field "$intake_json" token)"
|
||||
upload_json="$(upload_batch "$intake_token")"
|
||||
batch_id="$(extract_data_field "$upload_json" id)"
|
||||
|
||||
if ! psql_available; then
|
||||
log " SKIP: ILLEGAL_STATUS_TRANSITION check requires PostgreSQL (host psql or docker compose postgres)"
|
||||
return
|
||||
fi
|
||||
|
||||
psql_query "UPDATE digitization_batches SET status = 'IN_ENTRY' WHERE id = '$batch_id';" >/dev/null
|
||||
|
||||
local entry_json entry_id
|
||||
entry_json="$(login entry1)"
|
||||
entry_id="$(extract_data_field "$entry_json" userId)"
|
||||
|
||||
assign_json="$(assign_batch "$intake_token" "$batch_id" "$entry_id")"
|
||||
assign_code="$(http_code -X PATCH "$API_URL/api/v1/digitization-batches/$batch_id/assign" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"entryClerkUserId\":\"$entry_id\"}")"
|
||||
error_code="$(extract_error_code "$assign_json")"
|
||||
|
||||
if [[ "$assign_code" == "409" && "$error_code" == "ILLEGAL_STATUS_TRANSITION" ]]; then
|
||||
pass "assign on non-uploaded batch returns 409 ILLEGAL_STATUS_TRANSITION"
|
||||
else
|
||||
fail "assign on non-uploaded batch returns 409 ILLEGAL_STATUS_TRANSITION (http=$assign_code code=$error_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
test_redis_assignment_lock() {
|
||||
section "5. Redis assignment lock"
|
||||
|
||||
if ! redis_available; then
|
||||
log " SKIP: Redis assignment lock check requires Redis (host redis-cli or docker compose redis)"
|
||||
return
|
||||
fi
|
||||
|
||||
local intake_json intake_token upload_json batch_id
|
||||
local entry1_json entry2_json entry1_id entry2_id
|
||||
local first_assign second_assign second_code second_error redis_value
|
||||
|
||||
intake_json="$(login intake1)"
|
||||
intake_token="$(extract_data_field "$intake_json" token)"
|
||||
upload_json="$(upload_batch "$intake_token")"
|
||||
batch_id="$(extract_data_field "$upload_json" id)"
|
||||
|
||||
entry1_json="$(login entry1)"
|
||||
entry2_json="$(login entry2)"
|
||||
entry1_id="$(extract_data_field "$entry1_json" userId)"
|
||||
entry2_id="$(extract_data_field "$entry2_json" userId)"
|
||||
|
||||
first_assign="$(assign_batch "$intake_token" "$batch_id" "$entry1_id")"
|
||||
if [[ "$(jq -er '.success' <<<"$first_assign")" == "true" ]]; then
|
||||
pass "assign batch to entry1 succeeds"
|
||||
else
|
||||
fail "assign batch to entry1 succeeds"
|
||||
return
|
||||
fi
|
||||
|
||||
second_assign="$(assign_batch "$intake_token" "$batch_id" "$entry2_id")"
|
||||
second_code="$(http_code -X PATCH "$API_URL/api/v1/digitization-batches/$batch_id/assign" \
|
||||
-H "Authorization: Bearer $intake_token" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"entryClerkUserId\":\"$entry2_id\"}")"
|
||||
second_error="$(extract_error_code "$second_assign")"
|
||||
if [[ "$second_code" == "409" && "$second_error" == "BATCH_ALREADY_ASSIGNED" ]]; then
|
||||
pass "assign same batch to entry2 returns 409 BATCH_ALREADY_ASSIGNED"
|
||||
else
|
||||
fail "assign same batch to entry2 returns 409 BATCH_ALREADY_ASSIGNED (http=$second_code code=$second_error)"
|
||||
fi
|
||||
|
||||
redis_value="$(redis_get "batch:assign:${batch_id}" 2>/dev/null || true)"
|
||||
if [[ "$redis_value" == "$entry1_id" ]]; then
|
||||
pass "Redis lock stores entry1 user ID"
|
||||
else
|
||||
fail "Redis lock stores entry1 user ID (got: ${redis_value:-<empty>})"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd curl
|
||||
require_cmd jq
|
||||
|
||||
if [[ ! -f "$FIXTURE_PDF" ]]; then
|
||||
log "ERROR: missing fixture PDF at $FIXTURE_PDF"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "VigilCare Records — Phase 1 verification"
|
||||
log "API: $API_URL"
|
||||
|
||||
assert_api_reachable
|
||||
|
||||
test_auth_roles_and_me
|
||||
test_refresh_logout_and_audit
|
||||
test_upload_and_presigned_url
|
||||
test_duplicate_detection
|
||||
test_illegal_status_transition
|
||||
test_redis_assignment_lock
|
||||
|
||||
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 verification checks passed."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user