refactor
This commit is contained in:
1 parent
6f36db63a1
commit
250cdbab7f
42 files changed
+96363
-11699
No files matched your search
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
تست دودی (Smoke Test) تمام اندپوینتهای اپ admin_panel.
|
||||
|
||||
چرا این تست حیاتی است؟
|
||||
`python manage.py check` خطاهای `NameError`/`ImportError` را که داخل *بدنهٔ توابع*
|
||||
ویو رخ میدهند نمیبیند. پس از تقسیم `views.py` به پکیج `views/`، تنها راه مطمئن
|
||||
برای کشف importهای جاافتاده، فراخوانی واقعی هر ویو است. این ماژول دقیقاً همان کار
|
||||
را انجام میدهد: هر مسیر urls.py با یک کاربر مجاز (superuser) فراخوانی میشود و
|
||||
هیچ پاسخی نباید کد ۵۰۰ داشته باشد.
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from admin_panel.models import (Action, Article, Factor, Sarresid,
|
||||
StoreTransferRequest)
|
||||
|
||||
from .factories import (make_category, make_customer, make_product, make_store,
|
||||
make_user, stock_in)
|
||||
|
||||
# کدهای وضعیتی که «نتیجهٔ قابلقبول» تلقی میشوند (نشانهٔ اجرای سالم ویو)
|
||||
ACCEPTABLE = {200, 201, 302, 400, 403, 404, 405}
|
||||
|
||||
GET = 'get'
|
||||
POST = 'post'
|
||||
|
||||
|
||||
class ViewSmokeTests(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = make_user('smokeAdmin', superuser=True)
|
||||
cls.other_user = make_user('smokeOther')
|
||||
# انبار اصلی که کاربر مالک آن است (owners) و دسترسی مشاهده دارد
|
||||
cls.store = make_store('انبار اصلی', owner=cls.user)
|
||||
# نکته: کاربر عمداً به انبار دوم اضافه نمیشود؛ چون چند ویو از
|
||||
# `StoreRoom.objects.get(user__id=...)` استفاده میکنند که با دو انبار
|
||||
# خطای MultipleObjectsReturned میدهد (رفتار پیشین کد، نه ناشی از refactor).
|
||||
cls.store2 = make_store('انبار دوم')
|
||||
|
||||
cls.category = make_category('دسته اصلی')
|
||||
cls.product = make_product('کالای اصلی', price=2000, category=cls.category)
|
||||
cls.spare_product = make_product('کالای حذفی', price=1500, category=cls.category)
|
||||
stock_in(cls.store, cls.product, 100)
|
||||
stock_in(cls.store, cls.spare_product, 5)
|
||||
|
||||
cls.customer = make_customer('مشتری اصلی')
|
||||
cls.spare_customer = make_customer('مشتری خام')
|
||||
# یک سند مالی تا ویو «بدهی» مسیر موفق را طی کند (با دیتابیس کاملاً خالی،
|
||||
# این ویو به دلیل باگ ازپیشموجود UnboundLocalError خطای ۵۰۰ میدهد).
|
||||
Article.objects.create(customer=cls.customer, debit=1000, credit=0, desc='سند آغازین دودی')
|
||||
cls.factor = Factor.objects.create(customer=cls.customer, amount=1000, pay_status='unpaid')
|
||||
cls.spare_factor = Factor.objects.create(customer=cls.customer, amount=1000, pay_status='unpaid')
|
||||
|
||||
cls.action = Action.objects.create(
|
||||
customer=cls.customer, forUser=cls.user, actionType='bazdid', status='waite',
|
||||
actionDateTime=timezone.now(),
|
||||
)
|
||||
cls.sarresid = Sarresid.objects.create(
|
||||
customer=cls.customer, price=5000, payStatus='false', type='check',
|
||||
sarresidDT=timezone.now(),
|
||||
)
|
||||
cls.transfer = StoreTransferRequest.objects.create(
|
||||
source_store=cls.store, target_store=cls.store2,
|
||||
product=cls.product, quantity=1, requester=cls.user,
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(self.user)
|
||||
# در صورت خطای سرور، استثنا را نگه میداریم تا کد وضعیت ۵۰۰ را ببینیم
|
||||
self.client.raise_request_exception = False
|
||||
|
||||
# ------------------------------------------------------------------ helpers
|
||||
|
||||
def _now_ms(self):
|
||||
return int(timezone.now().timestamp() * 1000)
|
||||
|
||||
def _url(self, name, **kwargs):
|
||||
return reverse(f'admin_panel:{name}', kwargs=kwargs)
|
||||
|
||||
def _endpoints(self):
|
||||
"""(نام مسیر, kwargs, متد, دادهٔ ارسالی)"""
|
||||
ms = self._now_ms()
|
||||
c = self.customer
|
||||
return [
|
||||
# --- داشبوردها و گزارشها ---
|
||||
('index', {}, GET, None),
|
||||
('p_login', {}, GET, None),
|
||||
('bedehi', {}, GET, None),
|
||||
('report', {}, GET, None),
|
||||
('inventory_movements_report', {}, GET, None),
|
||||
('financial_dashboard', {}, GET, None),
|
||||
('report_debtors_no_vosol', {}, GET, None),
|
||||
('rezayat_dashboard', {}, GET, None),
|
||||
('hesabdari', {}, GET, None),
|
||||
('hesabdari_residnashone', {}, GET, None),
|
||||
('hesabdari_check_pass', {}, GET, None),
|
||||
('hesabdari_check_pass_fail', {}, GET, None),
|
||||
('bedehiKhadamat', {}, GET, None),
|
||||
('bedehiNasb', {}, GET, None),
|
||||
# --- مشتریان ---
|
||||
('customers', {}, GET, None),
|
||||
# مسیر از converter نوع <slug> استفاده میکند، پس فقط کاراکترهای مجاز slug
|
||||
('customersSearch', {'searchVal': 'ali', 'searchOn': 'name'}, GET, None),
|
||||
('customersAdd', {}, POST, {
|
||||
'an_name': 'مشتری دودی', 'an_address': 'تهران',
|
||||
'installDate': '1400/01/01', 'idnCode': '111',
|
||||
'phone_titles': ['موبایل'], 'phones': ['09120000000'],
|
||||
}),
|
||||
('customer', {'c_id': c.id}, GET, None),
|
||||
('customer2', {'c_id': c.id}, GET, None),
|
||||
('customerNew', {'c_id': c.id}, GET, None),
|
||||
('customerAction', {'c_id': c.id}, GET, None),
|
||||
('customerNazar', {'c_id': c.id}, GET, None),
|
||||
('customerNewAction', {'c_id': c.id}, POST, {
|
||||
'actionType': 'bazdid', 'dateTimeAnjam': ms,
|
||||
'userID': self.user.id, 'desc': 'اقدام دودی',
|
||||
}),
|
||||
('customerEditAction', {'c_id': c.id, 'action_id': self.action.id}, POST, {
|
||||
'actionType': 'bazdid', 'dateTimeAnjam': ms,
|
||||
'userID': self.user.id, 'desc': 'ویرایش دودی',
|
||||
}),
|
||||
('customerDel', {'c_id': self.spare_customer.id}, GET, None),
|
||||
('customerFactors', {'c_id': c.id}, GET, None),
|
||||
('customerFactor', {'c_id': c.id, 'f_id': self.factor.id}, GET, None),
|
||||
('FactorDel', {'c_id': c.id, 'f_id': self.spare_factor.id}, POST, {
|
||||
'cancel_reason': 'error', 'payment_action': 'keep',
|
||||
}),
|
||||
('newFactor', {'c_id': c.id}, POST, {
|
||||
'prdsList': json.dumps([]), 'payType': 'naghd',
|
||||
'FactorType': 'Nasb', 'FactorDesc': 'دودی', 'sarResidDate': '',
|
||||
}),
|
||||
('newFactor2', {'c_id': c.id}, POST, {
|
||||
'prdsList': json.dumps([]), 'PardakhtHa': json.dumps({}),
|
||||
'FactorType': 'Nasb', 'FactorDesc': 'دودی', 'sarResidDate': '',
|
||||
}),
|
||||
('customerHesabdari', {'c_id': c.id}, GET, None),
|
||||
('customerSarresidh', {'c_id': c.id}, GET, None),
|
||||
('customerHesabdariAdd', {'c_id': c.id}, POST, {
|
||||
'price': '1000', 'type': 'debit', 'typePy': 'nasb',
|
||||
'desc': 'سند دودی', 'nahvePardakhat': 'نقد',
|
||||
}),
|
||||
# --- JSON ---
|
||||
('productsJson', {}, GET, None),
|
||||
('ActionJson', {'action_id': self.action.id}, GET, None),
|
||||
('CustomerJson', {'c_id': c.id}, GET, None),
|
||||
('productMojodi', {'p_id': self.product.id}, GET, None),
|
||||
# --- انبار ---
|
||||
('stores', {}, GET, None),
|
||||
('store', {'s_id': self.store.id}, GET, None),
|
||||
('operations', {'s_id': self.store.id}, GET, None),
|
||||
('operationProduct', {'s_id': self.store.id, 'p_id': self.product.id}, GET, None),
|
||||
('newOperation', {'s_id': self.store.id, 'p_id': self.product.id}, GET, None),
|
||||
('newStore', {}, POST, {'storeName': 'انبار دودی'}),
|
||||
('store_transfers', {'s_id': self.store.id}, GET, None),
|
||||
('process_transfer_action', {'transfer_id': self.transfer.id}, POST, {'action': 'invalid'}),
|
||||
('productAdd', {}, POST, {
|
||||
'namePrd': 'کالای دودی', 'category': self.category.id,
|
||||
'price': '1000', 'mojodiAval': '5', 'storeroom': self.store.id,
|
||||
}),
|
||||
('productEdit', {}, POST, {
|
||||
'prd_code': self.product.id, 'namePrd': 'کالای ویرایششده',
|
||||
'category': self.category.id, 'price': '2500',
|
||||
}),
|
||||
('operation', {}, POST, {
|
||||
'prd_id': self.product.id, 'actionType': 'vorod',
|
||||
'tedad': '3', 'azanbar': self.store.id, 'desc': 'ورود دودی',
|
||||
}),
|
||||
('productDel', {'p_id': self.spare_product.id, 's_id': self.store.id}, GET, None),
|
||||
('customerEdit', {'c_id': c.id}, POST, {
|
||||
'an_name': 'مشتری ویرایشدودی', 'an_address': 'کرج',
|
||||
'installDate': '1400/02/02', 'idnCode': '222',
|
||||
'phone_titles': ['موبایل'], 'phones': ['09120000001'],
|
||||
}),
|
||||
# --- اقدامات و نظرسنجی ---
|
||||
('rezayat', {}, GET, None),
|
||||
('rezayatEdit', {'ac_id': self.action.id}, POST, {'csGrade': '20', 'csDesc': 'عالی'}),
|
||||
('entezar', {}, GET, None),
|
||||
('ActionList', {}, GET, None),
|
||||
('ActionListToday', {}, GET, None),
|
||||
('ActionListUserToday', {'u_id': self.user.id}, GET, None),
|
||||
('ActionListAll', {}, GET, None),
|
||||
# --- چک و سررسید ---
|
||||
('passCheck', {'articl_id': self.sarresid.id}, POST, {'message': 'وصول شد'}),
|
||||
('update_check_info', {'sarresid_id': self.sarresid.id}, POST, {
|
||||
'check_row_number': '1', 'check_sayyad_id': '1234567890123456',
|
||||
'check_owner_name': 'مالک', 'check_owner_phone': '09120000000',
|
||||
}),
|
||||
('addSarresid', {'cs_id': c.id}, POST, {
|
||||
'n_sarresidDateTime': ms, 'n_sarresidPrice': '1000',
|
||||
'n_sarresidType': 'nesiye',
|
||||
}),
|
||||
('pardakhtNesiye', {'cs_id': c.id}, POST, {
|
||||
'next_sarresidDateTime': ms, 'next_sarresidPrice': '100',
|
||||
'sr_articlID': self.sarresid.id, 'nahvePardakht': 'naghd',
|
||||
}),
|
||||
('api_check_status_inquiry', {}, GET, None),
|
||||
('api_check_sabt_inquiry', {}, GET, None),
|
||||
# در انتهای لیست: خروج از حساب، چون بعد از آن تمام درخواستها بیاعتبار میشوند
|
||||
('p_logout', {}, GET, None),
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------ tests
|
||||
|
||||
def test_every_endpoint_responds_without_server_error(self):
|
||||
failures = []
|
||||
for name, kwargs, method, data in self._endpoints():
|
||||
url = self._url(name, **kwargs)
|
||||
with self.subTest(endpoint=name):
|
||||
# HTTP_REFERER لازم است چون چند ویو با redirect(request.META['HTTP_REFERER'])
|
||||
# بازمیگردند و مرورگر واقعی همیشه آن را میفرستد.
|
||||
headers = {'HTTP_REFERER': '/panel/'}
|
||||
if method == GET:
|
||||
response = self.client.get(url, **headers)
|
||||
else:
|
||||
response = self.client.post(url, data or {}, **headers)
|
||||
|
||||
if response.status_code not in ACCEPTABLE:
|
||||
failures.append((name, response.status_code, url))
|
||||
|
||||
self.assertEqual(
|
||||
failures, [],
|
||||
msg=(
|
||||
"این اندپوینتها پاسخ غیرمنتظره (احتمالاً خطای ۵۰۰ ناشی از import جاافتاده) "
|
||||
f"دادهاند: {failures}"
|
||||
),
|
||||
)
|
||||
Reference in new issue
Block a user