// اختبار مسار المواقع (docs/17 — H): يثبت أن العتبات تعمل على Redis حقيقي، // وأن سائقاً واقفاً لا يكلّف شيئاً. يستخدم fetch المدمج — بلا تبعيات. // // يعتمد أن POST /drivers/location يرجع { ok, significant } — فالعتبة تُتحقَّق // عبر الـAPI الحقيقي بلا حاجة للنظر في القاعدة. // // التشغيل: // docker run --rm --network tripz-net -e BASE=http://tripz-api:4010/api \ // -v /home/tripz-llc/backend/scripts:/s node:22-alpine node /s/location-test.mjs // // بعده تحقَّق من كتابة الـworker (بعد ~6 ثوانٍ): // SELECT count(*) FROM tripz_driver_tracks; -- يجب أن يساوي عدد «ذات الدلالة» // SELECT loc_status, speed, loc_updated_at FROM tripz_drivers WHERE loc_updated_at IS NOT NULL; const BASE = process.env.BASE || 'http://localhost:4010/api'; const TENANT = process.env.TENANT || 'siro'; const CODE = '1234'; const STATIONARY_PINGS = 10; const MOVING_PINGS = 8; const ORIGIN = { lat: 31.9539, lng: 35.9106 }; const metersToLat = (m) => m / 111_320; const H = (t) => ({ 'Content-Type': 'application/json', ...(t ? { Authorization: `Bearer ${t}` } : {}) }); async function api(method, path, token, body) { const res = await fetch(`${BASE}${path}`, { method, headers: { ...H(token), 'x-tenant-id': TENANT }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text().catch(() => ''); let json = {}; try { json = text ? JSON.parse(text) : {}; } catch { /* غير JSON */ } if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${text.slice(0, 200)}`); return json; } async function main() { const phone = `0797${String(Date.now()).slice(-6)}`; const { access_token: t } = await api('POST', '/auth/verify-otp', null, { phone, code: CODE }); const driver = await api('POST', '/drivers/apply', t, { vehicle_make: 'LocationTest', service_class: 'economy', }); await api('PATCH', `/drivers/${driver.id}/approve`, t, {}); await api('PATCH', '/drivers/status', t, { online: true }); console.log(`سائق: ${driver.id} (${phone}) — متصل\n`); // 1) واقف مكانه: الأولى ذات دلالة، والباقي يجب أن تُهمَل كلها. let stationarySignificant = 0; for (let i = 0; i < STATIONARY_PINGS; i++) { const r = await api('POST', '/drivers/location', t, { ...ORIGIN, speed: 0, heading: 0 }); if (r.significant) stationarySignificant++; } console.log(`واقف : ${STATIONARY_PINGS} نبضة → ${stationarySignificant} ذات دلالة (المتوقع 1: الأولى فقط)`); // 2) متحرّك 30م كل نبضة: كلها يجب أن تكون ذات دلالة. let movingSignificant = 0; for (let i = 1; i <= MOVING_PINGS; i++) { const r = await api('POST', '/drivers/location', t, { lat: ORIGIN.lat + metersToLat(30 * i), lng: ORIGIN.lng, speed: 40, heading: 0, }); if (r.significant) movingSignificant++; } console.log(`متحرّك : ${MOVING_PINGS} نبضة × 30م → ${movingSignificant} ذات دلالة (المتوقع ${MOVING_PINGS})`); const totalPings = STATIONARY_PINGS + MOVING_PINGS; const totalSignificant = stationarySignificant + movingSignificant; const saved = Math.round(((totalPings - totalSignificant) / totalPings) * 100); console.log(''); console.log(`الإجمالي: ${totalPings} نبضة → ${totalSignificant} فقط لمست Redis/الفهرس`); console.log(`أي أن ${saved}% من النبضات كلّفت أمر EXPIRE واحداً لا أكثر.`); console.log(''); const checks = [ ['النبضة الأولى ذات دلالة', stationarySignificant >= 1], ['الوقوف لا يُنتج نبضات ذات دلالة', stationarySignificant === 1], ['كل تحرّك 30م ذو دلالة', movingSignificant === MOVING_PINGS], ]; let failed = 0; for (const [name, pass] of checks) { console.log(`${pass ? '✅' : '❌'} ${name}`); if (!pass) failed++; } console.log(''); console.log(`بعد ~6 ثوانٍ يجب أن يكون الـworker كتب ${totalSignificant} نقطة مسار. تحقَّق:`); console.log(` SELECT count(*) FROM tripz_driver_tracks WHERE driver_id = '${driver.id}';`); console.log(` SELECT loc_status, speed, loc_updated_at FROM tripz_drivers WHERE id = '${driver.id}';`); console.log(''); console.log(failed === 0 ? '✅ العتبات تعمل.' : `❌ ${failed} محكّ فشل.`); process.exit(failed === 0 ? 0 : 1); } main().catch((e) => { console.error('خطأ:', e.message); process.exit(1); });