feat: setup app review accounts and automated E2E simulation
This commit is contained in:
@@ -5,24 +5,23 @@ 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 = {
|
||||
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);
|
||||
@@ -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,21 +77,18 @@ 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;
|
||||
|
||||
@@ -107,10 +99,7 @@ async function run() {
|
||||
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',
|
||||
@@ -119,9 +108,9 @@ async function run() {
|
||||
destination: { lat: 32.0700, lng: 36.0900 }
|
||||
}, riderToken);
|
||||
|
||||
console.log(`\x1b[32m[Rider] Trip created: ${tripRequest.id}\x1b[0m`);
|
||||
console.log(`\x1b[32m[Rider] Trip created: ${tripRequest.trip.id}\x1b[0m`);
|
||||
|
||||
// 6. Simulate Trip Flow (Wait 10 seconds per state)
|
||||
// 7. Simulate Trip Flow (Wait 10 seconds per state)
|
||||
await new Promise(r => setTimeout(r, 10000));
|
||||
|
||||
if (tripId) {
|
||||
@@ -133,33 +122,20 @@ async function run() {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user