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'
This commit is contained in:
311
backend/scripts/test-bot.js
Normal file
311
backend/scripts/test-bot.js
Normal file
@@ -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 <email> --password <password> --slug <event-slug>');
|
||||
console.log('\nOptions:');
|
||||
console.log(' --interval <seconds> 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);
|
||||
});
|
||||
Reference in New Issue
Block a user