Reorganize project: move docs to docs/ and test scripts to tests/

Moved 9 documentation .md files from root into docs/.
Moved 4 test scripts from root into tests/.
Updated cross-references in README.md and docs to reflect new paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-07 14:07:39 -05:00
parent 974f0e4a67
commit 84398ebdd0
14 changed files with 95 additions and 16 deletions

View File

@@ -0,0 +1,146 @@
#!/usr/bin/env node
/**
* Test script for session.ended WebSocket event
*
* This script:
* 1. Connects to the WebSocket server
* 2. Authenticates with a JWT token
* 3. Subscribes to a session
* 4. Listens for session.ended events
*
* Usage:
* node test-session-end-websocket.js <session_id> <jwt_token>
*
* Example:
* node test-session-end-websocket.js 17 your-jwt-token-here
*/
const WebSocket = require('ws');
// Configuration
const WS_URL = process.env.WS_URL || 'ws://localhost:5000/api/sessions/live';
const SESSION_ID = process.argv[2] || '17';
const JWT_TOKEN = process.argv[3];
if (!JWT_TOKEN) {
console.error('❌ Error: JWT token is required');
console.error('Usage: node test-session-end-websocket.js <session_id> <jwt_token>');
process.exit(1);
}
console.log('🚀 Testing session.ended WebSocket event');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(`📡 Connecting to: ${WS_URL}`);
console.log(`🎮 Session ID: ${SESSION_ID}`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
// Create WebSocket connection
const ws = new WebSocket(WS_URL);
ws.on('open', () => {
console.log('✅ Connected to WebSocket server\n');
// Step 1: Authenticate
console.log('🔐 Authenticating...');
ws.send(JSON.stringify({
type: 'auth',
token: JWT_TOKEN
}));
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
switch (message.type) {
case 'auth_success':
console.log('✅ Authentication successful\n');
// Step 2: Subscribe to session
console.log(`📻 Subscribing to session ${SESSION_ID}...`);
ws.send(JSON.stringify({
type: 'subscribe',
sessionId: parseInt(SESSION_ID)
}));
break;
case 'subscribed':
console.log(`✅ Subscribed to session ${message.sessionId}\n`);
console.log('👂 Listening for session.ended events...');
console.log(' (Close the session in the Picker to trigger the event)\n');
break;
case 'session.ended':
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('🎉 SESSION.ENDED EVENT RECEIVED!');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('\n📦 Event Data:');
console.log(JSON.stringify(message, null, 2));
console.log('\n✨ Event Details:');
console.log(` Session ID: ${message.data.session.id}`);
console.log(` Active: ${message.data.session.is_active === 1 ? 'Yes' : 'No'}`);
console.log(` Games Played: ${message.data.session.games_played}`);
console.log(` Timestamp: ${message.timestamp}`);
console.log('\n✅ Test successful! The bot should now announce the session end.\n');
// Close connection after receiving the event
setTimeout(() => {
console.log('👋 Closing connection...');
ws.close();
}, 1000);
break;
case 'auth_error':
case 'error':
console.error(`❌ Error: ${message.message}`);
ws.close();
process.exit(1);
break;
case 'pong':
// Ignore pong messages
break;
default:
console.log(`📨 Received message: ${message.type}`);
console.log(JSON.stringify(message, null, 2));
}
} catch (err) {
console.error('❌ Failed to parse message:', err);
console.error('Raw data:', data.toString());
}
});
ws.on('error', (err) => {
console.error('❌ WebSocket error:', err.message);
process.exit(1);
});
ws.on('close', (code, reason) => {
console.log(`\n🔌 Connection closed (code: ${code})`);
if (reason) {
console.log(` Reason: ${reason}`);
}
process.exit(0);
});
// Handle Ctrl+C gracefully
process.on('SIGINT', () => {
console.log('\n\n👋 Shutting down...');
ws.close();
process.exit(0);
});
// Send periodic pings to keep connection alive
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
// Clean up interval on close
ws.on('close', () => {
clearInterval(pingInterval);
});

182
tests/test-webhook-simple.sh Executable file
View File

@@ -0,0 +1,182 @@
#!/bin/bash
# Simple Webhook Test Script for macOS
# Uses webhook.site for easy testing
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
API_URL="${API_URL:-https://hso.cottongin.xyz/api}"
echo -e "\n${BLUE}╔════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Webhook Test Script (webhook.site) ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════╝${NC}\n"
# Check if JWT_TOKEN is set
if [ -z "$JWT_TOKEN" ]; then
echo -e "${RED}❌ ERROR: JWT_TOKEN not set!${NC}\n"
echo "Please set your JWT token:"
echo " 1. Get token:"
echo " curl -X POST https://hso.cottongin.xyz/api/auth/login \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -d '{\"key\":\"YOUR_ADMIN_KEY\"}'"
echo ""
echo " 2. Export the token:"
echo " export JWT_TOKEN=\"your_token_here\""
echo ""
echo " 3. Run this script:"
echo " ./test-webhook-simple.sh"
echo ""
exit 1
fi
echo -e "${GREEN}${NC} JWT_TOKEN is set"
echo -e "${GREEN}${NC} API URL: $API_URL"
echo ""
# Get a webhook.site URL
echo -e "${BLUE}📡 Getting webhook.site URL...${NC}"
WEBHOOK_RESPONSE=$(curl -s -X POST https://webhook.site/token)
WEBHOOK_UUID=$(echo "$WEBHOOK_RESPONSE" | grep -o '"uuid":"[^"]*' | cut -d'"' -f4)
if [ -z "$WEBHOOK_UUID" ]; then
echo -e "${RED}❌ Failed to get webhook.site URL${NC}"
exit 1
fi
WEBHOOK_URL="https://webhook.site/$WEBHOOK_UUID"
WEBHOOK_SECRET="test_secret_$(date +%s)"
echo -e "${GREEN}${NC} Webhook URL: $WEBHOOK_URL"
echo -e "${GREEN}${NC} View webhooks at: $WEBHOOK_URL"
echo -e "${GREEN}${NC} Secret: $WEBHOOK_SECRET"
echo ""
# Cleanup function
cleanup() {
echo ""
echo -e "${YELLOW}🧹 Cleaning up...${NC}"
if [ -n "$WEBHOOK_ID" ]; then
echo " Deleting webhook $WEBHOOK_ID..."
DELETE_RESPONSE=$(curl -s -X DELETE \
"$API_URL/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $JWT_TOKEN")
if echo "$DELETE_RESPONSE" | grep -q "deleted successfully"; then
echo -e " ${GREEN}${NC} Webhook deleted"
else
echo -e " ${YELLOW}${NC} Could not delete webhook (you may need to delete it manually)"
fi
fi
echo -e " ${GREEN}${NC} Cleanup complete"
echo ""
echo -e "${BLUE}👋 Goodbye!${NC}\n"
exit 0
}
# Trap Ctrl+C
trap cleanup SIGINT SIGTERM
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${BLUE}Starting Webhook Tests${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}\n"
# Test 1: Create webhook
echo -e "${YELLOW}📝 Test 1: Creating webhook...${NC}"
CREATE_RESPONSE=$(curl -s -X POST \
"$API_URL/webhooks" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Test Webhook\",
\"url\": \"$WEBHOOK_URL\",
\"secret\": \"$WEBHOOK_SECRET\",
\"events\": [\"game.added\"]
}")
if echo "$CREATE_RESPONSE" | grep -q '"id"'; then
WEBHOOK_ID=$(echo "$CREATE_RESPONSE" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
echo -e "${GREEN}${NC} Webhook created with ID: $WEBHOOK_ID"
else
echo -e "${RED}${NC} Failed to create webhook"
echo " Response: $CREATE_RESPONSE"
exit 1
fi
echo ""
# Test 2: List webhooks
echo -e "${YELLOW}📝 Test 2: Listing webhooks...${NC}"
LIST_RESPONSE=$(curl -s "$API_URL/webhooks" \
-H "Authorization: Bearer $JWT_TOKEN")
WEBHOOK_COUNT=$(echo "$LIST_RESPONSE" | grep -o '"id":' | wc -l | tr -d ' ')
echo -e "${GREEN}${NC} Found $WEBHOOK_COUNT webhook(s)"
echo ""
# Test 3: Send test webhook
echo -e "${YELLOW}📝 Test 3: Sending test webhook...${NC}"
TEST_RESPONSE=$(curl -s -X POST \
"$API_URL/webhooks/test/$WEBHOOK_ID" \
-H "Authorization: Bearer $JWT_TOKEN")
if echo "$TEST_RESPONSE" | grep -q "Test webhook sent"; then
echo -e "${GREEN}${NC} Test webhook sent"
else
echo -e "${RED}${NC} Failed to send test webhook"
echo " Response: $TEST_RESPONSE"
fi
echo ""
# Wait for webhook delivery
echo -e "${YELLOW}⏳ Waiting for webhook delivery (3 seconds)...${NC}"
sleep 3
echo ""
# Test 4: Check webhook logs
echo -e "${YELLOW}📝 Test 4: Checking webhook logs...${NC}"
LOGS_RESPONSE=$(curl -s "$API_URL/webhooks/$WEBHOOK_ID/logs?limit=10" \
-H "Authorization: Bearer $JWT_TOKEN")
LOG_COUNT=$(echo "$LOGS_RESPONSE" | grep -o '"id":' | wc -l | tr -d ' ')
echo -e "${GREEN}${NC} Found $LOG_COUNT log entries"
if [ "$LOG_COUNT" -gt 0 ]; then
echo ""
echo "Recent webhook deliveries:"
echo "$LOGS_RESPONSE" | python3 -c "import sys, json; print(json.dumps(json.load(sys.stdin), indent=2))" 2>/dev/null || echo "$LOGS_RESPONSE"
fi
echo ""
# Summary
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${BLUE}Test Summary${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}${NC} Webhook created: ID $WEBHOOK_ID"
echo -e "${GREEN}${NC} Test webhook sent"
echo -e "${GREEN}${NC} Webhook logs: $LOG_COUNT entries"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}\n"
echo -e "${GREEN}🎉 All tests completed!${NC}"
echo ""
echo -e "${BLUE}💡 Next steps:${NC}"
echo " 1. Visit $WEBHOOK_URL to see webhook deliveries"
echo " 2. Add a game to an active session in the Picker page"
echo " 3. Refresh webhook.site to see the real webhook"
echo " 4. Press Ctrl+C to cleanup and exit"
echo ""
echo -e "${YELLOW}⏳ Press Ctrl+C when done to cleanup...${NC}"
echo ""
# Wait for Ctrl+C
while true; do
sleep 1
done

294
tests/test-webhook.js Normal file
View File

@@ -0,0 +1,294 @@
#!/usr/bin/env node
/**
* Webhook Test Script
*
* This script creates a local webhook receiver and tests the webhook system.
*
* Usage:
* 1. Start your backend server (docker-compose up or npm run dev)
* 2. Run this script: node test-webhook.js
* 3. The script will:
* - Start a local webhook receiver on port 3001
* - Create a webhook in the API
* - Send a test webhook
* - Wait for incoming webhooks
*/
const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');
// Configuration
const API_URL = process.env.API_URL || 'http://localhost:5000';
const WEBHOOK_PORT = process.env.WEBHOOK_PORT || 3001;
const WEBHOOK_SECRET = 'test_secret_' + Math.random().toString(36).substring(7);
// You need to set this - get it from: curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d '{"key":"YOUR_ADMIN_KEY"}'
const JWT_TOKEN = process.env.JWT_TOKEN || '';
if (!JWT_TOKEN) {
console.error('\n❌ ERROR: JWT_TOKEN not set!');
console.error('\nPlease set your JWT token:');
console.error(' 1. Get token: curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d \'{"key":"YOUR_ADMIN_KEY"}\'');
console.error(' 2. Run: JWT_TOKEN="your_token_here" node test-webhook.js');
console.error(' OR: export JWT_TOKEN="your_token_here" && node test-webhook.js\n');
process.exit(1);
}
let webhookId = null;
let receivedWebhooks = [];
// Create Express app for webhook receiver
const app = express();
// Important: Parse JSON and keep raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
// Webhook receiver endpoint
app.post('/webhook/jackbox', (req, res) => {
console.log('\n📨 Webhook received!');
console.log('Headers:', {
'x-webhook-signature': req.headers['x-webhook-signature'],
'x-webhook-event': req.headers['x-webhook-event'],
'user-agent': req.headers['user-agent']
});
const signature = req.headers['x-webhook-signature'];
// Verify signature
if (!signature || !signature.startsWith('sha256=')) {
console.log('❌ Missing or invalid signature format');
return res.status(401).send('Missing or invalid signature');
}
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
// Timing-safe comparison
let isValid = false;
try {
isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch (err) {
console.log('❌ Signature verification failed:', err.message);
return res.status(401).send('Invalid signature');
}
if (!isValid) {
console.log('❌ Signature mismatch!');
console.log(' Expected:', expectedSignature);
console.log(' Received:', signature);
return res.status(401).send('Invalid signature');
}
console.log('✅ Signature verified!');
console.log('\nPayload:', JSON.stringify(req.body, null, 2));
if (req.body.event === 'game.added') {
const game = req.body.data.game;
console.log(`\n🎮 Game Added: ${game.title} from ${game.pack_name}`);
console.log(` Players: ${game.min_players}-${game.max_players}`);
console.log(` Session ID: ${req.body.data.session.id}`);
console.log(` Games Played: ${req.body.data.session.games_played}`);
}
receivedWebhooks.push(req.body);
res.status(200).send('OK');
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
webhooksReceived: receivedWebhooks.length
});
});
// Start webhook receiver
const server = app.listen(WEBHOOK_PORT, async () => {
console.log(`\n🚀 Webhook receiver started on http://localhost:${WEBHOOK_PORT}`);
console.log(`📍 Webhook endpoint: http://localhost:${WEBHOOK_PORT}/webhook/jackbox`);
console.log(`🔐 Secret: ${WEBHOOK_SECRET}\n`);
// Wait a moment for server to be ready
await new Promise(resolve => setTimeout(resolve, 500));
// Run tests
await runTests();
});
async function runTests() {
console.log('═══════════════════════════════════════════════════════');
console.log('Starting Webhook Tests');
console.log('═══════════════════════════════════════════════════════\n');
try {
// Test 1: Create webhook
console.log('📝 Test 1: Creating webhook...');
const createResponse = await fetch(`${API_URL}/api/webhooks`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Test Webhook',
url: `http://localhost:${WEBHOOK_PORT}/webhook/jackbox`,
secret: WEBHOOK_SECRET,
events: ['game.added']
})
});
if (!createResponse.ok) {
const error = await createResponse.text();
throw new Error(`Failed to create webhook: ${createResponse.status} ${error}`);
}
const webhook = await createResponse.json();
webhookId = webhook.id;
console.log(`✅ Webhook created with ID: ${webhookId}\n`);
// Test 2: List webhooks
console.log('📝 Test 2: Listing webhooks...');
const listResponse = await fetch(`${API_URL}/api/webhooks`, {
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!listResponse.ok) {
throw new Error(`Failed to list webhooks: ${listResponse.status}`);
}
const webhooks = await listResponse.json();
console.log(`✅ Found ${webhooks.length} webhook(s)\n`);
// Test 3: Send test webhook
console.log('📝 Test 3: Sending test webhook...');
const testResponse = await fetch(`${API_URL}/api/webhooks/test/${webhookId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!testResponse.ok) {
throw new Error(`Failed to send test webhook: ${testResponse.status}`);
}
console.log('✅ Test webhook sent\n');
// Wait for webhook to be received
console.log('⏳ Waiting for webhook delivery (5 seconds)...');
await new Promise(resolve => setTimeout(resolve, 5000));
if (receivedWebhooks.length > 0) {
console.log(`✅ Received ${receivedWebhooks.length} webhook(s)!\n`);
} else {
console.log('⚠️ No webhooks received yet. Check webhook logs.\n');
}
// Test 4: Check webhook logs
console.log('📝 Test 4: Checking webhook logs...');
const logsResponse = await fetch(`${API_URL}/api/webhooks/${webhookId}/logs?limit=10`, {
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (!logsResponse.ok) {
throw new Error(`Failed to get webhook logs: ${logsResponse.status}`);
}
const logs = await logsResponse.json();
console.log(`✅ Found ${logs.length} log entries\n`);
if (logs.length > 0) {
console.log('Recent webhook deliveries:');
logs.forEach((log, i) => {
console.log(` ${i + 1}. Event: ${log.event_type}, Status: ${log.response_status || 'pending'}, Time: ${log.created_at}`);
if (log.error_message) {
console.log(` Error: ${log.error_message}`);
}
});
console.log('');
}
// Summary
console.log('═══════════════════════════════════════════════════════');
console.log('Test Summary');
console.log('═══════════════════════════════════════════════════════');
console.log(`✅ Webhook created: ID ${webhookId}`);
console.log(`✅ Webhooks received: ${receivedWebhooks.length}`);
console.log(`✅ Webhook logs: ${logs.length} entries`);
console.log('═══════════════════════════════════════════════════════\n');
console.log('🎉 All tests completed!');
console.log('\n💡 Next steps:');
console.log(' 1. Add a game to an active session in the Picker page');
console.log(' 2. Watch for the webhook to be received here');
console.log(' 3. Press Ctrl+C to cleanup and exit\n');
} catch (error) {
console.error('\n❌ Test failed:', error.message);
console.error('\nMake sure:');
console.error(' - Backend server is running (http://localhost:5000)');
console.error(' - JWT_TOKEN is valid');
console.error(' - Port 3001 is available\n');
await cleanup();
process.exit(1);
}
}
async function cleanup() {
console.log('\n🧹 Cleaning up...');
if (webhookId) {
try {
console.log(` Deleting webhook ${webhookId}...`);
const deleteResponse = await fetch(`${API_URL}/api/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`
}
});
if (deleteResponse.ok) {
console.log(' ✅ Webhook deleted');
} else {
console.log(' ⚠️ Could not delete webhook (you may need to delete it manually)');
}
} catch (err) {
console.log(' ⚠️ Error during cleanup:', err.message);
}
}
console.log(' Stopping webhook receiver...');
server.close(() => {
console.log(' ✅ Server stopped');
console.log('\n👋 Goodbye!\n');
process.exit(0);
});
}
// Handle Ctrl+C
process.on('SIGINT', async () => {
console.log('\n\n⚠ Received interrupt signal');
await cleanup();
});
// Handle errors
process.on('uncaughtException', async (err) => {
console.error('\n❌ Uncaught exception:', err);
await cleanup();
});

268
tests/test-webhook.sh Executable file
View File

@@ -0,0 +1,268 @@
#!/bin/bash
# Webhook Test Script (Bash/curl version)
# This script tests the webhook system using only curl and bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
API_URL="${API_URL:-https://hso.cottongin.xyz/api}"
WEBHOOK_PORT="${WEBHOOK_PORT:-3001}"
WEBHOOK_SECRET="test_secret_$(date +%s)"
echo -e "\n${BLUE}╔════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Webhook Test Script (curl) ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════╝${NC}\n"
# Check if JWT_TOKEN is set
if [ -z "$JWT_TOKEN" ]; then
echo -e "${RED}❌ ERROR: JWT_TOKEN not set!${NC}\n"
echo "Please set your JWT token:"
echo " 1. Get token:"
echo " curl -X POST https://hso.cottongin.xyz/api/auth/login \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -d '{\"key\":\"YOUR_ADMIN_KEY\"}'"
echo ""
echo " 2. Export the token:"
echo " export JWT_TOKEN=\"your_token_here\""
echo ""
echo " 3. Run this script:"
echo " ./test-webhook.sh"
echo ""
exit 1
fi
# Check if nc (netcat) is available
if ! command -v nc &> /dev/null; then
echo -e "${RED}❌ ERROR: 'nc' (netcat) command not found!${NC}"
echo "Please install netcat to run this test."
exit 1
fi
echo -e "${GREEN}${NC} JWT_TOKEN is set"
echo -e "${GREEN}${NC} API URL: $API_URL"
echo -e "${GREEN}${NC} Webhook Port: $WEBHOOK_PORT"
echo -e "${GREEN}${NC} Webhook Secret: $WEBHOOK_SECRET"
echo ""
# Start a simple webhook receiver in the background
echo -e "${BLUE}🚀 Starting webhook receiver on port $WEBHOOK_PORT...${NC}"
# Create a named pipe for communication
FIFO="/tmp/webhook_test_$$"
mkfifo "$FIFO"
# Simple HTTP server using netcat
(
while true; do
{
# Read the request
read -r REQUEST
# Read headers until empty line
CONTENT_LENGTH=0
SIGNATURE=""
EVENT=""
while read -r HEADER; do
HEADER=$(echo "$HEADER" | tr -d '\r')
[ -z "$HEADER" ] && break
if [[ "$HEADER" =~ ^Content-Length:\ ([0-9]+) ]]; then
CONTENT_LENGTH="${BASH_REMATCH[1]}"
fi
if [[ "$HEADER" =~ ^X-Webhook-Signature:\ (.+) ]]; then
SIGNATURE="${BASH_REMATCH[1]}"
fi
if [[ "$HEADER" =~ ^X-Webhook-Event:\ (.+) ]]; then
EVENT="${BASH_REMATCH[1]}"
fi
done
# Read body if Content-Length is set
BODY=""
if [ "$CONTENT_LENGTH" -gt 0 ]; then
BODY=$(dd bs=1 count="$CONTENT_LENGTH" 2>/dev/null)
fi
# Log the webhook
if [ -n "$BODY" ]; then
echo "" >> "$FIFO"
echo "📨 Webhook received!" >> "$FIFO"
echo " Event: $EVENT" >> "$FIFO"
echo " Signature: $SIGNATURE" >> "$FIFO"
echo " Body: $BODY" >> "$FIFO"
echo "" >> "$FIFO"
fi
# Send response
echo "HTTP/1.1 200 OK"
echo "Content-Type: text/plain"
echo "Content-Length: 2"
echo ""
echo "OK"
} | nc -l -p "$WEBHOOK_PORT" -q 1
done
) &
WEBHOOK_PID=$!
# Display webhook output in background
tail -f "$FIFO" &
TAIL_PID=$!
# Give the server a moment to start
sleep 2
echo -e "${GREEN}${NC} Webhook receiver started (PID: $WEBHOOK_PID)"
echo -e "${GREEN}${NC} Listening on http://localhost:$WEBHOOK_PORT/webhook/jackbox"
echo ""
# Cleanup function
cleanup() {
echo ""
echo -e "${YELLOW}🧹 Cleaning up...${NC}"
if [ -n "$WEBHOOK_ID" ]; then
echo " Deleting webhook $WEBHOOK_ID..."
DELETE_RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
"$API_URL/api/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $JWT_TOKEN")
DELETE_CODE=$(echo "$DELETE_RESPONSE" | tail -n1)
if [ "$DELETE_CODE" = "200" ]; then
echo -e " ${GREEN}${NC} Webhook deleted"
else
echo -e " ${YELLOW}${NC} Could not delete webhook (you may need to delete it manually)"
fi
fi
echo " Stopping webhook receiver..."
kill $WEBHOOK_PID 2>/dev/null || true
kill $TAIL_PID 2>/dev/null || true
rm -f "$FIFO"
echo -e " ${GREEN}${NC} Cleanup complete"
echo ""
echo -e "${BLUE}👋 Goodbye!${NC}\n"
exit 0
}
# Trap Ctrl+C
trap cleanup SIGINT SIGTERM
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${BLUE}Starting Webhook Tests${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}\n"
# Test 1: Create webhook
echo -e "${YELLOW}📝 Test 1: Creating webhook...${NC}"
CREATE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"$API_URL/api/webhooks" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Test Webhook\",
\"url\": \"http://host.docker.internal:$WEBHOOK_PORT/webhook/jackbox\",
\"secret\": \"$WEBHOOK_SECRET\",
\"events\": [\"game.added\"]
}")
CREATE_CODE=$(echo "$CREATE_RESPONSE" | tail -n1)
CREATE_BODY=$(echo "$CREATE_RESPONSE" | head -n-1)
if [ "$CREATE_CODE" = "201" ]; then
WEBHOOK_ID=$(echo "$CREATE_BODY" | grep -o '"id":[0-9]*' | grep -o '[0-9]*')
echo -e "${GREEN}${NC} Webhook created with ID: $WEBHOOK_ID"
echo " Response: $CREATE_BODY"
else
echo -e "${RED}${NC} Failed to create webhook (HTTP $CREATE_CODE)"
echo " Response: $CREATE_BODY"
cleanup
fi
echo ""
# Test 2: List webhooks
echo -e "${YELLOW}📝 Test 2: Listing webhooks...${NC}"
LIST_RESPONSE=$(curl -s -w "\n%{http_code}" \
"$API_URL/api/webhooks" \
-H "Authorization: Bearer $JWT_TOKEN")
LIST_CODE=$(echo "$LIST_RESPONSE" | tail -n1)
LIST_BODY=$(echo "$LIST_RESPONSE" | head -n-1)
if [ "$LIST_CODE" = "200" ]; then
WEBHOOK_COUNT=$(echo "$LIST_BODY" | grep -o '"id":' | wc -l)
echo -e "${GREEN}${NC} Found $WEBHOOK_COUNT webhook(s)"
else
echo -e "${RED}${NC} Failed to list webhooks (HTTP $LIST_CODE)"
fi
echo ""
# Test 3: Send test webhook
echo -e "${YELLOW}📝 Test 3: Sending test webhook...${NC}"
TEST_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
"$API_URL/api/webhooks/test/$WEBHOOK_ID" \
-H "Authorization: Bearer $JWT_TOKEN")
TEST_CODE=$(echo "$TEST_RESPONSE" | tail -n1)
if [ "$TEST_CODE" = "200" ]; then
echo -e "${GREEN}${NC} Test webhook sent"
else
echo -e "${RED}${NC} Failed to send test webhook (HTTP $TEST_CODE)"
fi
echo ""
# Wait for webhook delivery
echo -e "${YELLOW}⏳ Waiting for webhook delivery (5 seconds)...${NC}"
sleep 5
echo ""
# Test 4: Check webhook logs
echo -e "${YELLOW}📝 Test 4: Checking webhook logs...${NC}"
LOGS_RESPONSE=$(curl -s -w "\n%{http_code}" \
"$API_URL/api/webhooks/$WEBHOOK_ID/logs?limit=10" \
-H "Authorization: Bearer $JWT_TOKEN")
LOGS_CODE=$(echo "$LOGS_RESPONSE" | tail -n1)
LOGS_BODY=$(echo "$LOGS_RESPONSE" | head -n-1)
if [ "$LOGS_CODE" = "200" ]; then
LOG_COUNT=$(echo "$LOGS_BODY" | grep -o '"id":' | wc -l)
echo -e "${GREEN}${NC} Found $LOG_COUNT log entries"
echo ""
echo "Recent webhook deliveries:"
echo "$LOGS_BODY" | python3 -m json.tool 2>/dev/null || echo "$LOGS_BODY"
else
echo -e "${RED}${NC} Failed to get webhook logs (HTTP $LOGS_CODE)"
fi
echo ""
# Summary
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${BLUE}Test Summary${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}${NC} Webhook created: ID $WEBHOOK_ID"
echo -e "${GREEN}${NC} Test webhook sent"
echo -e "${GREEN}${NC} Webhook logs: $LOG_COUNT entries"
echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}\n"
echo -e "${GREEN}🎉 All tests completed!${NC}"
echo ""
echo -e "${BLUE}💡 Next steps:${NC}"
echo " 1. Add a game to an active session in the Picker page"
echo " 2. Watch for the webhook to be received above"
echo " 3. Press Ctrl+C to cleanup and exit"
echo ""
echo -e "${YELLOW}⏳ Webhook receiver is still running...${NC}"
echo ""
# Keep running until Ctrl+C
wait $WEBHOOK_PID