""" تست‌های قرارداد مشتری: افزودن، ویرایش و نگهبان حذف (عدم حذف مشتری دارای سابقه). """ from django.test import TestCase from django.urls import reverse from admin_panel.models import Article, Customer, Factor, PhoneNumber from .factories import login_superuser, make_customer, make_user class CustomerCrudTests(TestCase): def setUp(self): self.user = login_superuser(self.client, 'csUser') def test_add_customer_creates_phones(self): url = reverse('admin_panel:customersAdd') response = self.client.post(url, { 'an_name': 'علی رضایی', 'an_address': 'تهران', 'installDate': '1400/01/01', 'idnCode': '1234567890', 'phone_titles': ['موبایل', 'ثابت', ''], 'phones': ['09120000000', '02112345678', ''], }) self.assertIn(response.status_code, (200, 302)) customer = Customer.objects.get(name_family='علی رضایی') self.assertEqual(customer.phone1, '09120000000') self.assertEqual(customer.phone2, '02112345678') # ردیف خالی نباید ذخیره شود self.assertEqual(PhoneNumber.objects.filter(customer=customer).count(), 2) def test_edit_customer_replaces_phones(self): customer = make_customer('مشتری ویرایش', phones=['09120000001']) url = reverse('admin_panel:customerEdit', kwargs={'c_id': customer.id}) response = self.client.post(url, { 'an_name': 'مشتری ویرایش شده', 'an_address': 'شیراز', 'installDate': '1401/02/03', 'idnCode': '999', 'laghv_bazdid': 'true', 'phone_titles': ['موبایل جدید'], 'phones': ['09120009999'], }) self.assertIn(response.status_code, (200, 302)) customer.refresh_from_db() self.assertEqual(customer.name_family, 'مشتری ویرایش شده') self.assertTrue(customer.laghv_bazdid) phones = list(PhoneNumber.objects.filter(customer=customer)) self.assertEqual(len(phones), 1) self.assertEqual(phones[0].phone_number, '09120009999') class CustomerInfoCardUITests(TestCase): """کارت پروفایل مشتری (ستون راست): ساختار حرفه‌ای، ویرایش و تعیین شمارهٔ اصلی. این تست‌ها قرارداد ویوها/قالب‌ها را قفل می‌کنند تا بازطراحی UI به ویرایش مشتری یا تعیین «شمارهٔ اصلی پیامک» آسیب نزند. """ def setUp(self): self.user = login_superuser(self.client, 'cardUser') def _customer_page(self, customer): return self.client.get( reverse('admin_panel:customer', kwargs={'c_id': customer.id}), ).content.decode('utf-8') def test_profile_card_structure_is_rendered(self): customer = make_customer('مشتری کارت', phones=['09120000000']) html = self._customer_page(customer) for needle in ('has-background-white brad5', 'id="formcsEdit"', 'dynamic-phones-edit', 'شماره‌های تماس', 'لغو بازدید'): self.assertIn(needle, html, msg=needle) def test_template_comments_never_leak_into_the_page(self): """رگرسیون: کامنت چندخطی {# #} جنگو معتبر نیست و به‌صورت متن چاپ می‌شود. قالب کارت پروفایل باید از ``{% comment %}`` استفاده کند؛ این تست تضمین می‌کند هیچ متن کامنتی در HTML نهایی دیده نشود. """ customer = make_customer('مشتری کامنت', phones=['09120000000']) html = self._customer_page(customer) for leaked in ('تک‌منبع', 'DRY', 'شناسه‌های موردنیاز', 'کارت پروفایل مشتری —'): self.assertNotIn(leaked, html, msg=f'متن کامنت در صفحه لو رفته: {leaked}') def test_edit_controls_present_and_single_form(self): customer = make_customer('مشتری ویرایش‌کارت', phones=['09120000000']) html = self._customer_page(customer) for needle in ('id="formcsEdit"', 'id="enEdit"', 'id="editCs"', 'id="cancelEdit"', 'id="showUserDT"'): self.assertIn(needle, html, msg=needle) # نباید فرم تودرتو ساخته شود؛ فقط یک فرم ویرایش در ستون وجود دارد. self.assertEqual(html.count('id="formcsEdit"'), 1) def test_primary_toggle_present_next_to_phone(self): customer = make_customer('مشتری اصلی‌کارت') PhoneNumber.objects.create(customer=customer, phone_number='09120000000') html = self._customer_page(customer) self.assertIn('sms-primary-toggle', html) self.assertIn('data-phone="09120000000"', html) self.assertIn( 'data-url="%s"' % reverse('admin_panel:sms_primary_toggle', kwargs={'c_id': customer.id}), html, ) def test_card_also_renders_on_legacy_base_page(self): """صفحهٔ جزئیات فاکتور (customerFactor) از customerBase.html استفاده می‌کند.""" factor = Factor.objects.create(customer=make_customer('مشتری فاکتورکارت'), amount=1000, pay_status='unpaid') html = self.client.get( reverse('admin_panel:customerFactor', kwargs={'c_id': factor.customer_id, 'f_id': factor.id}), ).content.decode('utf-8') self.assertIn('has-background-white brad5', html) self.assertIn('id="formcsEdit"', html) def test_edit_persists_phones_and_primary_selection(self): """ویرایش مشتری با markup جدید باید شماره‌ها و انتخاب «اصلی» را ذخیره کند.""" customer = make_customer('مشتری ویرایش‌نهایی', phones=['09120000001']) url = reverse('admin_panel:customerEdit', kwargs={'c_id': customer.id}) response = self.client.post(url, { 'an_name': 'مشتری ویرایش‌نهایی', 'an_address': 'قم', 'installDate': '1401/02/03', 'idnCode': '555', 'laghv_bazdid': 'true', 'notes': 'یادداشت', 'phone_titles': ['موبایل'], 'phones': ['09120009999'], 'phone_primary': ['0'], }) self.assertIn(response.status_code, (200, 302)) customer.refresh_from_db() self.assertEqual(customer.name_family, 'مشتری ویرایش‌نهایی') self.assertTrue(customer.laghv_bazdid) self.assertEqual(customer.notes, 'یادداشت') phones = list(PhoneNumber.objects.filter(customer=customer)) self.assertEqual(len(phones), 1) self.assertEqual(phones[0].phone_number, '09120009999') # شمارهٔ تیک‌خورده به‌عنوان «اصلی» ذخیره شده است. self.assertEqual(customer.sms_primary_phones, '+989120009999') class InactiveCustomerHiddenTests(TestCase): """مشتری غیرفعال (is_active=False) در فهرست و در جستجو دیده نمی‌شود.""" def setUp(self): self.user = login_superuser(self.client, 'hideUser') self.active = make_customer('خطیبی فعال', phones=['09120000041']) self.inactive = make_customer('خطیبی غیرفعال', is_active=False, phones=['09120000042']) # جستجوی تلفن از ستون phone1 می‌خواند (فیلد قدیمی مشتری). Customer.objects.filter(pk=self.active.pk).update(phone1='09120000041') Customer.objects.filter(pk=self.inactive.pk).update(phone1='09120000042') def test_inactive_absent_from_default_listing(self): html = self.client.get(reverse('admin_panel:customers')).content.decode('utf-8') self.assertIn('خطیبی فعال', html) self.assertNotIn('خطیبی غیرفعال', html) def test_inactive_absent_from_search_by_name(self): html = self.client.get( reverse('admin_panel:customers'), {'searchVal': 'خطیبی', 'searchOn': 'name'}, ).content.decode('utf-8') self.assertIn('خطیبی فعال', html) self.assertNotIn('خطیبی غیرفعال', html) def test_inactive_absent_from_search_by_phone(self): html = self.client.get( reverse('admin_panel:customers'), {'searchVal': '0912000004', 'searchOn': 'phone'}, ).content.decode('utf-8') self.assertIn('09120000041', html) self.assertNotIn('09120000042', html) def test_inactive_absent_from_path_search_view(self): # مسیر فقط نویسه‌های لاتین می‌پذیرد. make_customer('khatibi-active') make_customer('khatibi-inactive', is_active=False) html = self.client.get( reverse('admin_panel:customersSearch', kwargs={'searchVal': 'khatibi', 'searchOn': 'name'}), ).content.decode('utf-8') self.assertIn('khatibi-active', html) self.assertNotIn('khatibi-inactive', html) class CustomerDeleteGuardTests(TestCase): def setUp(self): self.user = make_user('delUser', superuser=True) self.client.force_login(self.user) def test_customer_without_history_is_deactivated(self): customer = make_customer('مشتری خام') url = reverse('admin_panel:customerDel', kwargs={'c_id': customer.id}) self.client.get(url) customer.refresh_from_db() # حذف نرم: رکورد باقی می‌ماند ولی غیرفعال می‌شود self.assertFalse(customer.is_active) def test_customer_with_financial_history_is_never_deleted(self): customer = make_customer('مشتری دارای سابقه') Article.objects.create(customer=customer, debit=1000, credit=0, desc='سابقه مالی') url = reverse('admin_panel:customerDel', kwargs={'c_id': customer.id}) self.client.get(url) customer.refresh_from_db() self.assertTrue(customer.is_active, msg="مشتری دارای سابقه مالی نباید حذف/غیرفعال شود.") self.assertTrue(Customer.objects.filter(id=customer.id).exists())