test: سكربت تحقّق مسار المواقع على السيرفر (H)

loadtest يرسل نبضة واحدة لكل سائق فلا يختبر العتبات إطلاقاً. هذا السكربت
يرسل 10 نبضات من مكان ثابت ثم 8 نبضات بتحرّك 30م، ويتحقق من علم significant
الذي ترجعه POST /drivers/location — أي العتبات على Redis حقيقي عبر الـAPI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-17 03:03:34 +03:00
co-authored by Claude Opus 4.8
parent da37dffa44
commit 7e24c63dd8
2 changed files with 110 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
// اختبار مسار المواقع (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);
});
+6
View File
@@ -66,3 +66,9 @@ docker run --rm --network tripz-net -e BASE=http://tripz-api:4010/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/loadtest.mjs 300 30
```
```
# مسار المواقع (docs/17 — H): يثبت أن العتبات تعمل وأن الواقف لا يكلّف شيئاً
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
```