const { io } = require('socket.io-client'); const axios = require('axios'); const { execSync } = require('child_process'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const argv = yargs(hideBin(process.argv)) .option('trips', { alias: 't', type: 'number', description: 'Number of concurrent trips to simulate', default: 1 }) .option('base-url', { alias: 'b', type: 'string', description: 'Base URL for backend APIs', default: 'http://localhost/backend' }) .option('socket-url', { alias: 's', type: 'string', description: 'Location server socket URL', default: 'ws://localhost:2020' }) .argv; // Helper to get JWT for mock driver function getMockDriverJwt(driverId) { try { const output = execSync(`php generate_mock_data.php ${driverId}`).toString(); return JSON.parse(output).jwt; } catch (err) { console.error(`Failed to generate JWT for driver ${driverId}`, err.message); return 'mock_jwt'; } } async function simulateTrip(tripIndex) { const driverId = 900000 + tripIndex; const passengerId = 800000 + tripIndex; const driverJwt = getMockDriverJwt(driverId); console.log(`[Trip ${tripIndex}] Starting simulation... Driver: ${driverId}, Passenger: ${passengerId}`); // 1. Connect Driver to Socket const socket = io(argv.socketUrl, { query: { driver_id: driverId, platform: 'android', jwt: driverJwt }, transports: ['websocket'] }); return new Promise((resolve, reject) => { let rideId = null; socket.on('connect', async () => { console.log(`[Trip ${tripIndex}] Driver ${driverId} connected to socket.`); // 2. Passenger Requests Ride try { const addRideFormData = new URLSearchParams({ passenger_id: passengerId, start_location: '31.95,35.91', // Amman end_location: '31.96,35.92', price: '5', price_token: 'dummy', distance: '2', carType: 'Economy', passenger_name: 'Test Passenger', passenger_phone: '0790000000', start_name: 'Start', end_name: 'End' }); const reqStart = Date.now(); const res = await axios.post(`${argv.baseUrl}/ride/rides/add_ride.php`, addRideFormData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); // Note: the backend add_ride.php might return the newly created ride ID console.log(`[Trip ${tripIndex}] Passenger requested ride. Response time: ${Date.now() - reqStart}ms`); } catch (err) { console.error(`[Trip ${tripIndex}] Error requesting ride:`, err.message); socket.disconnect(); return reject(err); } }); socket.on('market_new_ride', async (payload) => { rideId = payload.id; console.log(`[Trip ${tripIndex}] Driver ${driverId} received ride offer #${rideId}. Accepting...`); // 3. Driver Accepts Ride try { const acceptFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'accepted' }); const acceptStart = Date.now(); await axios.post(`${argv.baseUrl}/ride/rides/acceptRide.php`, acceptFormData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log(`[Trip ${tripIndex}] Driver ${driverId} accepted ride #${rideId}. Response time: ${Date.now() - acceptStart}ms`); // 4. Simulate Driving (Updates Location) let steps = 0; const driveInterval = setInterval(async () => { steps++; socket.emit('update_location', { lat: 31.95 + (steps * 0.001), lng: 35.91 + (steps * 0.001), heading: 90, speed: 40, status: 'on' }); if (steps === 3) { // Driver Arrived / Starts Ride const startFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'start' }); await axios.post(`${argv.baseUrl}/ride/rides/start_ride.php`, startFormData.toString()); console.log(`[Trip ${tripIndex}] Driver ${driverId} started ride #${rideId}`); } if (steps === 6) { clearInterval(driveInterval); // 5. Finish Ride const finishFormData = new URLSearchParams({ id: rideId, driver_id: driverId, status: 'finish' }); await axios.post(`${argv.baseUrl}/ride/rides/finish_ride_updates.php`, finishFormData.toString()); console.log(`[Trip ${tripIndex}] Driver ${driverId} finished ride #${rideId}.`); socket.disconnect(); resolve(`Trip ${tripIndex} completed successfully.`); } }, 1000); } catch (err) { console.error(`[Trip ${tripIndex}] Error during ride execution:`, err.message); socket.disconnect(); reject(err); } }); socket.on('disconnect', () => { console.log(`[Trip ${tripIndex}] Driver ${driverId} disconnected.`); }); }); } async function runTest() { console.log(`🚀 Starting Load Test with ${argv.trips} concurrent trips`); const promises = []; for (let i = 1; i <= argv.trips; i++) { promises.push(simulateTrip(i)); // Add a slight stagger so we don't overwhelm immediately await new Promise(r => setTimeout(r, 100)); } try { await Promise.all(promises); console.log("✅ All simulated trips finished successfully."); } catch (e) { console.error("❌ Some trips failed.", e); } } runTest();