loadtest يرسل نبضة واحدة لكل سائق فلا يختبر العتبات إطلاقاً. هذا السكربت يرسل 10 نبضات من مكان ثابت ثم 8 نبضات بتحرّك 30م، ويتحقق من علم significant الذي ترجعه POST /drivers/location — أي العتبات على Redis حقيقي عبر الـAPI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
4.6 KiB
JavaScript
105 lines
4.6 KiB
JavaScript
// اختبار مسار المواقع (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);
|
||
});
|