import readline from 'readline'; 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'; 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': 'simulated-device-123' } }; 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) { console.error(`\n\x1b[31m[API Error] ${method} ${path} -> ${res.status}\x1b[0m`); console.error(JSON.stringify(data, null, 2)); throw new Error(`API Error: ${res.status}`); } return data; } async function run() { try { console.log('\n=========================================='); console.log('🚀 Tripz Backend Automated E2E Simulation'); console.log('==========================================\n'); // 1. Authenticate Demo Driver const driverPhone = '+962790000001'; console.log(`[Driver] Authenticating Demo Driver (${driverPhone})...`); const driverAuth = await api('POST', '/auth/verify-otp', { phone: driverPhone, code: '1234' }); const driverToken = driverAuth.access_token; console.log('\x1b[32m[Driver] Authenticated successfully!\x1b[0m\n'); // 2. Authenticate Demo Rider const riderPhone = '+962790000002'; console.log(`[Rider] Authenticating Demo Rider (${riderPhone})...`); const riderAuth = await api('POST', '/auth/verify-otp', { phone: riderPhone, code: '1234' }); const riderToken = riderAuth.access_token; console.log('\x1b[32m[Rider] Authenticated successfully!\x1b[0m\n'); // 3. Setup Driver Profile & Go Online console.log(`[Driver] Creating Driver profile...`); await api('POST', '/drivers/apply', { service_class: 'economy', vehicle_make: 'Toyota', vehicle_model: 'Prius', vehicle_plate: '12-3456' }, driverToken); console.log(`[Driver] Going Online...`); await api('PATCH', '/drivers/status', { online: true }, driverToken); console.log('\x1b[32m[Driver] Profile created and is now ONLINE.\x1b[0m\n'); // 4. Connect WebSockets console.log(`[Sockets] Connecting Rider and Driver via WebSockets...`); const WS_URL = API_URL.replace('/api', ''); const driverSocket = io(WS_URL, { extraHeaders: { Authorization: `Bearer ${driverToken}` } }); const riderSocket = io(WS_URL, { extraHeaders: { Authorization: `Bearer ${riderToken}` } }); let tripId = null; // Sockets Error Logging driverSocket.on('connect_error', (err) => console.error(`\x1b[31m[Driver WS] Error: ${err.message}\x1b[0m`)); riderSocket.on('connect_error', (err) => console.error(`\x1b[31m[Rider WS] Error: ${err.message}\x1b[0m`)); driverSocket.on('connect', () => console.log('\x1b[36m[Driver WS] Connected to Gateway\x1b[0m')); riderSocket.on('connect', () => console.log('\x1b[36m[Rider WS] Connected to Gateway\x1b[0m')); driverSocket.on('trip:offer', async (data) => { console.log(`\n\x1b[35m[Driver WS] 🔔 RECEIVED OFFER for Trip ${data.tripId || data.id}!\x1b[0m`); console.log(`Offer Payload:`, JSON.stringify(data, null, 2)); tripId = data.tripId || data.id; try { console.log(`[Driver] Accepting Trip...`); await api('POST', `/trips/${tripId}/accept`, null, driverToken); console.log(`\x1b[32m[Driver] Trip Accepted successfully!\x1b[0m`); } catch (err) { console.error(`\x1b[31m[Driver] Failed to accept trip!\x1b[0m`); } }); riderSocket.on('trip:update', (data) => { console.log(`\x1b[33m[Rider WS] Trip status updated: ${data.status}\x1b[0m`); }); await new Promise(r => setTimeout(r, 2000)); // 5. Driver Location Pings let driverLat = 32.0645; let driverLng = 36.0827; const locationInterval = setInterval(() => { driverLat += 0.0001; driverSocket.emit('driver:location', { lat: driverLat, lng: driverLng, speed: 40, heading: 90 }); console.log(`[Driver WS] 📍 Location ping: ${driverLat.toFixed(5)}, ${driverLng.toFixed(5)}`); }, 3000); // Send first ping IMMEDIATELY driverSocket.emit('driver:location', { lat: driverLat, lng: driverLng, speed: 40, heading: 90 }); console.log(`[Driver WS] 📍 Initial Location ping: ${driverLat.toFixed(5)}, ${driverLng.toFixed(5)}`); // Wait for the server to process the location update into Redis await new Promise(r => setTimeout(r, 1500)); // 6. Rider Requests Trip console.log(`\n[Rider] Requesting Trip from Zarqa...`); const tripRequest = await api('POST', '/trips', { city: 'amman', service_class: 'economy', origin: { lat: 32.0650, lng: 36.0830 }, destination: { lat: 32.0700, lng: 36.0900 } }, riderToken); console.log(`\x1b[32m[Rider] Trip created: ${tripRequest.trip?.id || tripRequest.id}\x1b[0m`); console.log(`[System] Offered to \x1b[33m${tripRequest.offeredDrivers}\x1b[0m drivers nearby.`); // 7. Simulate Trip Flow await new Promise(r => setTimeout(r, 10000)); if (tripId) { try { console.log(`\n[Driver] Arriving at pickup...`); await api('PATCH', `/trips/${tripId}/status`, { status: 'driver_arrived' }, driverToken); await new Promise(r => setTimeout(r, 5000)); console.log(`\n[Driver] Starting Trip...`); await api('PATCH', `/trips/${tripId}/status`, { status: 'in_progress' }, driverToken); await new Promise(r => setTimeout(r, 5000)); console.log(`\n[Driver] Completing Trip...`); await api('PATCH', `/trips/${tripId}/status`, { status: 'completed' }, driverToken); console.log(`\n\x1b[32m🎉 E2E Simulation Completed Successfully!\x1b[0m\n`); } catch(e) { console.error(`\x1b[31m[Simulation Error] ${e.message}\x1b[0m`); } } else { console.log(`\n\x1b[31mFailed to receive offer!\x1b[0m`); console.log(`[Hint] Check if offeredDrivers was 0. If it was 0, the backend didn't find the driver in Redis. If it was > 0, the backend emitted the socket event but the driver didn't receive it.\n`); } clearInterval(locationInterval); driverSocket.disconnect(); riderSocket.disconnect(); process.exit(0); } catch (error) { console.error(`\n\x1b[31m[Fatal Error] Simulation Crashed: ${error.message}\x1b[0m\n`); process.exit(1); } } run();