From 79cb19d4f86f7caaaae558f0103c2d08394b495a Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 18 Jul 2026 21:08:42 +0300 Subject: [PATCH] feat: setup app review accounts and automated E2E simulation --- backend/e2e-tester/simulation.js | 124 +++++++----------- backend/src/modules/auth/auth.service.ts | 11 +- .../src/modules/drivers/drivers.service.ts | 5 +- 3 files changed, 61 insertions(+), 79 deletions(-) diff --git a/backend/e2e-tester/simulation.js b/backend/e2e-tester/simulation.js index 49975e6..cadf1b3 100644 --- a/backend/e2e-tester/simulation.js +++ b/backend/e2e-tester/simulation.js @@ -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); diff --git a/backend/src/modules/auth/auth.service.ts b/backend/src/modules/auth/auth.service.ts index 5166199..9c6a53d 100644 --- a/backend/src/modules/auth/auth.service.ts +++ b/backend/src/modules/auth/auth.service.ts @@ -83,8 +83,10 @@ export class AuthService { const ttl = this.config.get('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); diff --git a/backend/src/modules/drivers/drivers.service.ts b/backend/src/modules/drivers/drivers.service.ts index d0eb870..e1e63ee 100644 --- a/backend/src/modules/drivers/drivers.service.ts +++ b/backend/src/modules/drivers/drivers.service.ts @@ -35,6 +35,9 @@ export class DriversService { userId: string, data: Partial, ): Promise { + 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); }