feat: setup app review accounts and automated E2E simulation

This commit is contained in:
Hamza-Ayed
2026-07-18 21:08:42 +03:00
parent 836c77bb08
commit 79cb19d4f8
3 changed files with 61 additions and 79 deletions
+50 -74
View File
@@ -5,28 +5,27 @@ const API_URL = process.env.API_URL || 'http://localhost:4010/api';
// slug المستأجر — «siro» هو المزروع على السيرفر (راجع لوق [Seed] عند الإقلاع).
const TENANT_ID = process.env.TENANT_ID || 'siro';
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'
const options = {
method,
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);
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(`\x1b[31m[API Error] ${method} ${path} -> ${res.status}\x1b[0m`, data);
throw new Error(`API Error: ${res.status}`);
@@ -34,32 +33,28 @@ async function api(method, path, body = null, token = null) {
return data;
}
// --- Main Simulation ---
async function run() {
console.log('\n\x1b[36m==========================================');
console.log('🚀 Tripz Backend Live Simulation Engine');
console.log('==========================================\x1b[0m\n');
console.log('\n==========================================');
console.log('🚀 Tripz Backend Automated E2E Simulation');
console.log('==========================================\n');
// 1. Authentication
const driverPhone = await ask('📞 Enter Driver Phone Number (e.g. +962791234567): ');
console.log(`[Driver] Requesting OTP for ${driverPhone}...`);
// 1. Authenticate Demo Driver
const driverPhone = '+962790000001';
console.log(`[Driver] Authenticating Demo Driver (${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 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');
console.log('\x1b[32m[Driver] Authenticated successfully!\x1b[0m\n');
const riderPhone = await ask('\n📞 Enter Rider Phone Number (e.g. +962781234567): ');
console.log(`[Rider] Requesting OTP for ${riderPhone}...`);
// 2. Authenticate Demo Rider
const riderPhone = '+962790000002';
console.log(`[Rider] Authenticating Demo Rider (${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 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');
// 2. Setup Driver Profile & Go Online
// 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);
@@ -67,7 +62,7 @@ async function run() {
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
// 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}` } });
@@ -82,35 +77,29 @@ async function run() {
console.log(`\n\x1b[35m[Driver WS] 🔔 RECEIVED DISPATCH for Trip ${data.id}!\x1b[0m`);
tripId = data.id;
console.log(`[Driver] Accepting Trip ${tripId}...`);
console.log(`[Driver] Accepting Trip...`);
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`);
console.log(`\x1b[32m[Driver] Trip Accepted successfully!\x1b[0m`);
});
riderSocket.on('trip:status_changed', (data) => {
console.log(`\x1b[33m[Rider WS] Trip Status Changed -> ${data.status}\x1b[0m`);
riderSocket.on('trip:updated', (data) => {
console.log(`\x1b[33m[Rider WS] Trip status updated: ${data.status}\x1b[0m`);
});
// 4. Driver Location Pings
await new Promise(r => setTimeout(r, 2000));
// 5. Driver Location Pings
let driverLat = 32.0645; // Zarqa Coordinates
let driverLng = 36.0827;
const locationInterval = setInterval(() => {
// Driver moves slightly
driverLat += 0.0001;
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
// 6. Rider Requests Trip
console.log(`\n[Rider] Requesting Trip from Zarqa...`);
const tripRequest = await api('POST', '/trips', {
city: 'amman',
@@ -118,48 +107,35 @@ async function run() {
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)
console.log(`\x1b[32m[Rider] Trip created: ${tripRequest.trip.id}\x1b[0m`);
// 7. 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));
await new Promise(r => setTimeout(r, 5000));
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');
console.log(`\n\x1b[32m🎉 E2E Simulation Completed Successfully!\x1b[0m\n`);
} else {
console.log('\n\x1b[31mFailed to receive dispatch!\x1b[0m');
console.log(`\n\x1b[31mFailed to receive dispatch!\x1b[0m\n`);
}
// Cleanup
clearInterval(locationInterval);
driverSocket.disconnect();
riderSocket.disconnect();
rl.close();
process.exit(0);
}
run().catch(err => {
console.error('\x1b[31mSimulation Failed!\x1b[0m', err);
process.exit(1);
});
run().catch(console.error);
+7 -4
View File
@@ -83,8 +83,10 @@ export class AuthService {
const ttl = this.config.get<number>('auth.otpTtl') ?? 300;
await this.redis.set(this.otpKey(tenant.id, canonical), code, 'EX', ttl);
if (this.devMode) {
this.logger.log(`OTP (dev) tenant=${tenant.slug} phone=${canonical} => ${code}`);
// أرقام مخصصة لاختبارات E2E ومراجعي Apple/Google (لا تستهلك رصيد).
const isAppReviewAccount = ['+962790000001', '+962790000002'].includes(canonical);
if (this.devMode || isAppReviewAccount) {
this.logger.log(`OTP (dev/review) tenant=${tenant.slug} phone=${canonical} => ${code}`);
return { success: true, message: 'OTP sent (dev)', dev_code: code };
}
@@ -105,8 +107,9 @@ export class AuthService {
// بصيغة مختلفة قليلاً عن مرة الإرسال (مثال المالك: "01" مقابل "1").
const canonical = this.phones.normalize(phone, tenant.countryPack);
// في وضع التطوير: الرمز الثابت 1234 يمرّ دائماً (تسهيل الاختبار).
const devBypass = this.devMode && code === '1234';
// في وضع التطوير أو حسابات مراجعة آبل/جوجل: الرمز الثابت 1234 يمرّ دائماً.
const isAppReviewAccount = ['+962790000001', '+962790000002'].includes(canonical);
const devBypass = (this.devMode || isAppReviewAccount) && code === '1234';
if (!devBypass) {
const attemptsKey = this.otpAttemptsKey(tenant.id, canonical);
const attempts = await this.redis.incr(attemptsKey);
@@ -35,6 +35,9 @@ export class DriversService {
userId: string,
data: Partial<Driver>,
): Promise<Driver> {
const user = await this.users.findById(tenantId, userId);
const isAppReview = ['+962790000001', '+962790000002'].includes(user?.phone ?? '');
let driver = await this.findByUser(tenantId, userId);
if (!driver) {
// حدّ الباقة يُفرض على السيرفر (docs/19 — K4): السائق رقم 501 يُرفض
@@ -55,7 +58,7 @@ export class DriversService {
vehicle_plate: data.vehicle_plate,
vehicle_color: data.vehicle_color,
docs: data.docs ?? {},
verification_status: 'pending',
verification_status: isAppReview ? 'approved' : 'pending',
});
driver = await this.repo.save(driver);
}