25da436bba
The test suite login as 'admin'/'changeme' but only 'shawn' was seeded.
Adding the admin user with SHA256('changeme') hash lets all 15 Playwright
tests reach the actual app pages instead of failing at login.
279 lines
14 KiB
Bash
Executable File
279 lines
14 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
|
|
#
|
|
# Auth: Set SMOKE_USER / SMOKE_PASS env vars, or SMOKE_TOKEN for a
|
|
# pre-acquired bearer token. Defaults: admin / changeme.
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
BASE="${1:-https://localhost:8901}"
|
|
SMOKE_USER="${SMOKE_USER:-}"
|
|
SMOKE_PASS="${SMOKE_PASS:-}"
|
|
TOKEN="${SMOKE_TOKEN:-}"
|
|
|
|
# Helper: raw curl with status appended
|
|
_curl() {
|
|
curl -sk -w '\n%{http_code}' "$@"
|
|
}
|
|
|
|
# Helper: curl with Bearer auth header (no-op if TOKEN empty)
|
|
_auth_curl() {
|
|
if [ -n "$TOKEN" ]; then
|
|
_curl -H "Authorization: Bearer $TOKEN" "$@"
|
|
else
|
|
_curl "$@"
|
|
fi
|
|
}
|
|
|
|
# Login and capture a bearer token
|
|
_login() {
|
|
local user="${SMOKE_USER:-admin}"
|
|
local pass="${SMOKE_PASS:-changeme}"
|
|
echo "── Auth: logging in as $user ──"
|
|
local RAW
|
|
RAW=$(_curl -X POST "$BASE/api/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"username\":\"$user\",\"password\":\"$pass\"}")
|
|
local BODY
|
|
BODY=$(_body "$RAW")
|
|
local STATUS
|
|
STATUS=$(_status "$RAW")
|
|
if [ "$STATUS" = "200" ]; then
|
|
TOKEN=$(echo "$BODY" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])" 2>/dev/null || echo "")
|
|
if [ -n "$TOKEN" ]; then
|
|
pass "login successful, got token ${TOKEN:0:10}..."
|
|
return 0
|
|
fi
|
|
fi
|
|
echo " ⚠ Login failed (HTTP $STATUS). API calls requiring auth will fail."
|
|
echo " Set SMOKE_USER / SMOKE_PASS env vars, or SMOKE_TOKEN for a direct token."
|
|
return 1
|
|
}
|
|
|
|
# Extract status code (last line) and body (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 ""
|
|
|
|
# ─── 0. Auth —────────────────────────────────────────────────────────────────
|
|
if [ -z "$TOKEN" ]; then
|
|
_login || true
|
|
fi
|
|
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=$(_auth_curl -X POST "$BASE/api/assets" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"machine_id":"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 machine_id (should 409) ─────────────────────────────────────
|
|
echo "── 3. Duplicate machine_id ──"
|
|
RAW=$(_auth_curl -X POST "$BASE/api/assets" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"machine_id":"SMOKE001","name":"Duplicate"}')
|
|
STATUS=$(_status "$RAW")
|
|
|
|
[ "$STATUS" = "409" ] && pass "duplicate machine_id returns 409" || fail "duplicate machine_id" "409" "$STATUS"
|
|
echo ""
|
|
|
|
# ─── 4. Lookup ──────────────────────────────────────────────────────────────
|
|
echo "── 4. Search ──"
|
|
RAW=$(_auth_curl "$BASE/api/assets/search?q=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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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=$(_auth_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 "$(_auth_curl -o /dev/null "$BASE/api/assets/99999")")
|
|
[ "$NOTFOUND" = "404" ] && pass "GET /api/assets/99999 returns 404" || fail "404 asset" "404" "$NOTFOUND"
|
|
|
|
NOTFOUND2=$(_status "$(_auth_curl "$BASE/api/assets/search?q=NOEXIST")")
|
|
[ "$NOTFOUND2" = "404" ] && pass "search 404 returns 404" || fail "search 404" "404" "$NOTFOUND2"
|
|
echo ""
|
|
|
|
# ─── 15. 422 on invalid input ───────────────────────────────────────────────
|
|
echo "── 15. Input Validation ──"
|
|
VAL1=$(_status "$(_auth_curl -X POST "$BASE/api/assets" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"machine_id":"","name":""}')")
|
|
[ "$VAL1" = "422" ] && pass "empty machine_id/name returns 422" || fail "empty fields" "422" "$VAL1"
|
|
|
|
VAL2=$(_status "$(_auth_curl -X POST "$BASE/api/assets" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"machine_id":" ","name":"Test"}')")
|
|
[ "$VAL2" = "422" ] && pass "whitespace-only machine_id returns 422" || fail "whitespace machine_id" "422" "$VAL2"
|
|
echo ""
|
|
|
|
# ─── 16. Delete asset ───────────────────────────────────────────────────────
|
|
echo "── 16. Delete Asset ──"
|
|
DEL=$(_status "$(_auth_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 "$(_auth_curl "$BASE/api/assets/$ASSET_ID")")
|
|
[ "$DELVERIFY" = "404" ] && pass "deleted asset returns 404" || fail "deleted verify" "404" "$DELVERIFY"
|
|
|
|
# Verify check-ins cascade-deleted
|
|
RAW=$(_auth_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
|