fix(test): expect fixed to be rejected + flush tariff cache before testing
الدورة السابقة أعلنت الفشل والنظام سليم — العطل كان في السكربت: 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7e06b1241f
commit
2045896b95
@@ -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} طلباً).`);
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user