Spaces:
Build error
Build error
| # | |
| # End-to-end API contract tests for PillChecker. | |
| # Validates every field the iOS app depends on. | |
| # | |
| # Usage: ./scripts/e2e-test.sh [BASE_URL] [API_KEY] | |
| # API_KEY is optional β if provided, sends X-API-Key header. | |
| set -euo pipefail | |
| BASE_URL="${1:-http://localhost:8000}" | |
| API_KEY="${2:-}" | |
| PASSED=0 | |
| FAILED=0 | |
| fail() { echo " FAIL: $1"; FAILED=$((FAILED + 1)); } | |
| pass() { echo " PASS: $1"; PASSED=$((PASSED + 1)); } | |
| assert_eq() { | |
| local desc="$1" actual="$2" expected="$3" | |
| if [ "$actual" = "$expected" ]; then pass "$desc" | |
| else fail "$desc (expected '$expected', got '$actual')"; fi | |
| } | |
| assert_not_empty() { | |
| local desc="$1" value="$2" | |
| if [ -n "$value" ] && [ "$value" != "null" ]; then pass "$desc" | |
| else fail "$desc (got empty or null)"; fi | |
| } | |
| assert_status() { | |
| local desc="$1" actual="$2" expected="$3" | |
| if [ "$actual" = "$expected" ]; then pass "$desc" | |
| else fail "$desc (expected HTTP $expected, got HTTP $actual)"; fi | |
| } | |
| # Build curl auth args | |
| AUTH_ARGS=() | |
| if [ -n "$API_KEY" ]; then | |
| AUTH_ARGS=(-H "X-API-Key: $API_KEY") | |
| fi | |
| # --- Wait for API --- | |
| echo "Waiting for API at $BASE_URL..." | |
| for i in $(seq 1 60); do | |
| if curl -sf "$BASE_URL/health" > /dev/null 2>&1; then | |
| echo "API ready." | |
| break | |
| fi | |
| if [ "$i" -eq 60 ]; then echo "ERROR: API not healthy."; exit 1; fi | |
| sleep 2 | |
| done | |
| # ============================================ | |
| # 1. Health endpoints (no auth required) | |
| # ============================================ | |
| echo "" | |
| echo "=== GET /health ===" | |
| HEALTH=$(curl -sf "$BASE_URL/health") | |
| assert_eq "status" "$(echo "$HEALTH" | jq -r '.status')" "ok" | |
| assert_not_empty "version" "$(echo "$HEALTH" | jq -r '.version')" | |
| assert_eq "ner_model_loaded" "$(echo "$HEALTH" | jq -r '.ner_model_loaded')" "true" | |
| echo "" | |
| echo "=== GET /health/data ===" | |
| DATA_HEALTH=$(curl -sf "$BASE_URL/health/data") | |
| assert_eq "status" "$(echo "$DATA_HEALTH" | jq -r '.status')" "ready" | |
| assert_eq "drugbank" "$(echo "$DATA_HEALTH" | jq -r '.drugbank')" "connected" | |
| # ============================================ | |
| # 2. POST /analyze β contract validation | |
| # ============================================ | |
| echo "" | |
| echo "=== POST /analyze (valid drug text) ===" | |
| ANALYZE=$(curl -sf -X POST "$BASE_URL/analyze" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"text": "Ibuprofen 400 mg tablets"}') | |
| # Verify all DrugResult fields exist (iOS DrugResult.swift expects these) | |
| assert_eq "drugs[0].name" "$(echo "$ANALYZE" | jq -r '.drugs[0].name')" "Ibuprofen" | |
| assert_not_empty "drugs[0].rxcui" "$(echo "$ANALYZE" | jq -r '.drugs[0].rxcui')" | |
| assert_not_empty "drugs[0].source" "$(echo "$ANALYZE" | jq -r '.drugs[0].source')" | |
| assert_not_empty "drugs[0].confidence" "$(echo "$ANALYZE" | jq -r '.drugs[0].confidence')" | |
| # dosage and form can be null, but keys must exist | |
| assert_eq "drugs[0] has dosage key" "$(echo "$ANALYZE" | jq 'has("drugs") and (.drugs[0] | has("dosage"))')" "true" | |
| assert_eq "drugs[0] has form key" "$(echo "$ANALYZE" | jq 'has("drugs") and (.drugs[0] | has("form"))')" "true" | |
| # raw_text must exist (iOS AnalyzeResponse uses CodingKey "raw_text") | |
| assert_not_empty "raw_text" "$(echo "$ANALYZE" | jq -r '.raw_text')" | |
| echo "" | |
| echo "=== POST /analyze (no drugs found) ===" | |
| # Requires score-filtering fix: common words like 'hello'/'world' matched | |
| # RxNorm brand names at score ~3.98, well below the 6.0 threshold. | |
| ANALYZE_EMPTY=$(curl -sf -X POST "$BASE_URL/analyze" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"text": "hello world"}') | |
| assert_eq "empty drugs array" "$(echo "$ANALYZE_EMPTY" | jq '.drugs | length')" "0" | |
| assert_not_empty "raw_text present" "$(echo "$ANALYZE_EMPTY" | jq -r '.raw_text')" | |
| echo "" | |
| echo "=== POST /analyze (empty text β 422) ===" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/analyze" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"text": ""}') | |
| assert_status "empty text rejected" "$STATUS" "422" | |
| # ============================================ | |
| # 3. POST /interactions β contract validation | |
| # ============================================ | |
| echo "" | |
| echo "=== POST /interactions (known interaction) ===" | |
| INTERACTIONS=$(curl -sf -X POST "$BASE_URL/interactions" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"drugs": ["ibuprofen", "warfarin"]}') | |
| assert_eq "safe" "$(echo "$INTERACTIONS" | jq -r '.safe')" "false" | |
| INTER_COUNT=$(echo "$INTERACTIONS" | jq '.interactions | length') | |
| if [ "$INTER_COUNT" -gt 0 ]; then pass "interactions array not empty ($INTER_COUNT)" | |
| else fail "interactions array is empty"; fi | |
| # Verify all InteractionResult fields (iOS InteractionResult.swift CodingKeys) | |
| assert_not_empty "drug_a" "$(echo "$INTERACTIONS" | jq -r '.interactions[0].drug_a')" | |
| assert_not_empty "drug_b" "$(echo "$INTERACTIONS" | jq -r '.interactions[0].drug_b')" | |
| assert_not_empty "severity" "$(echo "$INTERACTIONS" | jq -r '.interactions[0].severity')" | |
| assert_not_empty "description" "$(echo "$INTERACTIONS" | jq -r '.interactions[0].description')" | |
| assert_not_empty "management" "$(echo "$INTERACTIONS" | jq -r '.interactions[0].management')" | |
| echo "" | |
| echo "=== POST /interactions (safe pair) ===" | |
| SAFE=$(curl -sf -X POST "$BASE_URL/interactions" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"drugs": ["acetaminophen", "amoxicillin"]}') | |
| assert_eq "safe" "$(echo "$SAFE" | jq -r '.safe')" "true" | |
| assert_eq "no interactions" "$(echo "$SAFE" | jq '.interactions | length')" "0" | |
| echo "" | |
| echo "=== POST /interactions (too few drugs β 422) ===" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/interactions" \ | |
| -H "Content-Type: application/json" \ | |
| ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \ | |
| -d '{"drugs": ["only_one"]}') | |
| assert_status "single drug rejected" "$STATUS" "422" | |
| # ============================================ | |
| # 4. Auth tests (only when API_KEY is set) | |
| # ============================================ | |
| if [ -n "$API_KEY" ]; then | |
| echo "" | |
| echo "=== Auth: no key β 401 ===" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/analyze" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"text": "test"}') | |
| assert_status "no key rejected" "$STATUS" "401" | |
| echo "" | |
| echo "=== Auth: wrong key β 401 ===" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/analyze" \ | |
| -H "Content-Type: application/json" \ | |
| -H "X-API-Key: wrong-key-12345" \ | |
| -d '{"text": "test"}') | |
| assert_status "wrong key rejected" "$STATUS" "401" | |
| echo "" | |
| echo "=== Auth: health endpoints need no key ===" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health") | |
| assert_status "/health no auth needed" "$STATUS" "200" | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health/data") | |
| assert_status "/health/data no auth needed" "$STATUS" "200" | |
| fi | |
| # --- Summary --- | |
| echo "" | |
| echo "================================" | |
| echo "Results: $PASSED passed, $FAILED failed" | |
| echo "================================" | |
| [ "$FAILED" -eq 0 ] || exit 1 | |