Files
khatibi/tests/test_urls_contract.py
2026-10-21 16:34:13 +03:30

194 lines
6.2 KiB
Python

"""
قرارداد URLها و ویوها پس از تقسیم `views.py` به پکیج `views/`.
نگهبان حذف تصادفی مسیر/ویو: مجموعه‌ی نام‌های URL با یک snapshot ثابت مقایسه می‌شود
و هر نام باید با ``reverse()`` قابل بازیابی باشد.
"""
from django.test import SimpleTestCase
from django.urls import reverse
# snapshot قطعی نام‌های مسیرهای اپ admin_panel (۷۷ مسیر)
EXPECTED_URL_NAMES = {
'index',
'p_login',
'p_logout',
'bedehi',
'report',
'inventory_movements_report',
'financial_dashboard',
'report_debtors_no_vosol',
'rezayat_dashboard',
'hesabdari',
'hesabdari_residnashone',
'hesabdari_check_pass',
'hesabdari_check_pass_fail',
'bedehiKhadamat',
'bedehiNasb',
'customers',
'customersSearch',
'customersAdd',
'customer',
'api_check_status_inquiry',
'api_check_sabt_inquiry',
'customer2',
'customerNew',
'customerAction',
'customerNazar',
'customerNewAction',
'customerEditAction',
'customerActionDelete',
'customerDel',
'customerFactors',
'customerFactor',
'FactorDel',
'newFactor',
'newFactor2',
'customerHesabdari',
'customerSarresidh',
'customerHesabdariAdd',
'stores',
'productsJson',
'ActionJson',
'CustomerJson',
'productMojodi',
'store',
'operations',
'operationProduct',
'newOperation',
'newStore',
'store_transfers',
'process_transfer_action',
'productAdd',
'productEdit',
'operation',
'productDel',
'customerEdit',
'rezayat',
'rezayatEdit',
'entezar',
'ActionList',
'ActionListToday',
'ActionListUserToday',
'ActionListAll',
'passCheck',
'update_check_info',
'addSarresid',
'pardakhtNesiye',
'sms_settings',
'sms_templates',
'sms_template_edit',
'customerSmsHistory',
'sms_customer_toggle',
'sms_primary_toggle',
'sms_send_custom',
'sms_queue',
'sms_report',
'sms_queue_retry',
'sms_queue_approve',
'sms_queue_delete',
'sms_queue_bulk',
'sms_queue_empty',
}
# مسیرهایی که آرگومان می‌گیرند: نام -> kwargs نمونه
PARAMETERIZED = {
'customersSearch': {'searchVal': 'ali', 'searchOn': 'name'},
'customer': {'c_id': 1},
'customer2': {'c_id': 1},
'customerNew': {'c_id': 1},
'customerAction': {'c_id': 1},
'customerNazar': {'c_id': 1},
'customerNewAction': {'c_id': 1},
'customerEditAction': {'c_id': 1, 'action_id': 1},
'customerActionDelete': {'c_id': 1, 'action_id': 1},
'customerDel': {'c_id': 1},
'customerFactors': {'c_id': 1},
'customerFactor': {'c_id': 1, 'f_id': 1},
'FactorDel': {'c_id': 1, 'f_id': 1},
'newFactor': {'c_id': 1},
'newFactor2': {'c_id': 1},
'customerHesabdari': {'c_id': 1},
'customerSarresidh': {'c_id': 1},
'customerHesabdariAdd': {'c_id': 1},
'ActionJson': {'action_id': 1},
'CustomerJson': {'c_id': 1},
'productMojodi': {'p_id': 1},
'store': {'s_id': 1},
'operations': {'s_id': 1},
'operationProduct': {'s_id': 1, 'p_id': 1},
'newOperation': {'s_id': 1, 'p_id': 1},
'store_transfers': {'s_id': 1},
'process_transfer_action': {'transfer_id': 1},
'productDel': {'p_id': 1, 's_id': 1},
'customerEdit': {'c_id': 1},
'rezayatEdit': {'ac_id': 1},
'ActionListUserToday': {'u_id': 1},
'passCheck': {'articl_id': 1},
'update_check_info': {'sarresid_id': 1},
'addSarresid': {'cs_id': 1},
'pardakhtNesiye': {'cs_id': 1},
'sms_template_edit': {'t_id': 1},
'customerSmsHistory': {'c_id': 1},
'sms_customer_toggle': {'c_id': 1},
'sms_primary_toggle': {'c_id': 1},
'sms_send_custom': {'c_id': 1},
'sms_queue_retry': {'q_id': 1},
'sms_queue_approve': {'q_id': 1},
'sms_queue_delete': {'q_id': 1},
}
class UrlContractTests(SimpleTestCase):
def _all_names(self):
from admin_panel.urls import urlpatterns
return {p.name for p in urlpatterns if p.name}
def test_url_name_snapshot_unchanged(self):
names = self._all_names()
missing = EXPECTED_URL_NAMES - names
added = names - EXPECTED_URL_NAMES
self.assertEqual(
missing, set(),
msg=f"این نام‌های URL پس از refactor گم شده‌اند: {sorted(missing)}",
)
self.assertEqual(
added, set(),
msg=f"نام‌های URL جدید و پیش‌بینی‌نشده اضافه شده‌اند: {sorted(added)}",
)
def test_every_url_reverses(self):
for name in sorted(EXPECTED_URL_NAMES):
kwargs = PARAMETERIZED.get(name, {})
with self.subTest(url=name):
try:
reverse(f'admin_panel:{name}', kwargs=kwargs)
except Exception as exc: # noqa: BLE001 - هدف: گزارش دقیق نام مشکل‌دار
self.fail(f"reverse('admin_panel:{name}') شکست خورد: {exc}")
def test_all_view_callables_are_importable(self):
"""هر `views.X` در urls.py باید در پکیج views موجود و قابل فراخوانی باشد."""
from admin_panel import views
from admin_panel.urls import urlpatterns
for pattern in urlpatterns:
if pattern.name not in EXPECTED_URL_NAMES:
# مسیرهای سرو استاتیک (از static()) ویوی اپ نیستند
continue
callback = getattr(pattern, 'callback', None)
if callback is None:
continue
self.assertTrue(
callable(callback),
msg=f"ویو مسیر «{pattern.name}» قابل فراخوانی نیست.",
)
self.assertTrue(
callback.__module__.startswith('admin_panel.views'),
msg=(
f"ویو مسیر «{pattern.name}» بجای پکیج admin_panel.views از "
f"ماژول {callback.__module__} می‌آید."
),
)
# اطمینان از اینکه از دسترس‌پذیری نام‌ها استفاده می‌کنیم
self.assertTrue(hasattr(views, 'p_login'))