From 2045896b956ca3af692b85ecaf5e7050de862425 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sun, 19 Jul 2026 02:09:05 +0300 Subject: [PATCH] fix(test): expect fixed to be rejected + flush tariff cache before testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit الدورة السابقة أعلنت الفشل والنظام سليم — العطل كان في السكربت: 1. **`fixed` تُرفض بـ400 وهذا هو المطلوب** (أُخرجت من الزرع عمداً)، لكن السكربت كان يعدّها فئة متوقَّعة فأسقط التغطية إلى 72/81. صارت في `REJECTED_CLASSES`: الرفض يُتحقَّق منه صراحةً، وغيابه هو الفشل. 2. **429 على `send-otp`** (10 لكل 5 دقائق) عند تشغيلتين متتاليتين — يُنتظر ويُعاد بدل الإبلاغ عن فشل ليس في النظام. 3. **إبطال كاش التعرفة صار خطوة في الدورة.** `getActive` تكاشي 900 ثانية، و**Redis حاوية منفصلة لا تتأثر بإعادة تشغيل الـapi** — وتطبيق `tariffs.sql` مباشرةً على القاعدة يتجاوز التطبيق فلا يُبطل شيئاً. النتيجة أن النظام خدم أسعاراً قديمة عبر دورتَي نشر كاملتين وهو يعلن النجاح. المسح الآن مبنيّ في السكربت لا متروك للتذكّر. Co-Authored-By: Claude Opus 4.8 --- backend/scripts/pricing-test.mjs | 33 +++++++++++++++++++++++++++--- backend/scripts/verify-and-test.sh | 20 ++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/backend/scripts/pricing-test.mjs b/backend/scripts/pricing-test.mjs index c5f584b..02a532b 100644 --- a/backend/scripts/pricing-test.mjs +++ b/backend/scripts/pricing-test.mjs @@ -33,10 +33,16 @@ if (!PLATFORM_SECRET) { process.exit(1); } +// الفئات المتوقَّع أن تُسعَّر. **`fixed` ليست منها**: أُخرجت من الزرع عمداً +// (سعر ثابت بلا جدول خطوط = رقم واحد لكل الوجهات)، فالمتوقَّع منها رفض 400 +// لا سعر — ويُتحقَّق من ذلك صراحةً في `REJECTED_CLASSES`. const SERVICE_CLASSES = [ - 'saver', 'delivery', 'economy', 'fixed', 'electric', 'lady', 'comfort', 'van', 'vip', + 'saver', 'delivery', 'economy', 'electric', 'lady', 'comfort', 'vip', 'van', ]; +/** فئات يجب أن يرفضها الخادم — الرفض هنا نجاحٌ لا فشل. */ +const REJECTED_CLASSES = ['fixed']; + /** * رحلات نموذجية داخل الزرقاء/عمّان (إحداثيات قابلة لإعادة الاستعمال في * أي بلد — الفارق بين البلدان هو التعرفة لا الجغرافيا؛ المسافة والزمن @@ -194,7 +200,14 @@ async function loginRider(tenantSlug, tenantId, countryPack, seed) { // `send-otp` يخزّن الرمز في Redis **قبل** محاولة الإرسال، فحتى لو فشل // الإرسال لرقم وهمي (503) يكون الرمز محفوظاً — وهو ما نقرأه. - const sent = await api('POST', '/auth/send-otp', { tenant: tenantSlug, body: { phone } }); + let sent = await api('POST', '/auth/send-otp', { tenant: tenantSlug, body: { phone } }); + // 429: `send-otp` محدود المعدّل (10 لكل 5 دقائق). تشغيلتان متتاليتان + // تصطدمان به، فننتظر بدل أن نُبلغ عن فشل ليس في النظام. + for (let wait = 0; sent.status === 429 && wait < 3; wait++) { + console.log(` ⏳ حدّ المعدّل على send-otp — انتظار 20 ثانية…`); + await new Promise((r) => setTimeout(r, 20000)); + sent = await api('POST', '/auth/send-otp', { tenant: tenantSlug, body: { phone } }); + } if (sent.body?.dev_code) { // وضع تطويري مفعّل (بيئة غير إنتاجية) — الرمز في الاستجابة مباشرةً. return (await verify(tenantSlug, phone, sent.body.dev_code))?.access_token; @@ -260,6 +273,7 @@ async function main() { } const rows = []; + let rejectionsOk = true; for (const t of TENANTS) { console.log(`\n--- ${t.slug} (${t.countryPack} / ${t.currency}) ---`); @@ -275,6 +289,19 @@ async function main() { continue; } + // الفئات المسحوبة يجب أن تُرفض — تسريب الـ2.5 دينار كان هنا. + for (const serviceClass of REJECTED_CLASSES) { + const res = await quoteFor(t.slug, token, serviceClass, RIDES[2]); // الأطول + if (res.status === 400) { + console.log(` ✅ ${serviceClass} مرفوضة كما يجب (400)`); + } else { + console.error( + ` ❌ ${serviceClass} لم تُرفض: ${res.status} — رحلة ${RIDES[2].label} بسعر ثابت تسريب.`, + ); + rejectionsOk = false; + } + } + for (const ride of RIDES) { for (const serviceClass of SERVICE_CLASSES) { const res = await quoteFor(t.slug, token, serviceClass, ride); @@ -357,7 +384,7 @@ async function main() { const comparable = rows.filter((r) => ['economy', 'van'].includes(r.serviceClass)).length; if (comparable === 0) console.error('⚠️ لا بيانات كافية للمقارنة — التحقّق أعلاه لم يقع فعلياً.'); - const bad = noQuote.length > 0 || !orderOk || rows.length < expected; + const bad = noQuote.length > 0 || !orderOk || rows.length < expected || !rejectionsOk; process.exitCode = bad ? 1 : 0; console.log(bad ? '\n❌ فشل الاختبار.' : `\n✅ الاختبار ناجح (${rows.length} طلباً).`); } diff --git a/backend/scripts/verify-and-test.sh b/backend/scripts/verify-and-test.sh index 7bea4cf..99be578 100755 --- a/backend/scripts/verify-and-test.sh +++ b/backend/scripts/verify-and-test.sh @@ -97,8 +97,24 @@ if [[ "$DEPLOY_ONLY" == "1" ]]; then hr; echo "✅ النشر سليم (بلا اختبار)."; exit 0 fi -# ── 4) اختبار التسعير ──────────────────────────────────────────────── -hr; echo "4) اختبار التسعير" +# ── 4) إبطال كاش التعرفة ───────────────────────────────────────────── +# `getActive` تكاشي في Redis 900 ثانية، و**Redis حاوية منفصلة لا تتأثر +# بإعادة تشغيل الـapi**. تطبيق `tariffs.sql` مباشرةً على القاعدة يتجاوز +# التطبيق فلا يُبطل شيئاً — فبقي النظام يخدم أسعاراً قديمة عبر دورتَي نشر +# كاملتين وهو يعلن النجاح. المسح هنا يجعل الطزاجة مضمونة بالبناء لا +# بالتذكّر. +hr; echo "4) إبطال كاش التعرفة" +KEYS=$(docker compose exec -T redis redis-cli -n "${REDIS_DB:-3}" --scan --pattern 'tripz:tariff:*' 2>/dev/null | tr -d '\r') +if [[ -n "$KEYS" ]]; then + COUNT=$(printf '%s\n' "$KEYS" | grep -c . || true) + printf '%s\n' "$KEYS" | xargs -r docker compose exec -T redis redis-cli -n "${REDIS_DB:-3}" DEL >/dev/null + echo " ✅ مُسح $COUNT مفتاح تعرفة" +else + echo " ℹ️ لا مفاتيح تعرفة مكاشية" +fi + +# ── 5) اختبار التسعير ──────────────────────────────────────────────── +hr; echo "5) اختبار التسعير" SECRET=$(grep -E '^PLATFORM_SECRET=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'\r ') docker run --rm --network tripz-net \ -e BASE=http://tripz-api:4010/api \