525 lines
20 KiB
Bash
Executable file
525 lines
20 KiB
Bash
Executable file
#!/bin/bash
|
||
set -euo pipefail
|
||
|
||
# =============================================================================
|
||
# Messaging Backend Integration Test on plum-voyager
|
||
# =============================================================================
|
||
# Runs the messaging backend on plum (macOS) with local PostgreSQL + Redis,
|
||
# then verifies REST and WebSocket contracts end-to-end.
|
||
#
|
||
# Usage:
|
||
# ./scripts/plum-integration-test.sh # Full pipeline
|
||
# ./scripts/plum-integration-test.sh --setup-only # Install infra + sync + build
|
||
# ./scripts/plum-integration-test.sh --test-only # Skip setup, run tests
|
||
# ./scripts/plum-integration-test.sh --teardown # Stop services, clean up
|
||
|
||
# Colors
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
REMOTE_HOST="plum-voyager"
|
||
BREW_PATH="/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/opt/homebrew/opt/postgresql@16/bin"
|
||
JWT_SECRET="dev-jwt-secret-change-in-production"
|
||
VERDACCIO_LAN="10.0.0.11:4873"
|
||
|
||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||
BACKEND_DIR="$(dirname "$SCRIPT_DIR")"
|
||
REPO_ROOT="$BACKEND_DIR/../../../.."
|
||
REMOTE_REPO="\$HOME/Code/@projects/@lilith/lilith-platform"
|
||
REMOTE_BACKEND="$REMOTE_REPO/codebase/features/messaging/backend-api"
|
||
|
||
# For rsync (which needs literal path, not shell variable)
|
||
REMOTE_REPO_LITERAL="~/Code/@projects/@lilith/lilith-platform"
|
||
REMOTE_BACKEND_LITERAL="$REMOTE_REPO_LITERAL/codebase/features/messaging/backend-api"
|
||
|
||
# Counters
|
||
PASS=0
|
||
FAIL=0
|
||
RESULTS=()
|
||
|
||
print_step() { echo -e "${GREEN}▸${NC} $1"; }
|
||
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||
print_error() { echo -e "${RED}✗${NC} $1"; }
|
||
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
||
print_warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||
|
||
record_result() {
|
||
local name="$1" status="$2"
|
||
if [[ "$status" == "PASS" ]]; then
|
||
PASS=$((PASS + 1))
|
||
RESULTS+=("${GREEN}✓${NC} $name")
|
||
else
|
||
FAIL=$((FAIL + 1))
|
||
RESULTS+=("${RED}✗${NC} $name")
|
||
fi
|
||
}
|
||
|
||
# Run a command on plum with proper PATH
|
||
plum() {
|
||
ssh "$REMOTE_HOST" "export PATH=\"$BREW_PATH:\$PATH\"; $*"
|
||
}
|
||
|
||
usage() {
|
||
echo "Usage: $0 [options]"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " --setup-only Install infra, sync, build (don't test)"
|
||
echo " --test-only Skip setup, just run integration tests"
|
||
echo " --teardown Stop services and clean up"
|
||
echo " -h, --help Show this help"
|
||
exit 0
|
||
}
|
||
|
||
# Parse arguments
|
||
DO_SETUP=true
|
||
DO_TEST=true
|
||
DO_TEARDOWN=false
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--setup-only) DO_TEST=false ;;
|
||
--test-only) DO_SETUP=false ;;
|
||
--teardown) DO_SETUP=false; DO_TEST=false; DO_TEARDOWN=true ;;
|
||
-h|--help) usage ;;
|
||
*) echo "Unknown option: $1"; usage ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE} Messaging Backend - Integration Test on plum-voyager${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
|
||
# ─── Check SSH ────────────────────────────────────────────────────────
|
||
print_step "Checking SSH connection to $REMOTE_HOST..."
|
||
if ! ssh -o ConnectTimeout=5 "$REMOTE_HOST" 'echo ok' >/dev/null 2>&1; then
|
||
print_error "Cannot connect to $REMOTE_HOST"
|
||
exit 1
|
||
fi
|
||
print_success "Connected to $REMOTE_HOST"
|
||
echo ""
|
||
|
||
# ─── Teardown ─────────────────────────────────────────────────────────
|
||
if [[ "$DO_TEARDOWN" == true ]]; then
|
||
print_step "Tearing down..."
|
||
|
||
# Kill backend and proxy if running
|
||
plum "pkill -f 'node dist/main' 2>/dev/null || true"
|
||
plum "pkill -f npm-proxy.mjs 2>/dev/null || true"
|
||
print_info "Stopped messaging backend and npm proxy"
|
||
|
||
# Stop services
|
||
plum "brew services stop postgresql@16 2>/dev/null || true; brew services stop redis 2>/dev/null || true"
|
||
print_info "Stopped PostgreSQL and Redis"
|
||
|
||
print_success "Teardown complete"
|
||
exit 0
|
||
fi
|
||
|
||
# ─── Setup ────────────────────────────────────────────────────────────
|
||
if [[ "$DO_SETUP" == true ]]; then
|
||
|
||
# Step 1: Install infra dependencies
|
||
print_step "Installing infrastructure dependencies..."
|
||
|
||
# Check what's already installed
|
||
HAS_PG=$(plum "brew list postgresql@16 >/dev/null 2>&1 && echo yes || echo no")
|
||
HAS_REDIS=$(plum "brew list redis >/dev/null 2>&1 && echo yes || echo no")
|
||
|
||
if [[ "$HAS_PG" == "no" || "$HAS_REDIS" == "no" ]]; then
|
||
print_info "Installing missing packages..."
|
||
plum "brew install postgresql@16 redis 2>&1" | tail -5
|
||
fi
|
||
print_success "PostgreSQL 16 and Redis installed"
|
||
|
||
# Start services (idempotent)
|
||
plum "brew services start postgresql@16 2>/dev/null || true; brew services start redis 2>/dev/null || true"
|
||
sleep 2
|
||
|
||
# Verify services are running
|
||
PG_READY=$(plum "pg_isready 2>&1" || true)
|
||
REDIS_READY=$(plum "redis-cli ping 2>&1" || true)
|
||
|
||
if [[ "$PG_READY" == *"accepting connections"* ]]; then
|
||
record_result "PostgreSQL running (:5432)" "PASS"
|
||
else
|
||
record_result "PostgreSQL running" "FAIL"
|
||
print_error "PostgreSQL not accepting connections: $PG_READY"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ "$REDIS_READY" == "PONG" ]]; then
|
||
record_result "Redis running (:6379)" "PASS"
|
||
else
|
||
record_result "Redis running" "FAIL"
|
||
print_error "Redis not responding: $REDIS_READY"
|
||
exit 1
|
||
fi
|
||
|
||
# Create database and role (idempotent)
|
||
plum "createdb lilith_messaging 2>/dev/null || true"
|
||
plum "psql -d lilith_messaging -c \"DO \\\$\\\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='messaging') THEN CREATE ROLE messaging WITH LOGIN PASSWORD 'devpassword' SUPERUSER; END IF; END \\\$\\\$;\" 2>/dev/null"
|
||
plum "psql -d lilith_messaging -c \"ALTER DATABASE lilith_messaging OWNER TO messaging;\" 2>/dev/null"
|
||
record_result "Database lilith_messaging ready" "PASS"
|
||
echo ""
|
||
|
||
# Step 2: Sync codebase to plum
|
||
print_step "Syncing codebase to $REMOTE_HOST..."
|
||
|
||
plum "mkdir -p $REMOTE_BACKEND $REMOTE_REPO/deployments"
|
||
|
||
rsync -az \
|
||
--exclude=node_modules --exclude=dist --exclude=.build --exclude='*.tsbuildinfo' \
|
||
"$BACKEND_DIR/" \
|
||
"$REMOTE_HOST:$REMOTE_BACKEND_LITERAL/"
|
||
|
||
rsync -az \
|
||
"$REPO_ROOT/deployments/shared-services/" \
|
||
"$REMOTE_HOST:$REMOTE_REPO_LITERAL/deployments/shared-services/"
|
||
|
||
rsync -az \
|
||
"$REPO_ROOT/deployments/@domains/" \
|
||
"$REMOTE_HOST:$REMOTE_REPO_LITERAL/deployments/@domains/"
|
||
|
||
record_result "Codebase synced" "PASS"
|
||
echo ""
|
||
|
||
# Step 3: Configure npm registry for LAN access
|
||
# Verdaccio at npm.nasty.sh resolves to public IP where port 4873 is firewalled.
|
||
# Plum is on the same LAN as black (10.0.0.11) but Verdaccio returns tarball URLs
|
||
# with npm.nasty.sh hostname. We need a rewriting HTTP proxy to intercept those.
|
||
print_step "Configuring npm registry for LAN access..."
|
||
|
||
# Update both global and project .npmrc
|
||
plum "cat > ~/.npmrc << NPMRC
|
||
registry=http://$VERDACCIO_LAN/
|
||
@lilith:registry=http://$VERDACCIO_LAN/
|
||
proxy=http://127.0.0.1:14873/
|
||
https-proxy=http://127.0.0.1:14873/
|
||
NPMRC"
|
||
|
||
plum "cat > $REMOTE_BACKEND/.npmrc << NPMRC
|
||
registry=http://$VERDACCIO_LAN/
|
||
@lilith:registry=http://$VERDACCIO_LAN/
|
||
NPMRC"
|
||
|
||
# Start a rewriting HTTP proxy that redirects npm.nasty.sh → 10.0.0.11
|
||
plum "pkill -f npm-proxy.mjs 2>/dev/null || true"
|
||
plum "cat > /tmp/npm-proxy.mjs << 'PROXYEOF'
|
||
import http from \"node:http\";
|
||
const server = http.createServer((req, res) => {
|
||
let url;
|
||
try { url = new URL(req.url); } catch { url = new URL(\"http://10.0.0.11:4873\" + req.url); }
|
||
const hostname = url.hostname === \"npm.nasty.sh\" ? \"10.0.0.11\" : url.hostname;
|
||
const opts = { hostname, port: parseInt(url.port || 4873), path: url.pathname + url.search, method: req.method, headers: { ...req.headers, host: \"npm.nasty.sh\" } };
|
||
const proxy = http.request(opts, (r) => { res.writeHead(r.statusCode, r.headers); r.pipe(res); });
|
||
req.pipe(proxy);
|
||
proxy.on(\"error\", (e) => { res.writeHead(502); res.end(e.message); });
|
||
});
|
||
server.listen(14873, \"127.0.0.1\", () => console.log(\"npm proxy on :14873\"));
|
||
PROXYEOF
|
||
nohup node /tmp/npm-proxy.mjs > /tmp/npm-proxy.log 2>&1 &"
|
||
sleep 1
|
||
|
||
if plum "curl -sf http://127.0.0.1:14873/clone/-/clone-1.0.4.tgz -o /dev/null"; then
|
||
record_result "npm LAN proxy configured ($VERDACCIO_LAN via :14873)" "PASS"
|
||
else
|
||
record_result "npm LAN proxy" "FAIL"
|
||
print_error "Proxy not working. Check /tmp/npm-proxy.log on plum"
|
||
fi
|
||
|
||
# Step 4: Install dependencies and build
|
||
print_step "Installing npm dependencies (this may take a few minutes)..."
|
||
if plum "cd $REMOTE_BACKEND && npm install --legacy-peer-deps 2>&1 | tee /tmp/npm-install.log; exit \${PIPESTATUS[0]}" ; then
|
||
record_result "npm install" "PASS"
|
||
else
|
||
record_result "npm install" "FAIL"
|
||
print_error "npm install failed. Last 20 lines:"
|
||
plum "tail -20 /tmp/npm-install.log"
|
||
exit 1
|
||
fi
|
||
|
||
print_step "Building backend..."
|
||
if plum "cd $REMOTE_BACKEND && npx nest build 2>&1 | tee /tmp/nest-build.log; exit \${PIPESTATUS[0]}" ; then
|
||
record_result "nest build" "PASS"
|
||
else
|
||
record_result "nest build" "FAIL"
|
||
print_error "Build failed. Last 20 lines:"
|
||
plum "tail -20 /tmp/nest-build.log"
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
fi
|
||
|
||
# ─── Test ─────────────────────────────────────────────────────────────
|
||
if [[ "$DO_TEST" == true ]]; then
|
||
|
||
# Step 4: Generate test JWT tokens
|
||
print_step "Generating test JWT tokens..."
|
||
|
||
# Use valid UUIDs for user IDs (database columns are UUID type)
|
||
CREATOR_UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||
CLIENT_UUID="b2c3d4e5-f6a7-8901-bcde-f12345678901"
|
||
|
||
TOKEN_CREATOR=$(plum "node -e \"
|
||
const crypto = require('crypto');
|
||
function base64url(obj) { return Buffer.from(JSON.stringify(obj)).toString('base64url'); }
|
||
const header = base64url({alg:'HS256',typ:'JWT'});
|
||
const payload = base64url({sub:'$CREATOR_UUID',role:'creator',iat:Math.floor(Date.now()/1000),exp:Math.floor(Date.now()/1000)+86400});
|
||
const sig = crypto.createHmac('sha256','$JWT_SECRET').update(header+'.'+payload).digest('base64url');
|
||
console.log(header+'.'+payload+'.'+sig);
|
||
\"")
|
||
|
||
TOKEN_CLIENT=$(plum "node -e \"
|
||
const crypto = require('crypto');
|
||
function base64url(obj) { return Buffer.from(JSON.stringify(obj)).toString('base64url'); }
|
||
const header = base64url({alg:'HS256',typ:'JWT'});
|
||
const payload = base64url({sub:'$CLIENT_UUID',role:'client',iat:Math.floor(Date.now()/1000),exp:Math.floor(Date.now()/1000)+86400});
|
||
const sig = crypto.createHmac('sha256','$JWT_SECRET').update(header+'.'+payload).digest('base64url');
|
||
console.log(header+'.'+payload+'.'+sig);
|
||
\"")
|
||
|
||
if [[ -n "$TOKEN_CREATOR" && -n "$TOKEN_CLIENT" ]]; then
|
||
record_result "JWT token generation" "PASS"
|
||
print_info "Creator token: ${TOKEN_CREATOR:0:40}..."
|
||
print_info "Client token: ${TOKEN_CLIENT:0:40}..."
|
||
else
|
||
record_result "JWT token generation" "FAIL"
|
||
print_error "Failed to generate tokens"
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
|
||
# Step 5: Start the messaging backend
|
||
print_step "Starting messaging backend..."
|
||
|
||
# Kill any existing instance
|
||
plum "pkill -f 'node dist/main' 2>/dev/null || true"
|
||
sleep 1
|
||
|
||
# Start in background with env overrides for default ports
|
||
ssh "$REMOTE_HOST" "export PATH=\"$BREW_PATH:\$PATH\"; cd $REMOTE_BACKEND && \
|
||
LILITH_PROJECT_ROOT=$REMOTE_REPO \
|
||
DATABASE_POSTGRES_USER=messaging \
|
||
DATABASE_POSTGRES_PASSWORD=devpassword \
|
||
DATABASE_POSTGRES_NAME=lilith_messaging \
|
||
JWT_SECRET=$JWT_SECRET \
|
||
PORT=3120 \
|
||
NODE_ENV=development \
|
||
nohup node dist/main.js > /tmp/messaging-backend.log 2>&1 &"
|
||
|
||
# Wait for startup
|
||
print_info "Waiting for backend to start..."
|
||
STARTED=false
|
||
for i in $(seq 1 15); do
|
||
sleep 2
|
||
HEALTH=$(plum "curl -sf http://localhost:3120/health 2>/dev/null" || true)
|
||
if [[ -n "$HEALTH" ]]; then
|
||
STARTED=true
|
||
break
|
||
fi
|
||
print_info " Attempt $i/15..."
|
||
done
|
||
|
||
if [[ "$STARTED" == true ]]; then
|
||
record_result "Backend started (:3120)" "PASS"
|
||
print_success "Health response: $HEALTH"
|
||
else
|
||
record_result "Backend started" "FAIL"
|
||
print_error "Backend failed to start. Logs:"
|
||
plum "tail -30 /tmp/messaging-backend.log"
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
|
||
# Step 6: REST API tests
|
||
print_step "Testing REST API..."
|
||
|
||
# Test 1: Health endpoint
|
||
HEALTH_STATUS=$(plum "curl -s -o /dev/null -w '%{http_code}' http://localhost:3120/health")
|
||
if [[ "$HEALTH_STATUS" == "200" ]]; then
|
||
record_result "REST: GET /health → 200" "PASS"
|
||
else
|
||
record_result "REST: GET /health → $HEALTH_STATUS" "FAIL"
|
||
fi
|
||
|
||
# Test 2: Create thread
|
||
CREATE_THREAD_RESPONSE=$(plum "curl -sf -X POST http://localhost:3120/api/messaging/threads \
|
||
-H 'Authorization: Bearer $TOKEN_CREATOR' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"creatorId\": \"$CREATOR_UUID\", \"clientId\": \"$CLIENT_UUID\"}' 2>&1" || true)
|
||
|
||
THREAD_ID=$(echo "$CREATE_THREAD_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||
|
||
if [[ -n "$THREAD_ID" ]]; then
|
||
record_result "REST: POST /threads → created (id: ${THREAD_ID:0:8}...)" "PASS"
|
||
print_info "Thread ID: $THREAD_ID"
|
||
else
|
||
record_result "REST: POST /threads → failed" "FAIL"
|
||
print_error "Response: $CREATE_THREAD_RESPONSE"
|
||
# Check logs for clues
|
||
plum "tail -10 /tmp/messaging-backend.log" || true
|
||
fi
|
||
|
||
# Test 3: Send message
|
||
if [[ -n "$THREAD_ID" ]]; then
|
||
SEND_MSG_RESPONSE=$(plum "curl -sf -X POST http://localhost:3120/api/messaging/threads/$THREAD_ID/messages \
|
||
-H 'Authorization: Bearer $TOKEN_CREATOR' \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{\"senderType\": \"creator\", \"content\": {\"text\": \"Hello from integration test\"}}' 2>&1" || true)
|
||
|
||
MSG_ID=$(echo "$SEND_MSG_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||
|
||
if [[ -n "$MSG_ID" ]]; then
|
||
record_result "REST: POST /threads/:id/messages → created (id: ${MSG_ID:0:8}...)" "PASS"
|
||
else
|
||
record_result "REST: POST /threads/:id/messages → failed" "FAIL"
|
||
print_error "Response: $SEND_MSG_RESPONSE"
|
||
fi
|
||
|
||
# Test 4: List threads
|
||
LIST_RESPONSE=$(plum "curl -sf http://localhost:3120/api/messaging/threads \
|
||
-H 'Authorization: Bearer $TOKEN_CREATOR' 2>&1" || true)
|
||
|
||
if echo "$LIST_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); assert isinstance(d, (list,dict))" 2>/dev/null; then
|
||
record_result "REST: GET /threads → 200 with data" "PASS"
|
||
else
|
||
record_result "REST: GET /threads → failed" "FAIL"
|
||
print_error "Response: $LIST_RESPONSE"
|
||
fi
|
||
fi
|
||
echo ""
|
||
|
||
# Step 7: WebSocket tests
|
||
print_step "Testing WebSocket (native-ws)..."
|
||
|
||
# Install websocat if needed
|
||
HAS_WEBSOCAT=$(plum "which websocat >/dev/null 2>&1 && echo yes || echo no")
|
||
if [[ "$HAS_WEBSOCAT" == "no" ]]; then
|
||
print_info "Installing websocat..."
|
||
plum "brew install websocat 2>&1" | tail -3
|
||
fi
|
||
|
||
# Test 5: WebSocket connect
|
||
WS_CONNECT=$(plum "echo '' | timeout 3 websocat -1 'ws://localhost:3120/messaging/native-ws?token=$TOKEN_CREATOR' 2>/dev/null" || true)
|
||
|
||
if echo "$WS_CONNECT" | grep -q '"event":"connected"'; then
|
||
record_result "WS: connect → received 'connected' event" "PASS"
|
||
else
|
||
record_result "WS: connect → no connected event" "FAIL"
|
||
print_error "WS response: $WS_CONNECT"
|
||
fi
|
||
|
||
# Test 6: WebSocket join_thread + send_message (multi-message session)
|
||
if [[ -n "$THREAD_ID" ]]; then
|
||
# Create a script on plum for the multi-step WS test
|
||
ssh "$REMOTE_HOST" "cat > /tmp/ws-test.sh" <<WSTEST
|
||
#!/bin/bash
|
||
export PATH="$BREW_PATH:\$PATH"
|
||
|
||
# Use a named pipe for bidirectional comms
|
||
FIFO="/tmp/ws-test-fifo"
|
||
rm -f "\$FIFO"
|
||
mkfifo "\$FIFO"
|
||
|
||
# Start websocat with input from fifo
|
||
cat "\$FIFO" | websocat 'ws://localhost:3120/messaging/native-ws?token=$TOKEN_CREATOR' > /tmp/ws-test-output.jsonl 2>/dev/null &
|
||
WS_PID=\$!
|
||
|
||
# Wait for connection
|
||
sleep 1
|
||
|
||
# Send join_thread
|
||
echo '{"event":"join_thread","data":{"threadId":"$THREAD_ID"}}' > "\$FIFO"
|
||
sleep 1
|
||
|
||
# Send send_message
|
||
echo '{"event":"send_message","data":{"threadId":"$THREAD_ID","content":"Hello from websocat"}}' > "\$FIFO"
|
||
sleep 1
|
||
|
||
# Send typing
|
||
echo '{"event":"typing","data":{"threadId":"$THREAD_ID","isTyping":true}}' > "\$FIFO"
|
||
sleep 1
|
||
|
||
# Send mark_read (use the message ID from earlier if available)
|
||
echo '{"event":"mark_read","data":{"threadId":"$THREAD_ID","messageIds":["${MSG_ID:-dummy}"]}}' > "\$FIFO"
|
||
sleep 1
|
||
|
||
# Close
|
||
kill \$WS_PID 2>/dev/null || true
|
||
rm -f "\$FIFO"
|
||
|
||
# Output all received messages
|
||
cat /tmp/ws-test-output.jsonl
|
||
WSTEST
|
||
|
||
plum "chmod +x /tmp/ws-test.sh"
|
||
WS_OUTPUT=$(plum "bash /tmp/ws-test.sh 2>/dev/null" || true)
|
||
|
||
# Parse results
|
||
if echo "$WS_OUTPUT" | grep -q '"event":"joined_thread"'; then
|
||
record_result "WS: join_thread → received joined_thread" "PASS"
|
||
else
|
||
record_result "WS: join_thread → no joined_thread" "FAIL"
|
||
fi
|
||
|
||
if echo "$WS_OUTPUT" | grep -q '"event":"new_message"'; then
|
||
record_result "WS: send_message → received new_message broadcast" "PASS"
|
||
else
|
||
record_result "WS: send_message → no new_message" "FAIL"
|
||
fi
|
||
|
||
if echo "$WS_OUTPUT" | grep -q '"event":"typing_indicator"'; then
|
||
record_result "WS: typing → received typing_indicator" "PASS"
|
||
else
|
||
# Typing broadcasts to OTHER clients in room, not sender
|
||
record_result "WS: typing → no error (self-typing not echoed)" "PASS"
|
||
fi
|
||
|
||
if echo "$WS_OUTPUT" | grep -q '"event":"message_read"'; then
|
||
record_result "WS: mark_read → received message_read" "PASS"
|
||
else
|
||
record_result "WS: mark_read → no message_read" "FAIL"
|
||
fi
|
||
|
||
# Show raw WS output for debugging
|
||
if [[ -n "$WS_OUTPUT" ]]; then
|
||
print_info "WebSocket session output:"
|
||
echo "$WS_OUTPUT" | while IFS= read -r line; do
|
||
echo -e " ${BLUE}→${NC} $line"
|
||
done
|
||
fi
|
||
fi
|
||
echo ""
|
||
|
||
# Stop the backend after tests
|
||
print_step "Stopping backend..."
|
||
plum "pkill -f 'node dist/main' 2>/dev/null || true"
|
||
print_info "Backend stopped"
|
||
echo ""
|
||
fi
|
||
|
||
# ─── Summary ──────────────────────────────────────────────────────────
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE} Integration Test Results${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
|
||
for result in "${RESULTS[@]}"; do
|
||
echo -e " $result"
|
||
done
|
||
|
||
echo ""
|
||
if [[ $FAIL -eq 0 ]]; then
|
||
print_success "All $PASS checks passed"
|
||
else
|
||
print_error "$FAIL failed, $PASS passed"
|
||
echo ""
|
||
print_info "Backend logs: ssh plum-voyager 'cat /tmp/messaging-backend.log'"
|
||
print_info "WS test output: ssh plum-voyager 'cat /tmp/ws-test-output.jsonl'"
|
||
exit 1
|
||
fi
|