Initial commit: Canteen Asset Geolocation Tool v2
This commit is contained in:
Executable
+232
@@ -0,0 +1,232 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user