قرار المالك 2026-07-27: الباك إند المعتمد صار باك إند سيرو PHP، ويُنقل إلى هذا المستودع كنسخة جديدة باسم «انطلق» على api.intaleqapp.com. backend/ → backend-archive/ (305 ملفاً، إعادة تسمية بلا تعديل محتوى) + README-ARCHIVE.md يوضّح سبب الأرشفة وسبب عدم الحذف: scripts/e2e-test.mjs هو أدق توثيق سلوكي لدورة الرحلة كاملة، ويبقى مرجعاً عند بناء الوحدات الناقصة في باك إند PHP (المحفظتان · الاستحقاقات · محرّك التسعير · الوحدات). مسارات backend/ في docs/*.md لم تُعدَّل عمداً — سجلّ تاريخي. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
129 lines
6.3 KiB
Bash
Executable File
129 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# دورة تحقّق واحدة: افحص البيئة → أقلِع → **انتظر الصحة فعلاً** → اختبر.
|
|
#
|
|
# سبب وجوده: تسلسل `up -d --build && restart && <اختبار>` كان يشغّل الاختبار
|
|
# بينما الـapi ساقطة، فيظهر الفشل كـ`EAI_AGAIN` في الاختبار — أي في المكان
|
|
# الخطأ تماماً. ضاعت دورتان كاملتان على تشخيص خاطئ لهذا السبب وحده.
|
|
# البوابة هنا تمنع تكراره: لا اختبار قبل ردّ /health الفعلي.
|
|
#
|
|
# التشغيل من backend/: bash scripts/verify-and-test.sh
|
|
# بلا اختبار (نشر فقط): bash scripts/verify-and-test.sh --deploy-only
|
|
# مع إعادة بناء: bash scripts/verify-and-test.sh --build
|
|
|
|
set -uo pipefail
|
|
|
|
BUILD=0
|
|
DEPLOY_ONLY=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--build) BUILD=1 ;;
|
|
--deploy-only) DEPLOY_ONLY=1 ;;
|
|
*) echo "خيار غير معروف: $arg"; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
cd "$(dirname "$0")/.." || exit 1
|
|
ENV_FILE=".env"
|
|
FAILED=0
|
|
|
|
hr() { printf '%s\n' "────────────────────────────────────────────────"; }
|
|
|
|
# ── 1) فحص البيئة قبل أي إقلاع ───────────────────────────────────────
|
|
hr; echo "1) فحص البيئة"
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "❌ لا يوجد $ENV_FILE"; exit 1
|
|
fi
|
|
|
|
NODE_ENV_VAL=$(grep -E '^NODE_ENV=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'\r ' || true)
|
|
OTP_DEV_VAL=$(grep -E '^OTP_DEV_MODE=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'\r ' || true)
|
|
echo " NODE_ENV=${NODE_ENV_VAL:-<غير مضبوط>} OTP_DEV_MODE=${OTP_DEV_VAL:-<غير مضبوط>}"
|
|
|
|
# التعارض القاتل: الحارس في main.ts ينهي العملية (exit 1) لا يحذّر فقط،
|
|
# فالحاوية تدخل حلقة إعادة تشغيل ويظهر العطل لاحقاً كفشل شبكة مضلِّل.
|
|
if [[ "$NODE_ENV_VAL" == "production" && "$OTP_DEV_VAL" == "true" ]]; then
|
|
echo " ⚠️ تعارض قاتل: OTP_DEV_MODE=true مع NODE_ENV=production → الـapi لن تقلع."
|
|
echo " ↪︎ أطفئ OTP_DEV_MODE تلقائياً (الاختبار لا يحتاجه — يقرأ الرمز من Redis)."
|
|
sed -i.bak 's/^OTP_DEV_MODE=.*/OTP_DEV_MODE=false/' "$ENV_FILE"
|
|
echo " ✅ OTP_DEV_MODE=false (نسخة احتياطية: ${ENV_FILE}.bak)"
|
|
fi
|
|
|
|
if ! grep -qE '^PLATFORM_SECRET=.+' "$ENV_FILE"; then
|
|
echo " ❌ PLATFORM_SECRET غير مضبوط — نقاط السوبر-أدمن مغلقة والاختبار يحتاجها."
|
|
exit 1
|
|
fi
|
|
|
|
# ── 2) الإقلاع ───────────────────────────────────────────────────────
|
|
hr; echo "2) الإقلاع"
|
|
if [[ "$BUILD" == "1" ]]; then
|
|
echo " بناء (الاختبارات تعمل داخل مرحلة البناء وتحجب النشر عند الفشل)…"
|
|
docker compose up -d --build || { echo "❌ فشل البناء"; exit 1; }
|
|
else
|
|
docker compose up -d || { echo "❌ فشل الإقلاع"; exit 1; }
|
|
fi
|
|
|
|
# ── 3) بوابة الصحة — لا اختبار قبلها ─────────────────────────────────
|
|
hr; echo "3) انتظار /health"
|
|
HEALTH_OK=0
|
|
for i in $(seq 1 30); do
|
|
STATUS=$(docker compose ps --format '{{.Name}} {{.Status}}' | grep '^tripz-api' || true)
|
|
# الحاوية في حلقة إعادة تشغيل = خطأ إقلاع، والانتظار لن يصلحه.
|
|
if [[ "$STATUS" == *Restarting* ]]; then
|
|
echo " ❌ tripz-api في حلقة إعادة تشغيل — خطأ إقلاع، لا مشكلة شبكة."
|
|
hr; echo "سبب السقوط (آخر أخطاء):"
|
|
docker compose logs api --tail=200 2>/dev/null | grep -E 'ERROR|⛔|Error:' | tail -15
|
|
exit 1
|
|
fi
|
|
if docker run --rm --network tripz-net curlimages/curl:latest \
|
|
-fsS --max-time 3 http://tripz-api:4010/api/health >/dev/null 2>&1; then
|
|
HEALTH_OK=1
|
|
echo " ✅ الـapi تستجيب (بعد ${i} محاولة)"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
if [[ "$HEALTH_OK" != "1" ]]; then
|
|
echo " ❌ لم تستجب /health خلال 60 ثانية."
|
|
hr; docker compose ps
|
|
hr; docker compose logs api --tail=40
|
|
exit 1
|
|
fi
|
|
|
|
docker compose ps --format 'table {{.Name}}\t{{.Status}}'
|
|
|
|
if [[ "$DEPLOY_ONLY" == "1" ]]; then
|
|
hr; echo "✅ النشر سليم (بلا اختبار)."; exit 0
|
|
fi
|
|
|
|
# ── 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 \
|
|
-e PLATFORM_SECRET="$SECRET" \
|
|
-v "$(pwd)/scripts:/s" \
|
|
node:22-alpine node /s/pricing-test.mjs
|
|
FAILED=$?
|
|
|
|
hr
|
|
if [[ "$FAILED" == "0" ]]; then echo "✅ تمّت الدورة بنجاح."; else echo "❌ فشل الاختبار (رمز $FAILED)."; fi
|
|
exit $FAILED
|