← back to Stayclaim

scripts/smoke.sh

195 lines

#!/usr/bin/env bash
# smoke.sh — post-deploy regression check for stayclaim / pastdoor
# Runs locally; hits production domains. No browser required.
# Usage: ./scripts/smoke.sh [BASE_OVERRIDE]
# BASE_OVERRIDE lets you hit a staging host instead, e.g. http://localhost:3001
# Exit 0 = all pass.  Exit 1 = one or more failures.

set -euo pipefail

PASS=0
FAIL=0
START=$(date +%s)

# ── config ──────────────────────────────────────────────────────────────────
WLT="${1:-https://wholivedthere.com}"
CMA="${1:-https://claimmyaddress.com}"
BBL="${1:-https://bubbesblock.com}"
# When a BASE_OVERRIDE is given all 3 surfaces collapse to the same host.
# The host-specific brand checks still work because they probe the HTML body.
CURL="curl -sS --max-time 15 --compressed"
KAMATERA_SSH="root@45.61.58.125"
DB_NAME="pastdoor"

# ── helpers ─────────────────────────────────────────────────────────────────
ok()   { echo "  [PASS] $*"; PASS=$((PASS+1)); }
fail() { echo "  [FAIL] $*"; FAIL=$((FAIL+1)); }

check_status() {
  local label="$1" url="$2" expected="${3:-200}"
  local code
  code=$($CURL -o /dev/null -w "%{http_code}" "$url" 2>/dev/null) || code=000
  if [[ "$code" == "$expected" ]]; then
    ok "$label  HTTP $code"
  else
    fail "$label  expected $expected got $code  ($url)"
  fi
}

# Returns body via stdout; also asserts HTTP 200.
fetch_body() {
  local label="$1" url="$2"
  local tmp; tmp=$(mktemp)
  local code
  code=$($CURL -o "$tmp" -w "%{http_code}" "$url" 2>/dev/null) || code=000
  if [[ "$code" != "200" ]]; then
    fail "$label  HTTP $code ($url)"
    rm -f "$tmp"
    echo ""
    return
  fi
  cat "$tmp"
  rm -f "$tmp"
}

assert_contains() {
  local label="$1" body="$2" needle="$3"
  if grep -qiF "$needle" <<<"$body"; then
    ok "$label  contains '$needle'"
  else
    fail "$label  missing '$needle'"
  fi
}

assert_nonempty() {
  local label="$1" body="$2"
  if [[ -n "$body" ]]; then
    ok "$label  body non-empty"
  else
    fail "$label  empty body"
  fi
}

assert_size_range() {
  local label="$1" body="$2" min="$3" max="$4"
  local sz=${#body}
  if (( sz >= min && sz <= max )); then
    ok "$label  size ${sz}B in [${min},${max}]"
  else
    fail "$label  size ${sz}B outside [${min},${max}]"
  fi
}

assert_json_array() {
  local label="$1" body="$2" key="${3:-}"
  local target="$body"
  [[ -n "$key" ]] && target=$(echo "$body" | jq -r ".$key" 2>/dev/null)
  if echo "$target" | jq -e 'if type=="array" then true else false end' &>/dev/null; then
    ok "$label  valid JSON array"
  else
    fail "$label  not a JSON array (body: ${body:0:120})"
  fi
}

assert_valid_json() {
  local label="$1" body="$2"
  if echo "$body" | jq . &>/dev/null; then
    ok "$label  valid JSON"
  else
    fail "$label  invalid JSON (body: ${body:0:120})"
  fi
}

# ── 1. Root URLs — brand strings ─────────────────────────────────────────────
echo ""
echo "=== 1. Root URLs ==="

for pair in "$WLT|wholivedthere" "$CMA|claimmyaddress" "$BBL|bubbe"; do
  host="${pair%%|*}"
  brand="${pair##*|}"
  body=$(fetch_body "GET $host/" "$host/")
  assert_nonempty "root/$brand" "$body"
  assert_contains "root/$brand brand" "$body" "$brand"
done

# ── 2. Sitemap per domain ────────────────────────────────────────────────────
echo ""
echo "=== 2. Sitemaps ==="

for host in "$WLT" "$CMA" "$BBL"; do
  label="sitemap $host"
  body=$(fetch_body "$label" "$host/sitemap.xml")
  assert_contains "$label xml-decl-or-urlset" "$body" '<url'
  assert_size_range "$label size" "$body" 1024 10485760   # 1KB – 10MB
done

# ── 3. Timeline RSS ──────────────────────────────────────────────────────────
echo ""
echo "=== 3. Timeline RSS ==="

body=$(fetch_body "GET /timeline/rss.xml" "$WLT/timeline/rss.xml")
assert_contains "rss opening tag" "$body" '<rss'

# ── 4. /api/events?kind=permit  (regression: was rejected with 400) ──────────
echo ""
echo "=== 4. /api/events?kind=permit ==="

body=$(fetch_body "GET /api/events?kind=permit" "$WLT/api/events?kind=permit")
assert_valid_json "events/permit response" "$body"
assert_json_array "events/permit .events" "$body" "events"
# Confirm kind filter echoed back (proves VALID_KINDS includes 'permit')
if echo "$body" | jq -e '.filters.kind == "permit"' &>/dev/null; then
  ok "events/permit  filters.kind=permit echoed"
else
  fail "events/permit  filters.kind not echoed as 'permit'"
fi

# ── 5. /api/search?q=test ───────────────────────────────────────────────────
echo ""
echo "=== 5. /api/search ==="

body=$(fetch_body "GET /api/search?q=test" "$WLT/api/search?q=test")
assert_valid_json "search response" "$body"

# ── 6. Known-good slug from DB ───────────────────────────────────────────────
echo ""
echo "=== 6. Address slug page ==="

SLUG=$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$KAMATERA_SSH" \
  "sudo -u postgres psql -Atq -d $DB_NAME -c \"SELECT slug FROM listing WHERE is_public=true LIMIT 1\"" 2>/dev/null \
  || echo "")

if [[ -z "$SLUG" ]]; then
  fail "slug lookup  could not retrieve slug from DB (ssh or psql failed)"
else
  ok "slug lookup  got '$SLUG'"
  body=$(fetch_body "GET /address/$SLUG" "$WLT/address/$SLUG")
  assert_nonempty "address/$SLUG body" "$body"
  assert_contains "address/$SLUG is HTML" "$body" '<html'
fi

# ── 7. robots.txt ────────────────────────────────────────────────────────────
echo ""
echo "=== 7. robots.txt ==="

body=$(fetch_body "GET /robots.txt" "$WLT/robots.txt")
assert_contains "robots sitemap directive" "$body" "Sitemap:"

# ── Summary ──────────────────────────────────────────────────────────────────
END=$(date +%s)
ELAPSED=$(( END - START ))
TOTAL=$(( PASS + FAIL ))

echo ""
echo "============================================"
echo "  Smoke result: ${PASS}/${TOTAL} passed  (${ELAPSED}s)"
echo "============================================"

if (( FAIL > 0 )); then
  echo "  FAILED — ${FAIL} check(s) did not pass"
  exit 1
fi

echo "  ALL CHECKS PASSED"
exit 0