feat: add automated stress testing script for driver location tracking and trip lifecycle simulation
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:4010/api';
|
||||
const TENANT_ID = process.env.TENANT_ID || 'siro';
|
||||
const NUM_DRIVERS = parseInt(process.env.NUM_DRIVERS || '100', 10);
|
||||
const TRIP_RATE_SEC = parseInt(process.env.TRIP_RATE_SEC || '2', 10);
|
||||
const DRIVER_PING_INTERVAL_MS = parseInt(process.env.DRIVER_PING_INTERVAL_MS || '3000', 10);
|
||||
|
||||
async function api(method, path, body = null, token = null) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-tenant-id': TENANT_ID,
|
||||
'x-device-id': 'stress-test-device'
|
||||
}
|
||||
};
|
||||
if (token) options.headers['Authorization'] = `Bearer ${token}`;
|
||||
if (body) options.body = JSON.stringify(body);
|
||||
|
||||
const res = await fetch(`${API_URL}${path}`, options);
|
||||
const data = await res.json().catch(() => null);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`API Error: ${method} ${path} -> ${res.status} : ${JSON.stringify(data)}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
let drivers = [];
|
||||
let reqCount = 0;
|
||||
let errors = 0;
|
||||
let tripsCompleted = 0;
|
||||
|
||||
async function setupDriver(index) {
|
||||
const phone = `+96279${String(index).padStart(6, '0')}`;
|
||||
try {
|
||||
// Authenticate using dev bypass
|
||||
const auth = await api('POST', '/auth/verify-otp', { phone, code: '1234' });
|
||||
const token = auth.access_token;
|
||||
|
||||
// Auto-create driver profile if missing
|
||||
if (!auth.user.vehicle_plate) {
|
||||
await api('POST', '/drivers/apply', {
|
||||
service_class: 'economy',
|
||||
vehicle_make: 'Toyota',
|
||||
vehicle_model: 'Corolla',
|
||||
vehicle_plate: `${index}-TEST`
|
||||
}, token).catch(() => {});
|
||||
}
|
||||
|
||||
// Go online
|
||||
await api('PATCH', '/drivers/status', { online: true }, token);
|
||||
|
||||
const WS_URL = API_URL.replace('/api', '');
|
||||
const socket = io(WS_URL, { extraHeaders: { Authorization: `Bearer ${token}` }, reconnection: true });
|
||||
|
||||
const driverObj = { phone, token, socket, available: true };
|
||||
drivers.push(driverObj);
|
||||
|
||||
// Initial random location in Amman
|
||||
let lat = 31.95 + (Math.random() * 0.1);
|
||||
let lng = 35.91 + (Math.random() * 0.1);
|
||||
|
||||
// Start pinging location
|
||||
setInterval(() => {
|
||||
lat += 0.0001; // Move slightly
|
||||
socket.emit('driver:location', { lat, lng, speed: 40, heading: 90 });
|
||||
reqCount++;
|
||||
}, DRIVER_PING_INTERVAL_MS);
|
||||
|
||||
// Listen for trip offers
|
||||
socket.on('trip:offer', async (data) => {
|
||||
if (!driverObj.available) return;
|
||||
driverObj.available = false;
|
||||
const tripId = data.tripId || data.id;
|
||||
try {
|
||||
await api('POST', `/trips/${tripId}/accept`, null, token);
|
||||
|
||||
// Simulate trip flow timeline (shortened for stress test)
|
||||
setTimeout(async () => {
|
||||
await api('PATCH', `/trips/${tripId}/status`, { status: 'driver_arrived' }, token).catch(()=>{});
|
||||
await api('PATCH', `/trips/${tripId}/status`, { status: 'in_progress' }, token).catch(()=>{});
|
||||
await api('PATCH', `/trips/${tripId}/status`, { status: 'completed' }, token).catch(()=>{});
|
||||
driverObj.available = true; // Free to take next trip
|
||||
tripsCompleted++;
|
||||
}, 5000);
|
||||
|
||||
} catch (err) {
|
||||
driverObj.available = true;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
async function spawnRiderAndRequestTrip(index) {
|
||||
const phone = `+96278${String(index).padStart(6, '0')}`;
|
||||
try {
|
||||
const auth = await api('POST', '/auth/verify-otp', { phone, code: '1234' });
|
||||
const token = auth.access_token;
|
||||
|
||||
// Pick a location roughly where drivers are spawned
|
||||
const lat = 31.95 + (Math.random() * 0.1);
|
||||
const lng = 35.91 + (Math.random() * 0.1);
|
||||
|
||||
await api('POST', '/trips', {
|
||||
city: 'amman',
|
||||
service_class: 'economy',
|
||||
origin: { lat, lng },
|
||||
destination: { lat: lat + 0.05, lng: lng + 0.05 }
|
||||
}, token);
|
||||
reqCount++;
|
||||
|
||||
} catch (err) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
console.log(`\x1b[36m==================================================\x1b[0m`);
|
||||
console.log(`🚀 Starting Stress Test`);
|
||||
console.log(`\x1b[33mDrivers:\x1b[0m ${NUM_DRIVERS}`);
|
||||
console.log(`\x1b[33mTrips/Sec:\x1b[0m ${TRIP_RATE_SEC}`);
|
||||
console.log(`\x1b[33mLocation Ping Interval:\x1b[0m ${DRIVER_PING_INTERVAL_MS}ms`);
|
||||
console.log(`\x1b[36m==================================================\x1b[0m\n`);
|
||||
|
||||
// Throttle driver creation so we don't spam the DB at startup
|
||||
for (let i = 0; i < NUM_DRIVERS; i++) {
|
||||
setupDriver(i);
|
||||
if (i % 10 === 0) await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
console.log(`[System] Finished spawning drivers. Waiting 5s for sockets...`);
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
console.log(`[System] Commencing trip requests...`);
|
||||
|
||||
let riderIndex = 0;
|
||||
setInterval(() => {
|
||||
for (let i = 0; i < TRIP_RATE_SEC; i++) {
|
||||
spawnRiderAndRequestTrip(riderIndex++);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Monitor performance locally
|
||||
setInterval(() => {
|
||||
const currentRps = reqCount / 5;
|
||||
console.log(`\x1b[32m[Metrics]\x1b[0m Active Drivers: ${drivers.length} | API/WS Requests/sec: ${currentRps.toFixed(1)} | Trips Completed: ${tripsCompleted} | Errors: ${errors}`);
|
||||
reqCount = 0;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
start();
|
||||
Reference in New Issue
Block a user