Files
tripz-llc/backend-archive/scripts/location-test.mjs
T
Hamza-AyedandClaude Opus 5 fff76c949f chore: أرشفة باك إند NestJS إلى backend-archive
قرار المالك 2026-07-27: الباك إند المعتمد صار باك إند سيرو PHP، ويُنقل
إلى هذا المستودع كنسخة جديدة باسم «انطلق» على api.intaleqapp.com.

backend/ → backend-archive/ (305 ملفاً، إعادة تسمية بلا تعديل محتوى)
+ README-ARCHIVE.md يوضّح سبب الأرشفة وسبب عدم الحذف: scripts/e2e-test.mjs
  هو أدق توثيق سلوكي لدورة الرحلة كاملة، ويبقى مرجعاً عند بناء الوحدات
  الناقصة في باك إند PHP (المحفظتان · الاستحقاقات · محرّك التسعير · الوحدات).

مسارات backend/ في docs/*.md لم تُعدَّل عمداً — سجلّ تاريخي.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 04:54:35 +03:00

105 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// اختبار مسار المواقع (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);
});