#!/usr/bin/env bash # ============================================================ # Docker Smoke Test — Tubearr # # Builds the Docker image, starts a container, and verifies # core endpoints work end-to-end. Tests restart persistence. # # Usage: bash scripts/docker-smoke-test.sh # ============================================================ set -euo pipefail # ── Configuration ── IMAGE_NAME="tubearr" # Container name must match docker-compose.yml container_name CONTAINER_NAME="tubearr" PORT=8989 HEALTH_TIMEOUT=90 # seconds to wait for healthy status COMPOSE_FILE="docker-compose.yml" # ── Color output helpers ── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color pass() { echo -e "${GREEN}✓ $1${NC}"; } fail() { echo -e "${RED}✗ $1${NC}"; } info() { echo -e "${YELLOW}→ $1${NC}"; } # ── Cleanup trap ── cleanup() { info "Cleaning up..." docker-compose -f "$COMPOSE_FILE" down --volumes --remove-orphans 2>/dev/null || true # Remove any leftover config/media dirs created by compose bind mounts rm -rf ./config ./media 2>/dev/null || true } trap cleanup EXIT # ── Pre-check: Ensure port is not already in use ── info "Checking port $PORT availability" if curl -sf "http://localhost:${PORT}/ping" >/dev/null 2>&1; then fail "Port $PORT is already in use — another service is running. Stop it before running this test." exit 1 fi pass "Port $PORT is available" # ── Step 1: Build Docker image ── info "Building Docker image: $IMAGE_NAME" docker build -t "$IMAGE_NAME" . || { fail "Docker build failed" exit 1 } pass "Docker image built successfully" # ── Step 2: Start container via docker-compose ── info "Starting container via docker-compose" docker-compose -f "$COMPOSE_FILE" up -d || { fail "docker-compose up failed" exit 1 } pass "Container started" # ── Step 3: Wait for healthy ── info "Waiting for container to become healthy (timeout: ${HEALTH_TIMEOUT}s)" elapsed=0 while [ $elapsed -lt $HEALTH_TIMEOUT ]; do status=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "unknown") if [ "$status" = "healthy" ]; then break fi if [ "$status" = "unhealthy" ]; then fail "Container became unhealthy" echo "Container logs:" docker logs "$CONTAINER_NAME" 2>&1 | tail -30 exit 1 fi sleep 2 elapsed=$((elapsed + 2)) done if [ "$status" != "healthy" ]; then fail "Container did not become healthy within ${HEALTH_TIMEOUT}s (status: $status)" echo "Container logs:" docker logs "$CONTAINER_NAME" 2>&1 | tail -30 exit 1 fi pass "Container is healthy" # ── Step 4: Verify /ping (unauthenticated) ── info "Testing GET /ping" PING_RESPONSE=$(curl -sf "http://localhost:${PORT}/ping" 2>&1) || { fail "GET /ping failed" exit 1 } if echo "$PING_RESPONSE" | grep -q '"status":"ok"'; then pass "GET /ping returns {\"status\":\"ok\"}" else fail "GET /ping unexpected response: $PING_RESPONSE" exit 1 fi # ── Step 5: Extract API key from container logs ── info "Extracting API key from container logs" # The auth plugin logs the generated key in a banner like: # API Key generated (save this — it will not be shown again): # # We look for a UUID-like string on a line by itself after the banner text. # On restart with persisted state, the key won't be in logs — use TUBEARR_API_KEY env var as fallback. API_KEY=$(docker logs "$CONTAINER_NAME" 2>&1 | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1 || true) if [ -z "$API_KEY" ]; then fail "Could not extract API key from container logs" echo "Container logs:" docker logs "$CONTAINER_NAME" 2>&1 | tail -20 exit 1 fi pass "API key extracted: ${API_KEY:0:8}...${API_KEY: -4}" # ── Step 6: Verify /api/v1/health (authenticated) ── info "Testing GET /api/v1/health" HEALTH_RESPONSE=$(curl -sf -H "X-Api-Key: $API_KEY" "http://localhost:${PORT}/api/v1/health" 2>&1) || { fail "GET /api/v1/health failed" exit 1 } if echo "$HEALTH_RESPONSE" | grep -q '"status"'; then pass "GET /api/v1/health returns health status" else fail "GET /api/v1/health unexpected response: $HEALTH_RESPONSE" exit 1 fi # ── Step 7: Verify /api/v1/system/status (authenticated) ── info "Testing GET /api/v1/system/status" STATUS_RESPONSE=$(curl -sf -H "X-Api-Key: $API_KEY" "http://localhost:${PORT}/api/v1/system/status" 2>&1) || { fail "GET /api/v1/system/status failed" exit 1 } if echo "$STATUS_RESPONSE" | grep -q '"appName":"Tubearr"'; then pass "GET /api/v1/system/status returns appName=Tubearr" else fail "GET /api/v1/system/status unexpected response: $STATUS_RESPONSE" exit 1 fi # ── Step 8: Verify auth rejection ── info "Testing auth rejection (no API key)" AUTH_CODE=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT}/api/v1/system/status" 2>&1) if [ "$AUTH_CODE" = "401" ]; then pass "Unauthenticated request correctly returns 401" else fail "Expected 401, got $AUTH_CODE" exit 1 fi # ── Step 9: Test restart persistence ── info "Testing container restart persistence" # Record the API key before restart PRE_RESTART_KEY="$API_KEY" # Restart the container docker-compose -f "$COMPOSE_FILE" restart || { fail "docker-compose restart failed" exit 1 } # Wait for healthy again info "Waiting for container to become healthy after restart" elapsed=0 while [ $elapsed -lt $HEALTH_TIMEOUT ]; do status=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "unknown") if [ "$status" = "healthy" ]; then break fi if [ "$status" = "unhealthy" ]; then fail "Container became unhealthy after restart" docker logs "$CONTAINER_NAME" 2>&1 | tail -20 exit 1 fi sleep 2 elapsed=$((elapsed + 2)) done if [ "$status" != "healthy" ]; then fail "Container did not become healthy after restart within ${HEALTH_TIMEOUT}s" docker logs "$CONTAINER_NAME" 2>&1 | tail -20 exit 1 fi pass "Container healthy after restart" # Verify /ping still works PING_AFTER=$(curl -sf "http://localhost:${PORT}/ping" 2>&1) || { fail "GET /ping failed after restart" exit 1 } if echo "$PING_AFTER" | grep -q '"status":"ok"'; then pass "GET /ping works after restart" else fail "GET /ping unexpected response after restart: $PING_AFTER" exit 1 fi # Verify the same API key works (state persisted via volume) HEALTH_AFTER=$(curl -sf -H "X-Api-Key: $PRE_RESTART_KEY" "http://localhost:${PORT}/api/v1/health" 2>&1) || { fail "GET /api/v1/health failed after restart with pre-restart API key" exit 1 } if echo "$HEALTH_AFTER" | grep -q '"status"'; then pass "Pre-restart API key still works — state persisted" else fail "API key state not preserved across restart" exit 1 fi # ── Done ── echo "" echo -e "${GREEN}═══════════════════════════════════════════${NC}" echo -e "${GREEN} SMOKE TEST PASSED${NC}" echo -e "${GREEN}═══════════════════════════════════════════${NC}" echo ""