feat: add location intelligence engine and E2E simulation script

This commit is contained in:
Hamza-Ayed
2026-07-18 19:08:52 +03:00
parent a15e65c42e
commit fef1235fc9
14 changed files with 539 additions and 3 deletions
+115
View File
@@ -0,0 +1,115 @@
{
"name": "tripz-e2e-tester",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tripz-e2e-tester",
"version": "1.0.0",
"dependencies": {
"socket.io-client": "^4.7.5"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-client": {
"version": "6.6.6",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz",
"integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.21.0",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "tripz-e2e-tester",
"version": "1.0.0",
"type": "module",
"dependencies": {
"socket.io-client": "^4.7.5"
}
}
+163
View File
@@ -0,0 +1,163 @@
import readline from 'readline';
import { io } from 'socket.io-client';
const API_URL = process.env.API_URL || 'http://localhost:4010';
const TENANT_ID = 'tripz'; // Change this if you have a different slug
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const ask = (query) => new Promise((resolve) => rl.question(query, resolve));
// --- API Helpers ---
async function api(method, path, body = null, token = null) {
const headers = {
'Content-Type': 'application/json',
'x-tenant-id': TENANT_ID,
'x-device-id': 'simulated-device-123'
};
if (token) headers['Authorization'] = `Bearer ${token}`;
const options = { method, headers };
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(`\x1b[31m[API Error] ${method} ${path} -> ${res.status}\x1b[0m`, data);
throw new Error(`API Error: ${res.status}`);
}
return data;
}
// --- Main Simulation ---
async function run() {
console.log('\n\x1b[36m==========================================');
console.log('🚀 Tripz Backend Live Simulation Engine');
console.log('==========================================\x1b[0m\n');
// 1. Authentication
const driverPhone = await ask('📞 Enter Driver Phone Number (e.g. +962791234567): ');
console.log(`[Driver] Requesting OTP for ${driverPhone}...`);
await api('POST', '/auth/send-otp', { phone: driverPhone });
const driverOtp = await ask(`🔑 Enter OTP received by Driver ${driverPhone}: `);
const driverAuth = await api('POST', '/auth/verify-otp', { phone: driverPhone, code: driverOtp });
const driverToken = driverAuth.access_token;
console.log('\x1b[32m[Driver] Authenticated successfully!\x1b[0m');
const riderPhone = await ask('\n📞 Enter Rider Phone Number (e.g. +962781234567): ');
console.log(`[Rider] Requesting OTP for ${riderPhone}...`);
await api('POST', '/auth/send-otp', { phone: riderPhone });
const riderOtp = await ask(`🔑 Enter OTP received by Rider ${riderPhone}: `);
const riderAuth = await api('POST', '/auth/verify-otp', { phone: riderPhone, code: riderOtp });
const riderToken = riderAuth.access_token;
console.log('\x1b[32m[Rider] Authenticated successfully!\x1b[0m\n');
// 2. 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');
// 3. Connect WebSockets
console.log(`[Sockets] Connecting Rider and Driver via WebSockets...`);
const driverSocket = io(API_URL, { extraHeaders: { Authorization: `Bearer ${driverToken}` } });
const riderSocket = io(API_URL, { extraHeaders: { Authorization: `Bearer ${riderToken}` } });
let tripId = null;
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:dispatch', async (data) => {
console.log(`\n\x1b[35m[Driver WS] 🔔 RECEIVED DISPATCH for Trip ${data.id}!\x1b[0m`);
tripId = data.id;
console.log(`[Driver] Accepting Trip ${tripId}...`);
await api('POST', `/trips/${tripId}/accept`, null, driverToken);
console.log('\x1b[32m[Driver] Trip Accepted!\x1b[0m');
// Join Trip Room
driverSocket.emit('trip:join', { tripId });
riderSocket.emit('trip:join', { tripId });
console.log(`[Sockets] Both joined trip room: ${tripId}\n`);
});
riderSocket.on('trip:status_changed', (data) => {
console.log(`\x1b[33m[Rider WS] Trip Status Changed -> ${data.status}\x1b[0m`);
});
// 4. Driver Location Pings
let driverLat = 32.0645; // Zarqa Coordinates
let driverLng = 36.0827;
const locationInterval = setInterval(() => {
// Driver moves slightly
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);
// Wait a few seconds before Rider requests
await new Promise(r => setTimeout(r, 5000));
// 5. 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 }, // Zarqa origin
destination: { lat: 32.0700, lng: 36.0900 }
}, riderToken);
console.log(`\x1b[32m[Rider] Trip created: ${tripRequest.id}\x1b[0m`);
// 6. Simulate Trip Flow (Wait 10 seconds per state)
await new Promise(r => setTimeout(r, 10000));
if (tripId) {
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);
console.log(`\n[System] Simulating 1-minute ride to destination...`);
// Simulate 30 seconds wait
await new Promise(r => setTimeout(r, 30000));
console.log(`[System] Halfway there...`);
await new Promise(r => setTimeout(r, 30000));
console.log(`\n[Driver] Completing Trip...`);
await api('PATCH', `/trips/${tripId}/status`, { status: 'completed' }, driverToken);
await new Promise(r => setTimeout(r, 2000));
console.log(`\n[Driver] Collecting Cash...`);
await api('PATCH', `/trips/${tripId}/status`, { status: 'paid' }, driverToken);
console.log('\n\x1b[32m🎉 Simulation Completed Successfully! 🎉\x1b[0m\n');
} else {
console.log('\n\x1b[31mFailed to receive dispatch!\x1b[0m');
}
// Cleanup
clearInterval(locationInterval);
driverSocket.disconnect();
riderSocket.disconnect();
rl.close();
}
run().catch(err => {
console.error('\x1b[31mSimulation Failed!\x1b[0m', err);
process.exit(1);
});