// اختبار طبقة التزويد (docs/22 — N): السوبر-أدمن ينشئ مستأجراً كاملاً ويقرأ // مانيفست تطبيقه. يتطلّب PLATFORM_SECRET (نفس سرّ السيرفر). // // التشغيل: // docker run --rm --network tripz-net -e BASE=http://tripz-api:4010/api \ // -e SECRET=$PLATFORM_SECRET -v /home/tripz-llc/backend/scripts:/s \ // node:22-alpine node /s/provision-test.mjs const BASE = process.env.BASE || 'http://localhost:4010/api'; const SECRET = process.env.SECRET || process.env.PLATFORM_SECRET || ''; let passed = 0, failed = 0; const check = (n, c, d) => { c ? (passed++, console.log(` ✅ ${n}`)) : (failed++, console.log(` ❌ ${n}${d ? ` — ${d}` : ''}`)); }; async function api(method, path, body, secret = SECRET) { const res = await fetch(`${BASE}${path}`, { method, headers: { 'Content-Type': 'application/json', ...(secret ? { 'x-platform-secret': secret } : {}) }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text().catch(() => ''); let json = {}; try { json = text ? JSON.parse(text) : {}; } catch {} return { status: res.status, ok: res.ok, body: json }; } async function main() { console.log(`\n🧪 اختبار تزويد السوبر-أدمن على ${BASE}\n`); if (!SECRET) { console.log(' ❌ لا PLATFORM_SECRET — مرّره عبر -e SECRET=…'); process.exit(1); } // 1) الحارس: بلا سرّ يُرفض const noSecret = await api('GET', '/admin/tenants', null, ''); check('نقاط السوبر-أدمن محميّة (بلا سرّ = 401)', noSecret.status === 401, `status=${noSecret.status}`); const wrong = await api('GET', '/admin/tenants', null, 'wrong-secret'); check('سرّ خاطئ يُرفض', wrong.status === 401); // 2) الكتالوج const cat = await api('GET', '/admin/features'); check('كتالوج الميزات ووسائل الدفع متاح', Array.isArray(cat.body.features) && Array.isArray(cat.body.payment_methods)); // 3) تزويد مستأجر كامل const slug = `test${Date.now().toString().slice(-8)}`; const prov = await api('POST', '/admin/tenants/provision', { name: 'مستأجر اختبار', slug, countryPack: 'eg', plan: 'brand', features: { bots: true }, paymentMethods: ['cash', 'paymob'], branding: { app_name: 'تطبيق الاختبار', bundle_id_android: 'com.test.rider', bundle_id_ios: 'com.test.rider' }, }); check('تزويد مستأجر جديد نجح', prov.ok, JSON.stringify(prov.body).slice(0, 120)); const tenantId = prov.body.id; // 4) وسائل الدفع صُفّيت للكتالوج const summary = await api('GET', `/admin/tenants/${tenantId}/summary`); check('وسائل الدفع محفوظة', Array.isArray(summary.body.payment_methods) && summary.body.payment_methods.includes('paymob'), JSON.stringify(summary.body.payment_methods)); // 5) الاستحقاقات: الميزة المشتراة مفعّلة + الافتراض منع const ent = await api('GET', `/admin/tenants/${tenantId}/entitlements`); check('ميزة bots المشتراة مفعّلة', ent.body.features?.bots === true); check('ميزة غير مشتراة (transit) محجوبة افتراضاً', ent.body.features?.transit === false); // 6) المانيفست جاهز لسكربت البناء const manifest = await api('GET', `/admin/tenants/${tenantId}/app-manifest`); check('المانيفست يحمل bundle ID', manifest.body.bundle_id_android === 'com.test.rider'); check('المانيفست يحمل اسم التطبيق', manifest.body.app_name === 'تطبيق الاختبار'); check('المانيفست يحمل الدولة والدفع', manifest.body.country_pack === 'eg' && manifest.body.payment_methods.includes('paymob')); // 7) slug مكرّر يُرفض const dup = await api('POST', '/admin/tenants/provision', { name: 'x', slug }); check('slug مكرّر يُرفض', dup.status === 400); // 8) تعديل وسائل الدفع (تصفية المخترع) const setPay = await api('PATCH', `/admin/tenants/${tenantId}/payment-methods`, { methods: ['cash', 'made_up_gateway', 'cliq'], }); check('وسيلة مخترعة تُصفّى، الصحيحة تبقى', setPay.body.settings?.payment_methods?.includes('cliq') && !setPay.body.settings?.payment_methods?.includes('made_up_gateway')); console.log('\n' + '═'.repeat(48)); console.log(`النتيجة: ${passed} نجح · ${failed} فشل`); console.log('═'.repeat(48) + '\n'); process.exit(failed === 0 ? 0 : 1); } main().catch((e) => { console.error('💥', e.message); process.exit(1); });