""" قرارداد 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'))