Files
canteen-asset-tracker/smoke_test.sh
T

233 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# smoke_test.sh — End-to-end smoke test for Canteen Asset Tracker
# Exercises the full workflow: health, CRUD, check-in, stats, export.
#
# Usage: ./smoke_test.sh [base_url]
# default: https://localhost:8901
#
set -euo pipefail
BASE="${1:-https://localhost:8901}"
# Helper: curl with status code appended after response body
_curl() {
curl -sk -w '\n%{http_code}' "$@"
}
# Extract status code from last line, body from everything before it
_status() { echo "$1" | tail -1; }
_body() { echo "$1" | sed '$d'; }
PASS=0
FAIL=0
pass() { echo "$1"; PASS=$((PASS + 1)); }
fail() { echo "$1 (expected $2, got $3)"; FAIL=$((FAIL + 1)); }
echo "══════════════════════════════════════════════════"
echo " Canteen Asset Tracker — E2E Smoke Test"
echo " Target: $BASE"
echo "══════════════════════════════════════════════════"
echo ""
# ─── 1. Health check ────────────────────────────────────────────────────────
echo "── 1. Health Check ──"
RAW=$(_curl "$BASE/health")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
echo " $BODY"
[ "$STATUS" = "200" ] && pass "GET /health returns 200" || fail "GET /health" "200" "$STATUS"
[[ "$BODY" == *'"status":"ok"'* ]] && pass "health body has status:ok" || fail "health body" '"status":"ok"' "$BODY"
echo ""
# ─── 2. Create asset ────────────────────────────────────────────────────────
echo "── 2. Create Asset ──"
RAW=$(_curl -X POST "$BASE/api/assets" \
-H "Content-Type: application/json" \
-d '{"barcode":"SMOKE001","name":"Smoke Refrigerator","category":"Appliances","status":"active"}')
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
echo " $BODY"
[ "$STATUS" = "201" ] && pass "POST /api/assets returns 201" || fail "POST /api/assets" "201" "$STATUS"
ASSET_ID=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
[ -n "$ASSET_ID" ] && pass "asset created with id=$ASSET_ID" || fail "asset create" "got id" "no id"
echo ""
# ─── 3. Duplicate barcode (should 409) ──────────────────────────────────────
echo "── 3. Duplicate Barcode ──"
RAW=$(_curl -X POST "$BASE/api/assets" \
-H "Content-Type: application/json" \
-d '{"barcode":"SMOKE001","name":"Duplicate"}')
STATUS=$(_status "$RAW")
[ "$STATUS" = "409" ] && pass "duplicate barcode returns 409" || fail "duplicate barcode" "409" "$STATUS"
echo ""
# ─── 4. Lookup by barcode ──────────────────────────────────────────────────
echo "── 4. Lookup by Barcode ──"
RAW=$(_curl "$BASE/api/assets/search?barcode=SMOKE001")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/assets/search returns 200" || fail "GET /api/assets/search" "200" "$STATUS"
[[ "$BODY" == *"Smoke Refrigerator"* ]] && pass "search returns correct name" || fail "search name" "Smoke Refrigerator" "$BODY"
echo ""
# ─── 5. Get single asset ────────────────────────────────────────────────────
echo "── 5. Get Single Asset ──"
RAW=$(_curl "$BASE/api/assets/$ASSET_ID")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/assets/$ASSET_ID returns 200" || fail "GET /api/assets/$ASSET_ID" "200" "$STATUS"
echo ""
# ─── 6. Update asset ────────────────────────────────────────────────────────
echo "── 6. Update Asset ──"
RAW=$(_curl -X PUT "$BASE/api/assets/$ASSET_ID" \
-H "Content-Type: application/json" \
-d '{"name":"Smoke Refrigerator v2","status":"maintenance"}')
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "PUT /api/assets/$ASSET_ID returns 200" || fail "PUT" "200" "$STATUS"
[[ "$BODY" == *"Smoke Refrigerator v2"* ]] && pass "update applied name" || fail "update name" "Smoke Refrigerator v2" "$BODY"
[[ "$BODY" == *"maintenance"* ]] && pass "update applied status" || fail "update status" "maintenance" "$BODY"
echo ""
# ─── 7. List assets ─────────────────────────────────────────────────────────
echo "── 7. List Assets ──"
RAW=$(_curl "$BASE/api/assets")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/assets returns 200" || fail "GET /api/assets" "200" "$STATUS"
[[ "$BODY" == *"Smoke Refrigerator v2"* ]] && pass "list includes updated asset" || fail "list content" "Smoke Refrigerator v2" "$BODY"
echo ""
# ─── 8. Filter by category ──────────────────────────────────────────────────
echo "── 8. Filter by Category ──"
RAW=$(_curl "$BASE/api/assets?category=Appliances")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/assets?category=Appliances returns 200" || fail "filter category" "200" "$STATUS"
COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
[ "$COUNT" = "1" ] && pass "category filter returns 1 result" || fail "category filter count" "1" "$COUNT"
echo ""
# ─── 9. Create check-in ─────────────────────────────────────────────────────
echo "── 9. Create Check-in ──"
RAW=$(_curl -X POST "$BASE/api/checkins" \
-H "Content-Type: application/json" \
-d "{\"asset_id\":$ASSET_ID,\"latitude\":40.7128,\"longitude\":-74.006,\"accuracy\":15.0,\"notes\":\"Found in kitchen\"}")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "201" ] && pass "POST /api/checkins returns 201" || fail "POST /api/checkins" "201" "$STATUS"
CHECKIN_ID=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
[ -n "$CHECKIN_ID" ] && pass "check-in created with id=$CHECKIN_ID" || fail "checkin create" "got id" "no id"
echo ""
# ─── 10. Check-in without GPS (allowed) ─────────────────────────────────────
echo "── 10. Check-in Without GPS ──"
RAW=$(_curl -X POST "$BASE/api/checkins" \
-H "Content-Type: application/json" \
-d "{\"asset_id\":$ASSET_ID,\"notes\":\"Quick sighting\"}")
STATUS=$(_status "$RAW")
[ "$STATUS" = "201" ] && pass "check-in without GPS returns 201" || fail "check-in no GPS" "201" "$STATUS"
echo ""
# ─── 11. List check-ins for asset ───────────────────────────────────────────
echo "── 11. List Check-ins ──"
RAW=$(_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/checkins?asset_id= returns 200" || fail "list checkins" "200" "$STATUS"
COUNT=$(echo "$BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
[ "$COUNT" = "2" ] && pass "asset has 2 check-ins" || fail "checkin count" "2" "$COUNT"
echo ""
# ─── 12. Stats ──────────────────────────────────────────────────────────────
echo "── 12. Stats ──"
RAW=$(_curl "$BASE/api/stats")
BODY=$(_body "$RAW")
STATUS=$(_status "$RAW")
[ "$STATUS" = "200" ] && pass "GET /api/stats returns 200" || fail "stats" "200" "$STATUS"
[[ "$BODY" == *'"total_assets":1'* ]] && pass "stats: total_assets=1" || fail "stats total_assets" "1" "$BODY"
[[ "$BODY" == *'"total_checkins":2'* ]] && pass "stats: total_checkins=2" || fail "stats total_checkins" "2" "$BODY"
echo ""
# ─── 13. CSV Export ─────────────────────────────────────────────────────────
echo "── 13. CSV Export ──"
RAW=$(_curl "$BASE/api/export/assets")
CSV_BODY=$(_body "$RAW")
CSV_STATUS=$(_status "$RAW")
[ "$CSV_STATUS" = "200" ] && pass "GET /api/export/assets returns 200" || fail "export assets" "200" "$CSV_STATUS"
[[ "$CSV_BODY" == *"Smoke Refrigerator"* ]] && pass "CSV contains asset name" || fail "CSV content" "Smoke Refrigerator" "$CSV_BODY"
echo ""
RAW=$(_curl "$BASE/api/export/checkins?asset_id=$ASSET_ID")
CSV2_BODY=$(_body "$RAW")
CSV2_STATUS=$(_status "$RAW")
[ "$CSV2_STATUS" = "200" ] && pass "GET /api/export/checkins returns 200" || fail "export checkins" "200" "$CSV2_STATUS"
[[ "$CSV2_BODY" == *"kitchen"* ]] && pass "checkin CSV contains note" || fail "checkin CSV" "kitchen" "$CSV2_BODY"
echo ""
# ─── 14. 404 on non-existent asset ──────────────────────────────────────────
echo "── 14. 404 Handling ──"
NOTFOUND=$(_status "$(_curl -o /dev/null "$BASE/api/assets/99999")")
[ "$NOTFOUND" = "404" ] && pass "GET /api/assets/99999 returns 404" || fail "404 asset" "404" "$NOTFOUND"
NOTFOUND2=$(_status "$(_curl "$BASE/api/assets/search?barcode=NOEXIST")")
[ "$NOTFOUND2" = "404" ] && pass "barcode search 404 returns 404" || fail "search 404" "404" "$NOTFOUND2"
echo ""
# ─── 15. 422 on invalid input ───────────────────────────────────────────────
echo "── 15. Input Validation ──"
VAL1=$(_status "$(_curl -X POST "$BASE/api/assets" \
-H "Content-Type: application/json" \
-d '{"barcode":"","name":""}')")
[ "$VAL1" = "422" ] && pass "empty barcode/name returns 422" || fail "empty barcode" "422" "$VAL1"
VAL2=$(_status "$(_curl -X POST "$BASE/api/assets" \
-H "Content-Type: application/json" \
-d '{"barcode":" ","name":"Test"}')")
[ "$VAL2" = "422" ] && pass "whitespace-only barcode returns 422" || fail "whitespace barcode" "422" "$VAL2"
echo ""
# ─── 16. Delete asset ───────────────────────────────────────────────────────
echo "── 16. Delete Asset ──"
DEL=$(_status "$(_curl -X DELETE "$BASE/api/assets/$ASSET_ID")")
[ "$DEL" = "204" ] && pass "DELETE /api/assets/$ASSET_ID returns 204" || fail "DELETE" "204" "$DEL"
# Verify gone
DELVERIFY=$(_status "$(_curl "$BASE/api/assets/$ASSET_ID")")
[ "$DELVERIFY" = "404" ] && pass "deleted asset returns 404" || fail "deleted verify" "404" "$DELVERIFY"
# Verify check-ins cascade-deleted
RAW=$(_curl "$BASE/api/checkins?asset_id=$ASSET_ID")
CKC_BODY=$(_body "$RAW")
COUNT=$(echo "$CKC_BODY" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
[ "$COUNT" = "0" ] && pass "check-ins cascade-deleted (0 remaining)" || fail "cascade delete" "0" "$COUNT"
echo ""
# ─── Summary ────────────────────────────────────────────────────────────────
echo "══════════════════════════════════════════════════"
echo " Results: $PASS passed, $FAIL failed"
echo "══════════════════════════════════════════════════"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0