ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
at master 216 lines 7.8 kB view raw
1#!/bin/bash 2# ═══════════════════════════════════════════════════════ 3# ATlast Docker Stack Smoke Test 4# Validates the full stack is running and responding. 5# Usage: bash scripts/docker-smoke-test.sh 6# ═══════════════════════════════════════════════════════ 7 8set -euo pipefail 9 10# ── Configuration ────────────────────────────────────── 11COMPOSE_FILE="docker/docker-compose.yml" 12BASE_URL="http://localhost" 13MAX_WAIT_SECONDS=90 14POLL_INTERVAL=5 15 16# ── Color output ─────────────────────────────────────── 17RED='\033[0;31m' 18GREEN='\033[0;32m' 19YELLOW='\033[1;33m' 20NC='\033[0m' # No Color 21 22PASS=0 23FAIL=0 24 25# ── Helper functions ─────────────────────────────────── 26 27log_info() { echo -e "${YELLOW}[INFO]${NC} $1"; } 28log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; PASS=$((PASS + 1)); } 29log_fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL + 1)); } 30 31check_status() { 32 local description="$1" 33 local url="$2" 34 local expected_status="$3" 35 36 local actual_status 37 actual_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" 2>/dev/null || echo "000") 38 39 if [ "$actual_status" = "$expected_status" ]; then 40 log_pass "$description → HTTP $actual_status (expected $expected_status)" 41 else 42 log_fail "$description → HTTP $actual_status (expected $expected_status) — URL: $url" 43 fi 44} 45 46check_json_field() { 47 local description="$1" 48 local url="$2" 49 local field="$3" 50 51 local response 52 response=$(curl -s --max-time 10 "$url" 2>/dev/null || echo "{}") 53 54 # Use node to parse JSON — available on any system running this stack. 55 local value 56 value=$(node -e " 57 try { 58 const data = JSON.parse(process.argv[1]); 59 const keys = process.argv[2].split('.'); 60 let val = data; 61 for (const key of keys) { val = val[key]; } 62 process.stdout.write(val !== undefined ? 'found' : 'missing'); 63 } catch (e) { 64 process.stdout.write('error'); 65 } 66 " "$response" "$field" 2>/dev/null || echo "error") 67 68 if [ "$value" = "found" ]; then 69 log_pass "$description → field '$field' present" 70 else 71 log_fail "$description → field '$field' missing or response unparseable" 72 fi 73} 74 75wait_for_healthy() { 76 local service_name="$1" 77 local url="$2" 78 local elapsed=0 79 80 log_info "Waiting for $service_name to become healthy (up to ${MAX_WAIT_SECONDS}s)..." 81 82 while [ $elapsed -lt $MAX_WAIT_SECONDS ]; do 83 local actual_status 84 actual_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo "000") 85 86 # Accept 200 (health ok) or 401 (auth endpoint, service is up) as "alive" 87 if [ "$actual_status" = "200" ] || [ "$actual_status" = "401" ]; then 88 log_info "$service_name is responding (HTTP $actual_status) after ${elapsed}s" 89 return 0 90 fi 91 92 sleep $POLL_INTERVAL 93 elapsed=$((elapsed + POLL_INTERVAL)) 94 log_info " ... still waiting (${elapsed}s / ${MAX_WAIT_SECONDS}s, HTTP $actual_status)" 95 done 96 97 echo -e "${RED}[ERROR]${NC} $service_name did not become healthy within ${MAX_WAIT_SECONDS}s" 98 return 1 99} 100 101show_logs_for_service() { 102 local service="$1" 103 echo "" 104 echo -e "${YELLOW}═══ Logs for: $service ═══${NC}" 105 docker compose -f "$COMPOSE_FILE" logs --tail=30 "$service" 2>/dev/null || true 106 echo "" 107} 108 109# ── Main ──────────────────────────────────────────────── 110 111echo "" 112echo -e "${YELLOW}╔══════════════════════════════════════════╗${NC}" 113echo -e "${YELLOW}║ ATlast Docker Stack Smoke Test ║${NC}" 114echo -e "${YELLOW}╚══════════════════════════════════════════╝${NC}" 115echo "" 116 117# ── Step 1: Build and start the stack ────────────────── 118log_info "Building and starting the stack..." 119docker compose -f "$COMPOSE_FILE" up --build -d 120 121echo "" 122log_info "Stack started. Waiting for services to initialize..." 123echo "" 124 125# ── Step 2: Wait for API health endpoint ─────────────── 126if ! wait_for_healthy "API" "$BASE_URL/api/health"; then 127 echo "" 128 echo -e "${RED}API did not start. Showing logs for debugging:${NC}" 129 show_logs_for_service "api" 130 show_logs_for_service "database" 131 show_logs_for_service "redis" 132 exit 1 133fi 134 135echo "" 136echo -e "${YELLOW}─── Running endpoint checks ───${NC}" 137echo "" 138 139# ── Step 3: API health check ─────────────────────────── 140check_status \ 141 "GET /api/health → 200 OK" \ 142 "$BASE_URL/api/health" \ 143 "200" 144 145check_json_field \ 146 "GET /api/health → has 'status' field" \ 147 "$BASE_URL/api/health" \ 148 "status" 149 150# ── Step 4: Auth endpoints (unauthenticated) ─────────── 151 152# 401 proves: API is running AND auth middleware is working correctly. 153# A missing session returning 401 is the expected, correct behavior. 154check_status \ 155 "GET /api/auth/session → 401 (no session cookie, auth middleware active)" \ 156 "$BASE_URL/api/auth/session" \ 157 "401" 158 159check_status \ 160 "GET /api/auth/client-metadata.json → 200" \ 161 "$BASE_URL/api/auth/client-metadata.json" \ 162 "200" 163 164check_json_field \ 165 "GET /api/auth/client-metadata.json → has 'client_id' field" \ 166 "$BASE_URL/api/auth/client-metadata.json" \ 167 "client_id" 168 169check_status \ 170 "GET /api/auth/jwks → 200" \ 171 "$BASE_URL/api/auth/jwks" \ 172 "200" 173 174check_json_field \ 175 "GET /api/auth/jwks → has 'keys' field" \ 176 "$BASE_URL/api/auth/jwks" \ 177 "keys" 178 179# ── Step 5: Frontend is served ───────────────────────── 180check_status \ 181 "GET / → 200 (frontend HTML served by nginx)" \ 182 "$BASE_URL/" \ 183 "200" 184 185# ── Step 6: Docker service health summary ────────────── 186echo "" 187echo -e "${YELLOW}─── Docker service status ───${NC}" 188docker compose -f "$COMPOSE_FILE" ps 189echo "" 190 191# ── Step 7: Show results ─────────────────────────────── 192echo -e "${YELLOW}─── Results ────────────────────────────────────${NC}" 193echo -e " ${GREEN}Passed: $PASS${NC}" 194echo -e " ${RED}Failed: $FAIL${NC}" 195echo "" 196 197if [ $FAIL -gt 0 ]; then 198 echo -e "${RED}Smoke test FAILED ($FAIL checks failed).${NC}" 199 echo "" 200 echo "Showing logs for potentially failing services:" 201 202 for service in api worker database redis frontend; do 203 STATUS=$(docker compose -f "$COMPOSE_FILE" ps --format "{{.Service}} {{.Status}}" 2>/dev/null \ 204 | grep "^$service " | awk '{print $2}' || echo "unknown") 205 if [[ "$STATUS" != *"Up"* ]] || [[ "$STATUS" == *"unhealthy"* ]]; then 206 show_logs_for_service "$service" 207 fi 208 done 209 210 exit 1 211else 212 echo -e "${GREEN}All smoke tests passed!${NC}" 213 echo "" 214 echo "The stack is running. Access the app at: $BASE_URL" 215 exit 0 216fi