From 671b16cb82c1e70b69ec2201fafa33f955dee586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Sat, 29 Nov 2025 19:00:43 +0100 Subject: [PATCH] feat(backend): add test bot for automated event chat testing Add test-bot.js script that simulates a user participating in event chat for testing purposes. Features: - Authenticates as user via API - Checks in to event using QR code token - Connects to Socket.IO and joins event room - Sends random messages at configurable intervals - Auto-accepts recording suggestions Usage from container: docker compose exec backend sh -c 'API_URL=http://localhost:3000 node scripts/test-bot.js --email user@example.com --password pass --slug event-slug --interval 10' --- backend/scripts/test-bot.js | 311 ++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 backend/scripts/test-bot.js diff --git a/backend/scripts/test-bot.js b/backend/scripts/test-bot.js new file mode 100644 index 0000000..a9af014 --- /dev/null +++ b/backend/scripts/test-bot.js @@ -0,0 +1,311 @@ +#!/usr/bin/env node + +/** + * Test Bot for spotlight.cam + * + * Usage: + * # From inside backend container: + * docker compose exec backend sh -c 'API_URL=http://localhost:3000 node scripts/test-bot.js --email bot@example.com --password Bot123! --slug test-event-2024' + * + * # From host (if backend dependencies are installed): + * cd backend && API_URL=http://localhost:3001 node scripts/test-bot.js --email bot@example.com --password Bot123! --slug test-event-2024 + * + * Options: + * --email Bot user email + * --password Bot user password + * --slug Event slug to join + * --interval Message interval in seconds (default: 15) + * + * Environment: + * API_URL Backend API URL (default: http://localhost:3001) + * Use http://localhost:3000 when running inside container + */ + +const io = require('socket.io-client'); + +// Parse CLI arguments +const args = process.argv.slice(2); +const getArg = (flag) => { + const index = args.indexOf(flag); + return index !== -1 ? args[index + 1] : null; +}; + +const email = getArg('--email'); +const password = getArg('--password'); +const slug = getArg('--slug'); +const interval = parseInt(getArg('--interval')) || 15; + +if (!email || !password || !slug) { + console.error('āŒ Missing required arguments!'); + console.log('\nUsage:'); + console.log(' node backend/scripts/test-bot.js --email --password --slug '); + console.log('\nOptions:'); + console.log(' --interval Message interval (default: 15)'); + process.exit(1); +} + +const API_URL = process.env.API_URL || 'http://localhost:3001'; + +// Random messages pool +const RANDOM_MESSAGES = [ + 'Hello everyone! šŸ‘‹', + 'Great to be here!', + 'Anyone else excited for this event?', + 'This is going to be awesome!', + 'Can\'t wait to dance! šŸ’ƒ', + 'Hey there!', + 'Looking forward to this!', + 'Who else is competing?', + 'Good luck everyone!', + 'Let\'s do this! šŸ”„', + 'Anyone want to practice later?', + 'What division are you in?', + 'First time at this event?', + 'The energy here is amazing!', + 'See you on the dance floor!', +]; + +let token = null; +let socket = null; +let userId = null; +let eventId = null; +let messageInterval = null; + +// Login to get JWT token +async function login() { + try { + console.log(`šŸ” Logging in as ${email}...`); + const response = await fetch(`${API_URL}/api/auth/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const responseData = await response.json(); + token = responseData.data.token; + userId = responseData.data.user.id; + console.log(`āœ… Logged in! User ID: ${userId}`); + console.log(` Token: ${token.substring(0, 20)}...`); + return token; + } catch (error) { + console.error('āŒ Login failed:', error.message); + process.exit(1); + } +} + +// Join event (check-in using token) +async function joinEvent() { + try { + console.log(`šŸ“ Getting event details for: ${slug}...`); + + // Get event details which includes the check-in token + const detailsResponse = await fetch(`${API_URL}/api/events/${slug}/details`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + } + }); + + if (!detailsResponse.ok) { + const errorData = await detailsResponse.json(); + throw new Error(errorData.error || `HTTP error! status: ${detailsResponse.status}`); + } + + const detailsData = await detailsResponse.json(); + eventId = detailsData.data.event.id; + const checkinToken = detailsData.data.checkin?.token; + + if (!checkinToken) { + throw new Error('No check-in token available for this event'); + } + + console.log(` Event ID: ${eventId}`); + console.log(`šŸ“ Checking in to event...`); + + // Check in to the event using the token + const checkinResponse = await fetch(`${API_URL}/api/events/checkin/${checkinToken}`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }); + + if (!checkinResponse.ok) { + const errorData = await checkinResponse.json(); + + // If already checked in, that's fine + if (errorData.alreadyCheckedIn) { + console.log(`ā„¹ļø Already checked in to this event`); + return eventId; + } + + throw new Error(errorData.error || `HTTP error! status: ${checkinResponse.status}`); + } + + const checkinData = await checkinResponse.json(); + if (checkinData.alreadyCheckedIn) { + console.log(`ā„¹ļø Already checked in to this event`); + } else { + console.log(`āœ… Checked in to event!`); + } + + return eventId; + } catch (error) { + console.error('āŒ Join event failed:', error.message); + process.exit(1); + } +} + +// Connect to Socket.IO +function connectSocket() { + return new Promise((resolve, reject) => { + console.log(`šŸ”Œ Connecting to Socket.IO...`); + + socket = io(API_URL, { + auth: { token }, + transports: ['websocket', 'polling'], + }); + + socket.on('connect', () => { + console.log(`āœ… Socket connected! Socket ID: ${socket.id}`); + + // Join event room + console.log(`🚪 Joining event room for: ${slug}...`); + socket.emit('join_event_room', { slug }); + + resolve(); + }); + + socket.on('connect_error', (error) => { + console.error('āŒ Socket connection error:', error.message); + reject(error); + }); + + socket.on('disconnect', (reason) => { + console.log(`āš ļø Socket disconnected: ${reason}`); + if (reason === 'io server disconnect') { + // Server disconnected, try to reconnect + socket.connect(); + } + }); + + // Listen for messages + socket.on('new_message', (data) => { + if (data.userId !== userId) { + console.log(`šŸ’¬ Message from ${data.username}: ${data.content}`); + } + }); + + // Listen for recording suggestions + socket.on('suggestion_created', async (data) => { + console.log(`šŸ“¹ Recording suggestion received:`, data); + + // Auto-accept if it's for us (we're the recorder) + if (data.suggestion && data.suggestion.recorderId === userId) { + console.log(` Auto-accepting suggestion ${data.suggestion.id}...`); + await acceptSuggestion(data.suggestion.id); + } + }); + + socket.on('error', (error) => { + console.error('āŒ Socket error:', error); + }); + }); +} + +// Send random message +function sendRandomMessage() { + const message = RANDOM_MESSAGES[Math.floor(Math.random() * RANDOM_MESSAGES.length)]; + + console.log(`šŸ“¤ Sending: "${message}"`); + socket.emit('send_event_message', { + content: message, + }); +} + +// Accept recording suggestion +async function acceptSuggestion(suggestionId) { + try { + const response = await fetch( + `${API_URL}/api/events/${slug}/suggestions/${suggestionId}/status`, + { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ status: 'accepted' }), + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + console.log(`āœ… Accepted suggestion ${suggestionId}`); + } catch (error) { + console.error(`āŒ Failed to accept suggestion:`, error.message); + } +} + +// Main function +async function main() { + console.log('šŸ¤– Test Bot Starting...\n'); + console.log(` Email: ${email}`); + console.log(` Event: ${slug}`); + console.log(` Message Interval: ${interval}s\n`); + + // Login + await login(); + + // Join event + await joinEvent(); + + // Connect socket + await connectSocket(); + + console.log(`\nāœ… Bot is ready!`); + console.log(` Sending random messages every ${interval} seconds`); + console.log(` Auto-accepting recording suggestions`); + console.log(` Press Ctrl+C to stop\n`); + + // Start sending random messages + messageInterval = setInterval(() => { + sendRandomMessage(); + }, interval * 1000); + + // Send first message immediately + setTimeout(() => sendRandomMessage(), 2000); +} + +// Handle graceful shutdown +process.on('SIGINT', () => { + console.log('\n\nšŸ›‘ Shutting down bot...'); + + if (messageInterval) { + clearInterval(messageInterval); + } + + if (socket) { + socket.disconnect(); + } + + console.log('šŸ‘‹ Goodbye!'); + process.exit(0); +}); + +// Run +main().catch((error) => { + console.error('āŒ Fatal error:', error); + process.exit(1); +});