#!/usr/bin/env bash # Smoke test for a running Reading List server. curl only. # Usage: BASE_URL=http://localhost:7860 scripts/smoke.sh set -euo pipefail BASE_URL="${BASE_URL:-http://localhost:7860}" BASE_URL="${BASE_URL%/}" BODY_FILE="$(mktemp)" trap 'rm -f "$BODY_FILE"' EXIT STEP=0 CODE="" CTYPE="" BODY="" pass() { printf 'PASS %d %s\n' "$STEP" "$1"; } fail() { printf 'FAIL %d %s\n' "$STEP" "$1" >&2 printf ' got HTTP %s, body: %.300s\n' "$CODE" "$BODY" >&2 exit 1 } # req METHOD PATH [JSON_BODY] -> sets CODE, CTYPE, BODY req() { local method="$1" path="$2" data="${3:-}" local out local args=(-sS -o "$BODY_FILE" -w '%{http_code} %{content_type}' -X "$method") if [[ -n "$data" ]]; then args+=(-H 'Content-Type: application/json' --data "$data") fi STEP=$((STEP + 1)) out="$(curl "${args[@]}" "$BASE_URL$path" 2>&1)" || { CODE="000"; BODY="$out"; fail "$method $path: could not connect to $BASE_URL" } CODE="${out%% *}" CTYPE="${out#* }" BODY="$(cat "$BODY_FILE")" } # json_int KEY -> first integer value of "KEY" in BODY json_int() { printf '%s' "$BODY" | sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p" | head -n 1 } TITLE="Smoke Test $(date +%s)-$$" echo "Smoke testing $BASE_URL" # 1. GET / -> 200 HTML req GET / [[ "$CODE" == "200" && "$CTYPE" == text/html* ]] || fail "GET / should return 200 text/html (content-type: $CTYPE)" pass "GET / -> 200 text/html" # 2. POST a book -> 201 req POST /api/books "{\"title\": \"$TITLE\", \"author\": \"Smoke\", \"status\": \"reading\"}" [[ "$CODE" == "201" ]] || fail "POST /api/books should return 201" BOOK_ID="$(json_int id)" [[ -n "$BOOK_ID" ]] || fail "POST /api/books response has no integer id" [[ "$BODY" == *"\"$TITLE\""* ]] || fail "POST /api/books response should echo the title" pass "POST /api/books -> 201 (id $BOOK_ID)" # 3. GET /api/books?status=reading includes it req GET "/api/books?status=reading" [[ "$CODE" == "200" ]] || fail "GET /api/books?status=reading should return 200" [[ "$BODY" == *"\"$TITLE\""* ]] || fail "GET /api/books?status=reading should include the new book" pass "GET /api/books?status=reading -> 200, includes new book" # Stats before the status change req GET /api/stats [[ "$CODE" == "200" ]] || fail "GET /api/stats should return 200" DONE_BEFORE="$(json_int done)" [[ -n "$DONE_BEFORE" ]] || fail "GET /api/stats should report by_status.done" pass "GET /api/stats -> 200 (done=$DONE_BEFORE)" # 4. PATCH its status -> 200 req PATCH "/api/books/$BOOK_ID" '{"status": "done"}' [[ "$CODE" == "200" ]] || fail "PATCH /api/books/$BOOK_ID should return 200" [[ "$BODY" == *'"status":"done"'* || "$BODY" == *'"status": "done"'* ]] || fail "PATCH response should have status done" pass "PATCH /api/books/$BOOK_ID -> 200, status done" # 5. GET /api/stats reflects the change req GET /api/stats [[ "$CODE" == "200" ]] || fail "GET /api/stats should return 200" DONE_AFTER="$(json_int done)" [[ "$DONE_AFTER" == "$((DONE_BEFORE + 1))" ]] || fail "by_status.done should go from $DONE_BEFORE to $((DONE_BEFORE + 1)), got '$DONE_AFTER'" pass "GET /api/stats -> 200 (done=$DONE_AFTER)" # 6. 422 on blank title req POST /api/books '{"title": " "}' [[ "$CODE" == "422" ]] || fail "POST /api/books with a blank title should return 422" pass "POST /api/books blank title -> 422" # 7. 404 on PATCH of a missing id req PATCH /api/books/999999999 '{"status": "done"}' [[ "$CODE" == "404" ]] || fail "PATCH /api/books/999999999 should return 404" pass "PATCH /api/books/999999999 -> 404" echo "All smoke checks passed."