#!/usr/bin/env bash # Integration test for Mailship # # Starts the server, runs through the full E2E flow, cleans up. # Exits 0 on success, 1 on failure. # # Usage: ./test/integration.sh [--server-only] # --server-only: just start the server (for manual testing) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" PORT="${PORT:-4741}" BASE_URL="http://localhost:$PORT" PASS=0 FAIL=0 GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' cleanup() { echo "" echo "=== Cleaning up ===" kill "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true rm -rf "$PROJECT_DIR/test-data" echo "Done." } trap cleanup EXIT # Ensure we have a fresh build cd "$PROJECT_DIR" if [ ! -d "dist" ]; then echo "Building project..." npx tsc fi # Generate a random secret for the test SECRET="$(openssl rand -hex 32)" # Export env vars for the server export MAILSHIP_SECRET="$SECRET" export MAILSHIP_NAME="Mailship Test" export MAILSHIP_URL="$BASE_URL" export BASE_URL="$BASE_URL" export SMTP_HOST="localhost" export SMTP_PORT="587" export SMTP_USER="test@test.com" export SMTP_PASSWORD="test" export SMTP_FROM="test@test.com" export DEFAULT_RELAYS="wss://relay.damus.io" export INDEXER_RELAYS="wss://purplepag.es" export SEARCH_RELAYS="wss://relay.nostr.band" export PORT="$PORT" export DATA_DIR="$PROJECT_DIR/test-data" mkdir -p "$DATA_DIR" DB_PATH="$DATA_DIR/mailship.db" echo "=== Starting server on port $PORT ===" node dist/index.js & SERVER_PID=$! sleep 3 if ! kill -0 "$SERVER_PID" 2>/dev/null; then echo -e "${RED}Server failed to start${NC}" exit 1 fi echo "Server PID: $SERVER_PID" if [ "${1:-}" = "--server-only" ]; then echo "" echo "Server running at $BASE_URL" echo "DB at $DB_PATH" echo "Press Ctrl+C to stop." wait "$SERVER_PID" exit 0 fi echo "" echo "=========================================" echo " INTEGRATION TESTS" echo "=========================================" echo "" pass() { PASS=$((PASS + 1)) echo -e " ${GREEN}✓${NC} $1" } fail() { FAIL=$((FAIL + 1)) echo -e " ${RED}✗${NC} $1" } # Helper: check that a JSON field equals expected value check_field() { local label="$1" local json="$2" local field="$3" local expected="$4" local actual actual=$(echo "$json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('$field',''))" 2>/dev/null) if [ "$actual" = "$expected" ]; then pass "$label" else fail "$label (expected $field=$expected, got $actual)" fi } # Test 1: Health check echo "1. Health check" HEALTH=$(curl -s "$BASE_URL/") check_field "Root endpoint returns Mailship" "$HEALTH" "name" "Mailship" # Test 2: Register subscription echo "" echo "2. Register subscription" REG=$(curl -s "$BASE_URL/subscription/email" -X PUT -H "Content-Type: application/json" \ -d '{"email":"test@example.com","frequency":"daily","pubkey":"abc123"}') KEY=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))" 2>/dev/null) CALLBACK=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('callback',''))" 2>/dev/null) if [ -n "$KEY" ] && [ -n "$CALLBACK" ]; then pass "Registration returns key and callback" else fail "Registration missing key or callback (got: $REG)" fi # Test 3: Confirm subscription echo "" echo "3. Confirm subscription" CONFIRM=$(curl -s "$BASE_URL/confirm?token=$KEY" 2>&1) if echo "$CONFIRM" | grep -qi "success"; then pass "Confirmation page shows success" else fail "Confirmation page doesn't show success" fi # Test 4: Check SQLite state echo "" echo "4. Database state" CONFIRMED=$(sqlite3 "$DB_PATH" "SELECT confirmed_at FROM subscriptions WHERE email='test@example.com';" 2>/dev/null) if [ -n "$CONFIRMED" ] && [ "$CONFIRMED" -gt 0 ]; then pass "Subscription confirmed in DB" else fail "Subscription not confirmed in DB" fi # Test 5: Push event to notify endpoint echo "" echo "5. Push event via notify" SUB_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM subscriptions WHERE email='test@example.com';" 2>/dev/null) # Fetch a real event from a relay EVENT_ID=$(nak req -k 1 -l 1 wss://relay.primal.net 2>/dev/null | head -1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])" 2>/dev/null) if [ -z "$EVENT_ID" ]; then # Try another relay EVENT_ID=$(nak req -k 1 -l 1 wss://nos.lol 2>/dev/null | head -1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])" 2>/dev/null) fi if [ -z "$EVENT_ID" ]; then fail "Could not fetch event from relay (network issue?)" else NOTIFY=$(curl -s "$BASE_URL/notify/$SUB_ID" -X POST -H "Content-Type: application/json" \ -d "{\"id\":\"$EVENT_ID\",\"relay\":\"wss://relay.primal.net\"}") check_field "Event stored in DB" "$NOTIFY" "stored" "True" fi # Test 6: Dedup echo "" echo "6. Dedup" if [ -n "$EVENT_ID" ]; then DEDUP=$(curl -s "$BASE_URL/notify/$SUB_ID" -X POST -H "Content-Type: application/json" \ -d "{\"id\":\"$EVENT_ID\",\"relay\":\"wss://relay.primal.net\"}") check_field "Duplicate event rejected" "$DEDUP" "stored" "False" fi # Test 7: 404 for nonexistent subscription echo "" echo "7. 404 for nonexistent subscription" NOT_FOUND=$(curl -s "$BASE_URL/notify/nonexistent-id" -X POST -H "Content-Type: application/json" \ -d '{"id":"abc","relay":"wss://relay.primal.net"}') check_field "Nonexistent subscription returns 404" "$NOT_FOUND" "error" "Subscription not found" # Test 8: Unsubscribe echo "" echo "8. Unsubscribe" UNSUB=$(curl -s "$BASE_URL/unsubscribe?token=$KEY") if echo "$UNSUB" | grep -qi "unsubscribed\|success"; then pass "Unsubscribe page renders" else fail "Unsubscribe page didn't render" fi # Test 9: Notify after unsubscribe returns 404 echo "" echo "9. No push after unsubscribe" if [ -n "$EVENT_ID" ]; then AFTER_UNSUB=$(curl -s "$BASE_URL/notify/$SUB_ID" -X POST -H "Content-Type: application/json" \ -d "{\"id\":\"$EVENT_ID\",\"relay\":\"wss://relay.primal.net\"}") check_field "Push after unsubscribe returns 404" "$AFTER_UNSUB" "error" "Subscription not active" fi # Test 10: Delete subscription echo "" echo "10. Delete subscription" DELETE=$(curl -s "$BASE_URL/subscription/$KEY" -X DELETE 2>&1) check_field "Delete returns ok" "$DELETE" "ok" "True" # Summary echo "" echo "=========================================" echo " RESULTS: $PASS passed, $FAIL failed" echo "=========================================" if [ "$FAIL" -gt 0 ]; then exit 1 fi exit 0