15 KiB
ممیزی کامل QA سیستم — بکاند Symfony API + پنل ادمین React
پروژه
clinicpro (Backend Symfony 7.4 + پنل ادمین React 19 داخل همان repo)
نقش اجراکننده
Senior QA Engineer. خروجی این پرامپت کد جدید محصول نیست؛ یک گزارش باگ/ممیزی واقعی و قابلبازتولید است، بهعلاوهٔ در صورت لزوم افزودن/اصلاح تستهای خودکار (phpunit / vitest) برای اثبات باگها. حدس ممنوع — هر یافته باید با خروجی واقعی دستور/تست یا ارجاع file:line مستند شود.
زمینه
سیستم منبع واحد داده و auth برای سه کلاینت است (پنل ادمین خودش، nobat724_front، clinic-pro-tauri). ۲۷۷ روت api/v1، ۷ دامنه اصلی، احراز هویت JWT بدونحالت (stateless). زیرساخت تست موجود است و باید استفاده شود، نه بازنویسی:
- Backend: PHPUnit — پایه
tests/ApiTestCase.php، ۳۸ فایل تست درtests/<Domain>/. اجرا:ddev exec php bin/phpunit - Frontend: Vitest — ۹ فایل
*.test.tsx/*.test.tsدرassets/admin/. اجرا:ddev exec yarn test - Static analysis:
ddev exec php vendor/bin/phpstan analyse(level 5)
هدف
اجرای واقعی تست روی کل سیستم و تولید گزارش ساختاریافته از باگها، مشکلات طراحی، ناسازگاری قرارداد API با کلاینت، و ضعفهای امنیتی. تمرکز روی رفتار واقعی مشاهدهشده، نه بازبینی نظری کد.
فایلهای مرتبط (نقشهٔ سیستم برای تست)
| ناحیه | مسیر | نقش |
|---|---|---|
| پایه پاسخها | src/Shared/Controller/BaseController.php |
قرارداد success/paginated/error/validationError |
| کدهای خطا | src/Shared/Constant/ErrorCodes.php |
همه error codeها + پیام فارسی |
| Exception handler | src/**/ExceptionSubscriber* |
تبدیل AppException به پاسخ خطا |
| امنیت | config/packages/security.yaml |
firewalls، public_endpoints، access_control |
| احراز هویت | src/Auth/ |
PasswordAuthenticator، oauth/token، refresh، otp-login |
| کنترلرهای admin | src/Admin/Controller/AdminApiController.php |
لیست/آمار admin (DQL array hydration) |
| دامنهها | src/{Appointment,Doctor,Clinic,Payment,Rating,Blog,Sms,Representation,Secretary,Settlement,Category,Billing,Subscription}/ |
business logic |
| تستهای موجود | tests/<Domain>/، tests/ApiTestCase.php |
نقطهٔ شروع افزودن تست |
| پنل ادمین | assets/admin/pages/ (۴۹ صفحه)، assets/admin/App.tsx |
routing + صفحات |
| API client فرانت | assets/admin/lib/api.ts |
fetch wrapper، خواندن JWT از localStorage['clinicpro-auth'] |
| state | assets/admin/stores/authStore.ts، uiStore.ts |
Zustand |
| مستندات قرارداد | docs/api/*.md |
مرجع مقایسهٔ رفتار واقعی با مستند |
| کاربران تست | TEST_USERS.md |
credential همهٔ نقشها |
پیشنیازها (قبل از شروع تست)
# سرویسها بالا باشند
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php create_test_users.php # بازساخت کاربران تست در صورت نیاز
ddev exec php bin/console messenger:consume async --limit=0 & # برای تست SMS/queue
# کاربران تست (رمز همه: Test@1234)
# ادمین: 09100000001 ROLE_ADMIN
# کلینیک: 09100100000 ROLE_CLINIC (uuid: 9ac73318-d313-4772-bf1e-418d47f8f4bc)
# دکتر: 09100100001 ROLE_DOCTOR (uuid: 2a3a7ab9-8d34-4118-862f-b458bcd6d77f)
# منشی: 09100100002 ROLE_SECRETARY
دریافت توکن برای تست زندهٔ API — پارامترهای دقیق grant را از docs/api/auth.md بخوان (حدس نزن)، سپس:
# قالب — پارامترهای دقیق (grant_type/client_id/scope) را از docs/api/auth.md تأیید کن
curl -sk -X POST https://clinic-pro.ddev.site/oauth/token \
-H 'Content-Type: application/json' \
-d '{"grant_type":"password","username":"09100000001","password":"Test@1234","scope":"clinicpro"}'
# توکن خروجی را در $TOKEN بگذار و برای endpointهای محافظتشده:
# -H "Authorization: Bearer $TOKEN"
وظایف
اجرا فازبهفاز؛ هر فاز یک آیتم todo. در هر فاز اول تست خودکار موجود را اجرا کن، سپس تست زندهٔ هدفمند. یافتهها را در گزارش (بخش «قالب گزارش») ثبت کن.
فاز ۰ — Baseline
ddev exec php bin/phpunit 2>&1 | tail -30 # وضعیت فعلی سبز/قرمز
ddev exec yarn test 2>&1 | tail -30
ddev exec php vendor/bin/phpstan analyse 2>&1 | tail -30
هر تست شکستخورده یا خطای phpstan = یافتهٔ فاز ۰ (قبل از هر تغییری وضعیت پایه ثبت شود).
فاز ۱ — قرارداد پاسخ و Envelope
از BaseController قرارداد را استخراج کن و روی نمونهٔ واقعی از هر شکل تأیید کن:
success→{ success:true, data }paginated→{ success, data:[], meta:{ totalRecords, totalPages, currentPage } }(تخت، نه nested)error→{ success:false, errors:[{code,message}] }validationError→ HTTP 422 باerrors:[{code,field,message}]
تست زنده روی حداقل یک endpoint از هر نوع:
curl -sk "https://clinic-pro.ddev.site/api/v1/admin/doctors?page=1&limit=5" -H "Authorization: Bearer $TOKEN" | head
curl -sk "https://clinic-pro.ddev.site/api/v1/doctor/INVALID-UUID" -H "Authorization: Bearer $TOKEN"
دام شناختهشده (double-nested): endpointهایی که $this->success(['data' => ...]) میزنند خروجی data.data.data میدهند. هر endpoint را که این الگو را دارد پیدا کن و بررسی کن کلاینت (assets/admin/ و docs/api/) درست استخراج میکند یا نه. ناسازگاری = یافته.
فاز ۲ — احراز هویت و مجوزها (Auth / RBAC)
نقشها: ROLE_ADMIN, ROLE_CLINIC, ROLE_DOCTOR, ROLE_SECRETARY, ROLE_REPRESENTATION.
- login flows:
POST /oauth/token،/oauth/token/refresh،/api/v1/user/login،/api/v1/user/otp-login،/oauth/logout. تست: توکن معتبر، توکن منقضی، refresh با توکن باطل، رمز اشتباه (نباید ۵۰۰ بدهد، باید خطای ساختیافته). - ماتریس دسترسی: برای نمونهای از endpointهای هر دامنه، با توکنِ نقشِ نامجاز درخواست بزن و انتظار
403(نه200، نه500). مثال بحرانی:
# دکتر نباید به endpointهای admin دسترسی داشته باشد
curl -sk -o /dev/null -w "%{http_code}\n" \
"https://clinic-pro.ddev.site/api/v1/admin/users" -H "Authorization: Bearer $DOCTOR_TOKEN" # انتظار 403
- IDOR / افقی: با توکن دکتر A به منبع دکتر B دسترسی بگیر (
/api/v1/appointment/{uuid}،/api/v1/patient/{uuid}،/api/v1/settlement/...). دسترسی موفق = یافتهٔ Critical. - public_endpoints: الگوی regex در
security.yamlرا با روتهای واقعی تطبیق بده؛ endpointی که باید محافظتشده باشد ولی زیر public افتاده = یافتهٔ Critical. برعکس، endpoint عمومی که ۴۰۱ میدهد = یافتهٔ High.
فاز ۳ — اعتبارسنجی ورودی و مدیریت خطا
برای endpointهای نوشتنی (POST/PUT/PATCH/DELETE — ۳۹ در admin، بقیه در دامنهها):
- بدنهٔ خالی، فیلد اجباری غایب، نوع اشتباه (string بهجای int)، مقدار خارج از بازه، UUID نامعتبر، تاریخ نامعتبر.
- انتظار:
422باvalidationErrorساختیافته، نه500و نه پذیرش خاموش. - timestamp: تاریخها باید Unix timestamp صحیح باشند نه DateTime؛ ورودی/خروجی تاریخ را روی یک نمونهٔ واقعی (مثلاً ساخت نوبت) بررسی کن.
فاز ۴ — امنیت API
- SQL Injection: روی endpointهای دارای فیلتر/جستجو (
/api/v1/patient/search-user,/api/v1/admin/*?search=,categorys/{bundle}) payload تزریق بفرست؛ چون لیستهای admin از DQL array hydration استفاده میکنند انتظار امنبودن است — اما تأیید عملی کن. - XSS ذخیرهشده: در فیلدهای متنی (نام دکتر، متن بلاگ، کامنت، متن پیامک post-visit)
<script>ذخیره کن و بررسی کن خروجی API خام برمیگرداند (escape سمت React است — مستند کن مسئولیت کجاست). - Mass assignment: در endpointهای update، فیلدهای غیرمجاز (مثل
role,id,balance) را در بدنه بفرست و ببین اعمال میشوند یا نه. - Rate limiting / brute-force: روی
oauth/tokenوotp-loginچند درخواست پیاپی با رمز غلط بزن؛ نبود محدودیت = یافتهٔ Medium/High. - payment callback:
^/api/v1/(payment|subscription-payment)/callback/عمومی است — بررسی کن امضا/توکن تراکنش اعتبارسنجی میشود (جعل callback = Critical).
فاز ۵ — پنل ادمین React SPA
ddev exec yarn test 2>&1 | tail -30 # vitest موجود
ddev exec npx tsc --noEmit --project tsconfig.json # خطای تایپ = یافته
ddev exec yarn dev 2>&1 | tail -15 # بیلد موفق
سپس بازبینی هدفمند (۴۹ صفحه در assets/admin/pages/):
- Routing / RBAC UI:
App.tsxوSelectContextPage.tsx— آیا مسیرهای هر نقش درست guard میشوند؟ آیا دکتر میتواند مسیر admin را مستقیم در URL باز کند؟ - استخراج پاسخ: در هر صفحهٔ paginated باید
data?.data+data?.meta?.totalRecords؛ single:data?.data؛ Category:data?.data?.data ?? []. هر صفحه که این را اشتباه استخراج میکند (لیست خالی/کرش) = یافته. با فاز ۱ متقاطع کن. - مدیریت خطا در UI: حالت API failure و offline — آیا loading/error state دارد یا کامپوننت کرش میکند؟ (
lib/api.tsرفتار روی ۴۰۱/۵۰۰/timeout). - فرمها: React Hook Form + Zod — آیا اعتبارسنجی سمت کلاینت با اعتبارسنجی backend همخوان است؟ (مثلاً فیلدی که Zod اجباری نمیداند ولی backend میداند).
- auth token: انقضای JWT حین کار با پنل — آیا refresh خودکار یا logout تمیز رخ میدهد یا کاربر در حلقهٔ خطا میافتد؟
فاز ۶ — یکپارچگی و سناریوهای واقعی کاربر (E2E)
با credential واقعی از TEST_USERS.md، این flowها را سرتاسر (API + مشاهدهٔ نتیجه در پنل) اجرا کن:
- ورود/خروج هر ۴ نقش.
- CRUD نوبت: کلینیک/دکتر یک نوبت میسازد → منشی ویرایش میکند → لغو → بررسی timestamp و وضعیت مالی.
- دعوت کلینیک: ارسال دعوت به دکتر (
/api/v1/admin/clinic/{uuid}/invitations) → accept/reject با token (/api/v1/clinic-invitation/{token}/accept). - جریان پرداخت: ساخت پرداخت → callback → تسویه (
Settlement). صحت مبالغ و کمیسیون. - همزمانی: دو کاربر یک اسلات نوبت را همزمان رزرو کنند — آیا double-booking رخ میدهد؟ (یافتهٔ Critical در صورت وقوع).
- بازیابی خطای شبکه: قطع API وسط یک عملیات نوشتنی — آیا داده نیمهکاره یا ناسازگار میماند؟
فاز ۷ — تدوین گزارش نهایی
گزارش را در این مسیر بنویس: docs/qa/full-system-audit-report.md (اگر پوشه نیست بساز). ساختار طبق «قالب گزارش» پایین.
قالب گزارش (برای هر یافته)
### [شناسه] عنوان مشکل
- **شدت:** Critical | High | Medium | Low
- **ناحیه:** Backend API | Admin SPA | Integration | Security
- **مسیر/کد مرتبط:** `src/...:line` یا endpoint
- **مراحل بازتولید:**
1. ...
2. ... (شامل دستور curl / تست دقیق)
- **نتیجهٔ مورد انتظار:** ...
- **نتیجهٔ واقعی:** ... (خروجی خام واقعی، نه توصیف)
- **پیشنهاد رفع:** ... (اشاره به فایل و الگوی درست پروژه)
جدول خلاصه در ابتدای گزارش: شمارش یافتهها به تفکیک شدت و ناحیه.
نکات مهم
- حدس ممنوع: یافته بدون خروجی واقعی یا
file:lineثبت نکن. اگر چیزی قابلبازتولید نبود، در بخش «نیازمند بررسی بیشتر» با ذکر دلیل بگذار، نه بهعنوان باگ قطعی. - قرارداد پروژه: پاسخها فقط از طریق
BaseControllerسنجیده شوند؛ کدهای خطا درErrorCodes.php؛ لیستهای admin از.getArrayResult(). - این تست است، نه رفکتور: کد محصول را تغییر نده مگر برای نوشتن تست خودکار جدید (
tests/یا*.test.tsx) که یک باگ را اثبات میکند؛ رفع باگها در پرامپتهای جداگانهٔ بعدی انجام شود. - cross-repo: هر ناسازگاری قرارداد که کلاینتهای دیگر (
nobat724_front,clinic-pro-tauri) را هم میشکند، در گزارش پرچمگذاری کن (این کلاینتها در build خطا نمیدهند). - دادههای تست: بعد از سناریوهای مخرب (فاز ۶)، در صورت آلودهشدن DB،
ddev exec php create_test_users.phpو در صورت نیاز reset مجدد. - حجم: ۲۷۷ روت را کامل نمیتوان دستی زد؛ نمونهگیری نمایندهٔ حداقل یک endpoint از هر method در هر دامنه الزامی است و در گزارش صراحتاً بنویس چه چیزی پوشش داده نشد (silent-cap ممنوع).
- بعد از پایان:
graphify update ..