🛡️ شبكة أمان الـRegressions — إزاي تتأكد إن القديم كله شغّال قبل أي تعديل
المشكلة اللي بتواجهها: تضيف فيتشر أو تعدّل حاجة → تبوظ حاجة تانية من غير ما تحس، خصوصًا لإن الموديولات بتشارك خدمات Core مشتركة (محرّك القيود/GL، المخزون، الضرايب). الملف ده تشخيص للوضع الفعلي (مقيس بالكود) + استراتيجية متدرّجة عملية لراجل واحد + خطة 4 أسابيع.
📅 4 يوليو 2026🧬 Moon ERP — BE (Laravel modular) + FE (Angular) + MoonStack🔎 فحص كود مقيس + KB (dev-workflow) + استشارة Fable 5الحالة: استراتيجية — جاهزة للتنفيذ
✅ عندك شبكة أمان ضخمة بالفعل
478 ملف اختبار / 4,672 اختبار في الباك إند. المشكلة مش إنها مش موجودة — المشكلة إن محدش بيشغّلها ومفيش حاجة بتفرضها.
🎯 الفجوة = بوابة (Gate) مش اختبارات
الحل الأساسي: CI يشغّل الـ478 على كل push + MoonStack يرفض يشحن غير لو الكود أخضر. ده اللي بيحوّل "أتمنى إنه شغّال" لـ"متأكد".
⚡ أسرع من المتوقع
السويت ~50-60 دقيقة تسلسلي، بس بـ4-6 shards + ParaTest → أقل من 10 دقايق. الـSQLite in-memory بتاعك مثالي للتوازي.
🔴 صفر unit tests (skipTests:true في كل مكان، 0 .spec.ts).
🟢 scripts/report-contract.mjs — harness عبقري: بيـbundle كود قوالب تقارير الـLIS الحقيقية بـesbuild ويختبرها ضد مصفوفة fixtures، بطبقتين: contract assertions (قواعد بتكسر الـbuild) + snapshots (بتكشف أي انحراف). ده نمط نوسّعه.
🟡 مجلّد e2e/ فيه ملفين Playwright (cashier shift، LIS board) — بس مفيش config ولا runner → يتامى، محتاجين توصيل.
التوزيع (MoonStack) — الـregression بتشحن لعملاء
العملاء self-hosted بياخدوا تحديثات عبر MoonStack (zip موقّع + versions.json). دورة التحديث على العميل: تنزيل → تحقق توقيع → backup → migrate → sync-reference seeders → health-check → finalize.
🔴 مفيش بوابة release — moonstack:ship بيشحن بلا ما يتأكد إن الكود أخضر → الـregression بتوصل العملاء. + الـ"seeder gap" معروف (التحديث مش بيبذر تعريفات إعدادات جديدة).
الخلاصة: إنت فاكر إن مفيش شبكة أمان — الحقيقة إن عندك واحدة كبيرة (478 اختبار) بس مش مطفّية غلط: مفيش حاجة تشغّلها، ومفيش حاجة تمنع الشحن لو حمرا. الحل مش نكتب اختبارات من الصفر — الحل نركّب البوابات ونسدّ فجوات محدّدة.
2 الفكرة الأساسية — الفرض (Enforcement)، مش الانضباط
الاختبارات موجودة والانضباط فشل بالفعل (محدش بيشغّلها). "أتأكد" مش بتيجي من سكربت ممكن تنساه — بتيجي من بوابة بتفرض نفسها.
⚠️ نقطة حرجة لراجل واحد بيـpush مباشر على hazemdev2: بوابة "منع الميرج" (PR gate) مش هتشتغل لإن مفيش PRs أصلاً. البوابة اللي بتشتغل فعلاً:
CI على on: push لـhazemdev2 وmain → كل push بياخد حكم أخضر/أحمر تلقائي.
moonstack:ship يرفض يعمل package غير لو الـSHA بالظبط عليه CI أخضر (عبر gh api). دي أهم بوابة — بتمنع شحن regression للعملاء.
+ branch protection على main. لكن push-CI + ship-gate هما الزوج الحامل.
3 البنية — 4 طبقات بوابات، كل واحدة أبطأ وأشمل
① لوكال سريع verify.sh — الموديول المتغيّر + ng build. ثواني. قبل الكوميت.
←
② بوابة push (CI) كل السويت BE بالتوازي + FE prod build + report-contract. <10 دق. كل push.
←
③ ليلي / قبل release السويت كاملة على MySQL حقيقي + Playwright smokes. أبطأ، أدق.
أيوهbrianium/paratest + artisan test --parallel. الـSQLite :memory: مثالي: كل process بياخد DB معزول تلقائيًا (per-connection) → مفيش تصادم DB الكلاسيكي.
changed-module فقط
لأ — بيفوّت الـcross-module (§5). السويت صغيرة كفاية نشغّلها كلها.
Test-Impact Analysis
أبدًا — أدوات PHP غير ناضجة، عبء صيانة.
🔴 مصايد لازم ننتبه لها:
مصدر الـflake الحقيقي مش الـDB — هو DomPDF وأي حاجة بتكتب في storage/ مشترك. نراجع اختبارات الـPDF/التقارير تستخدم Storage::fake().
اختبارات بتعتمد على ترتيب التنفيذ أو static state هتطفو تحت التوازي → جولة triage صغيرة تانية متوقّعة.
🟥 SQLite ≠ MySQL. العملاء بيشغّلوا الـmigrations على MySQL (FK، strict mode، DDL، JSON functions بتختلف). نخلّي SQLite للبوابة السريعة، بس تشغيلة ليلية + قبل-release على MySQL حقيقي (service container) — غير قابلة للتفاوض لمنتج بيشحن migrations.
تكلفة Actions: الفري 2000 دقيقة/شهر للريبو الخاص ≈ 15-25 تشغيلة كاملة. نوفّر بـconcurrency: cancel-in-progress (أكبر موفّر لراجل بيـpush كتير) + cache للـcomposer/npm + حصر on:push على الفرعين. لو اتخطّى → ~1$ للتشغيلة، أرخص من regression واحد بيتشحن.
✅ خلّي السويت سريعة كفاية إنك متختارش أبدًا — شغّلها كلها كل مرة. أي قاعدة اختيار صحيحة ("المتغيّر + Core + التابعين") بتنهار لـ"السويت كاملة" في نفس الحالات اللي بتخاف منها. و4,672 اختبار صغيرة بمقاييس الصناعة — تحت 10 دقايق بالتوازي، "شغّل كل حاجة" هو الأبسط والأصح. ليه بتمسك الـcross-module؟ لإن اختبارات LIS بتمرّن خدمات Core لما بتشتغل → الطريقة الوحيدة إنه يفوت = إنك متشغّلش اختبارات LIS.
قيد قانوني نموذجي → تأكيد صفوف المدين/الدائن بالظبط.
حركة مخزون → تأكيد الأرصدة.
حالات VAT (table-driven).
إجماليات الفاتورة.
دول بيحوّلوا "انحراف دلالي صامت عبر الموديولات" لـ"فشل صريح باسم Core" — أعلى قيمة لأي اختبارات جديدة، يوم-يومين شغل. (لو السويت طلعت فوق 15 دقيقة حتى بالتوازي: خريطة اعتماديات يدوية 16 سطر — "تغيير Core/Accounting/Inventory ⇒ السويت كاملة؛ الموديولات الطرفية CMMS/QMS/WebStore ممكن لوحدها". مش أكتر.)
6 الفرونت (صفر unit) — الترتيب: d ← b ← a ← c
(d) بوابة prod build + typecheckأسبوع 1 — تكلفة شبه صفر. ng build --configuration production بقوالب Angular الصارمة بيمسك فئة كبيرة من الـregressions الحقيقية (حقول اتغيّر اسمها، bindings مكسورة، أخطاء i18n في القوالب).
(b) Playwright critical smokesأسبوع 2-3 — نكتب playwright.config، نوصّل runner، ونكبّر لـ5-8 رحلات (سقف ~10): تسجيل دخول، إنشاء طلب تحليل → نتيجة → اعتماد، إنشاء فاتورة → إجماليات، ترحيل قيد، حركة مخزون. E2E هو المكان اللي بتظهر فيه أخطاء ربط NgRx. شغّلها ليلي/قبل-release الأول؛ ماترقّيهاش لبوابة push غير بعد أسابيع من إثبات إنها مش flaky (بوابة flaky بتعوّدك تتجاهل الأحمر = أسوأ من مفيش بوابة).
أهم إضافة على الإطلاق: تمرين مسار الترقية (Upgrade rehearsal) — بيمسك فئة كاملة من الأخطاء اللي السويت مبنيويًا مش بتشوفها.
تمرين ترقية في CI (قبل النشر): ناخد DB النسخة السابقة المنشورة بعد الـmigrate، ونطبّق التحديث الجديد زي ما العميل بالظبط بيعمل (migrate → sync-reference seeders → health-check) ونتأكد أخضر + smoke request. ده بيمسك الـseeder gap نهائيًا + فشل الـmigrations على داتا واقعية. 🔑 نخلّي الـfixture طازة آليًا: كل release الـCI بيحفظ dump ما بعد التركيب كـartifact → يبقى مدخل تمرين الـrelease اللي بعده (وإلا الـfixture بيتعفّن في 3 releases).
تمرين fresh-install: نفك الـinstaller الحقيقي على MySQL نظيف، نركّب، نقلّع، health-check، smoke request. بيختبر الحاجة اللي العميل بياخدها، مش الريبو.
settings:verify — نبنيه ونحطه في المكانين: أمر artisan بيقارن مفاتيح الإعدادات اللي الكود بيستخدمها ضد اللي الـseeders بتوفّرها → في CI و جوه health-check العميل. (طويل الأمد: نخلّي قراءة الإعداد fail-soft بـdefault مُعرّف في الكود → الصف الناقص يتدهور مش يكسر.)
ship بوابة صلبة:moonstack:ship يتأكد: CI أخضر على الـSHA بالظبط · release من main · التوقيع بيتحقق ضد مفتاح العميل · versions.json سليم · الإصدار بيزيد تصاعديًا.
Canary (رخيص، لاحقًا): نسختك بتـpoll manifest ما-قبل-النشر → التحديث الحقيقي بيتطبّق عليها الأول → smoke → بعدين تنشر للكل.
مرحلة 2 تمرين مسار الفشل مرة: نحاكي health-check فاشل وسط التحديث ونتأكد إن الـbackup/rollback بيرجّع فعلاً.
8 ⛔ اللي متعملوش (تجنّب مسرح الاختبارات)
🚫 مفيش نسبة تغطية إلزامية (coverage %). رقم تغطية على 4.7k اختبار brownfield = مسرح. البوابة = "السويت خضرا + منطق فلوس جديد بيتشحن باختباره".
⚠️ تجاوز متعمّد لقاعدتك العالمية (~/.claude/rules/common/testing.md: 80% تغطية + TDD دايمًا): مش مناسبة هنا — لمشروع قايم بـ4.7k اختبار، طقوس RED/GREEN على كل تغيير مبالغة لراجل واحد. المعيار: "الاختبار بيوصل مع التغيير" + characterization للقديم.
🚫 مفيش snapshots صفحة كاملة (DOM/pixel) — في UI ثنائي RTL بتتكسر مع كل تغيير شرعي وبتتعفّن لـ--update انعكاسي. تأكيدات موجّهة بس.
🚫 مفيش E2E عريض. سقف ~10 رحلات. الـflaky يتحذف أو يتحل جذريًا — مايتلفّش في retry loop.
🚫 مفيش self-hosted runners على سيرفر cPanel (قيود CageFS/SAPI موثّقة) — runners ubuntu هي البيئة النظيفة، سيب الـCI بعيد عن الجهاز ده.
🚫 مفيش quarantine دائم. قايمة الـskip (من triage الأسبوع 1) لازم تصغر: كل بند مؤرّخ، والتشغيلة الليلية تفشل لو القايمة كبرت. قايمة بتكبر بس = إزاي البوابات بتموت.
الخط الفاصل: الشبكة "كفاية" لما كل push بياخد تلقائيًا (سويت BE كاملة + FE prod build + contracts) في أقل من ~15 دقيقة، وكل release بياخد كمان install + upgrade rehearsal. بعد كده، أي check جديد لازم يشاور على فئة خطأ اتشحنت فعلاً — أي حاجة مبرّرة بـ"best practice" مش بحادثة حقيقية = مسرح، ولراجل واحد المسرح مش بس بيضيّع وقت، بيآكل الثقة اللي بتخلّيك تحترم البوابة أصلاً.
9 خطة 4 أسابيع
الأسبوع
المهام
أسبوع 1 الأساس + البوابات
يوم 1 — baseline triage: شغّل السويت كاملة مرة محليًا وسجّل الحقيقة (هيبقى فيه فشل/افتراضات بيئة). صلّح الرخيص، وعزل الباقي في skip-list مؤرّخة (بند/تاريخ لكل واحد). امنع CI على baseline أخضر-معروف، مش سويت مثالية. يوم 2-3 — CI الباك إند: GitHub Actions، on:push لـhazemdev2+main، 4-6 shards + ParaTest، أخضر على الـbaseline. يوم 3 — CI الفرونت (~20 دقيقة YAML): ng build --configuration production + report-contract.mjs. يوم 4-5 — ship-SHA gate:moonstack:ship يرفض بلا CI أخضر على الـSHA + branch protection على main.
أسبوع 2-3 ملء الفجوات عالية القيمة
golden money-path tests (10-20: قيود، مخزون، VAT، إجماليات). Playwright: config + runner + 5-8 smokes حرجة (ليلي الأول). تشغيلة ليلية على MySQL حقيقي (service container) — تمسك فروقات SQLite↔MySQL.
أسبوع 4 بوابة الـrelease
upgrade rehearsal (DB النسخة السابقة → migrate → seeders → health-check → smoke) + حفظ dump كـartifact للـrelease الجاي. fresh-install rehearsal على MySQL نظيف.
أمر settings:verify في CI + جوه health-check العميل.
بعد كده
فرصة أو أبدًا: توسيع report-contract، canary channel، تمرين مسار الفشل/rollback. مفيش حاجة تانية إلا لو خطأ حقيقي طلب.
10 أول خطوة ملموسة — دلوقتي
قبل أي YAML، محتاجين نعرف الحقيقة: السويت خضرا فعلاً ولا فيها فشل خفي؟ وبتاخد قد إيه؟ ده أساس كل حاجة بعده.
# 1) شغّل السويت كاملة مرة واحدة، سجّل النتيجة + الزمن (BE)
cd /home/moonui2/moon-erp-be
time php vendor/bin/pest 2>&1 | tee /tmp/baseline-run.log
# 2) ثبّت ParaTest وقيس الفرق بالتوازي
php composer.phar require --dev brianium/paratest
time php artisan test --parallel --processes=4
# 3) تأكد الفرونت بيـbuild + الـcontract أخضر
cd /home/moonui2/public_html/moon-erp
npx ng build --configuration production
node scripts/report-contract.mjs
أنا أقدر أعمل ده معاك دلوقتي: أشغّل الـbaseline (الخطوة 1+2) وأطلعلك تقرير — كام اختبار أخضر/أحمر، الزمن التسلسلي مقابل المتوازي، وقايمة أي فشل نعزله — ونبني عليه أول workflow لـGitHub Actions. قوللي أبدأ.