dy-dashboard/tests/test_spot_application_validation.py
2026-10-28 11:14:17 +08:00

168 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""新增申请校验与并发查重回归测试;数据库和操作日志全部模拟。"""
import unittest
from unittest.mock import MagicMock, patch
from flask import Flask, session
from routes import spot
class ApplicationValidationTest(unittest.TestCase):
def setUp(self):
self.app = Flask(__name__)
self.app.secret_key = 'test-only'
self.data = dict(product_name='样品', barcode='0012345678901',
sample_quantity='2', recipient_info='收件人及地址', group_name='京东1组')
self.conn = MagicMock()
self.cur = self.conn.cursor.return_value.__enter__.return_value
self.cur.fetchone.side_effect = [{'acquired': 1}, None]
self.cur.lastrowid = 88
self.db = patch.object(spot, 'get_db', return_value=self.conn).start()
self.log = patch.object(spot, 'add_operation_log').start()
patch.object(spot, 'role_required', return_value=True).start()
self.addCleanup(patch.stopall)
def create(self, changes=None):
data = dict(self.data)
data.update(changes or {})
with self.app.test_request_context('/api/spot/application/create', method='POST', data=data):
session['user_id'] = 7
return spot.application_create.__wrapped__().get_json()
def test_all_required_fields_are_checked_before_db_access(self):
for key in ('product_name', 'barcode', 'recipient_info', 'sample_quantity'):
for value in ('', ' '):
with self.subTest(key=key, value=value):
self.assertEqual(self.create({key: value})['code'], 400)
self.db.assert_not_called()
def test_barcode_requires_exactly_thirteen_ascii_digits(self):
for value in ('123456789012', '12345678901234', 'a123456789012',
'1234567890123', '123456 7890123', '1e12345678901'):
with self.subTest(value=value):
response = self.create({'barcode': value})
self.assertEqual(response['code'], 400)
self.assertIn('请填写正确的条形码', response['msg'])
self.db.assert_not_called()
def test_valid_group_is_saved_and_leading_zeroes_are_preserved(self):
response = self.create()
self.assertEqual(response, {'code': 0, 'id': 88, 'msg': '申请已添加'})
insert = next(c for c in self.cur.execute.call_args_list if 'INSERT INTO spot_applications' in c.args[0])
self.assertIn('group_name', insert.args[0])
self.assertEqual(insert.args[1][2], '京东1组')
self.assertEqual(insert.args[1][-2], '0012345678901')
self.conn.commit.assert_called_once()
self.assertIn('RELEASE_LOCK', self.cur.execute.call_args.args[0])
self.conn.close.assert_called_once()
def test_each_allowed_group_and_blank_are_accepted(self):
for group in (*spot.SPOT_APPLICATION_GROUPS, ''):
self.cur.fetchone.side_effect = [{'acquired': 1}, None]
with self.subTest(group=group):
self.assertEqual(self.create({'group_name': group})['code'], 0)
def test_invalid_group_is_rejected(self):
self.assertEqual(self.create({'group_name': '其他组'})['code'], 400)
self.db.assert_not_called()
def test_department_options_are_saved(self):
for department in ('洁维塔', '泊紫汀兰', ''):
self.cur.fetchone.side_effect = [{'acquired': 1}, None]
self.assertEqual(self.create({'department': department})['code'], 0)
insert = next(c for c in reversed(self.cur.execute.call_args_list)
if 'INSERT INTO' in c.args[0])
self.assertEqual(insert.args[1][1], department)
def test_invalid_department_is_rejected(self):
for changes in ({'department': '其他部门'},):
self.assertEqual(self.create(changes)['code'], 400)
self.db.assert_not_called()
def test_jieweita_requires_group_before_database_access(self):
for group in ('', ' '):
result = self.create({'brand': '洁维塔', 'group_name': group})
self.assertEqual(result['code'], 400)
self.assertIn('必须选择组别', result['msg'])
self.db.assert_not_called()
def test_bozitinglan_accepts_blank_group(self):
self.assertEqual(self.create({'brand': '泊紫汀兰', 'group_name': ''})['code'], 0)
def test_shenzhen_template_overrides_tampered_recipient_fields(self):
result = self.create(dict(brand='泊紫汀兰', recipient_template='shenzhen',
recipient_address='其他地址', recipient_name='其他人',
recipient_phone='000', recipient_info='篡改的信息'))
self.assertEqual(result['code'], 0)
insert = next(c for c in self.cur.execute.call_args_list if 'INSERT INTO' in c.args[0])
self.assertEqual(insert.args[1][-1],
'深圳市龙岗区坂田街道发达路6号云里智能园7栋3楼蝶雅东侧\n收件人:一部样品\n电话:17771977816')
def test_guangzhou_template_accepts_contact_edits_but_keeps_address(self):
result = self.create(dict(brand='洁维塔', recipient_template='guangzhou',
recipient_address='其他地址', recipient_name=' 张三 ',
recipient_phone=' 13800138000 '))
self.assertEqual(result['code'], 0)
insert = next(c for c in self.cur.execute.call_args_list if 'INSERT INTO' in c.args[0])
self.assertEqual(insert.args[1][-1],
'广州市白云区鹤龙街道广云路238号思成未来城市总部5楼\n收件人:张三\n电话:13800138000')
def test_invalid_template_and_empty_guangzhou_contacts_are_rejected(self):
for changes in ({'recipient_template': ''}, {'recipient_template': 'other'},
{'recipient_template': 'guangzhou', 'recipient_name': ' ', 'recipient_phone': '123'},
{'recipient_template': 'guangzhou', 'recipient_name': '张三', 'recipient_phone': ' '}):
self.assertEqual(self.create(changes)['code'], 400)
self.db.assert_not_called()
def test_free_text_brand_is_accepted(self):
self.assertEqual(self.create({'brand': ' 其他品牌 '})['code'], 0)
insert = next(c for c in self.cur.execute.call_args_list if 'INSERT INTO' in c.args[0])
self.assertIn('其他品牌', insert.args[1])
def test_overlong_brand_is_rejected(self):
self.assertEqual(self.create({'brand': '品' * 101})['code'], 400)
self.db.assert_not_called()
def test_duplicate_name_or_barcode_is_rejected_under_lock(self):
self.cur.fetchone.side_effect = [{'acquired': 1}, {'id': 3}]
response = self.create()
self.assertEqual(response['code'], 409)
self.assertIn('存在重复值', response['msg'])
calls = self.cur.execute.call_args_list
self.assertIn('GET_LOCK', calls[0].args[0])
self.assertIn('TRIM(product_name)=%s OR TRIM(barcode)=%s', calls[1].args[0])
self.assertEqual(calls[1].args[1], ('样品', '0012345678901'))
self.assertFalse(any('INSERT INTO' in c.args[0] for c in calls))
self.conn.commit.assert_not_called()
self.assertIn('RELEASE_LOCK', calls[-1].args[0])
def test_lock_timeout_never_inserts(self):
self.cur.fetchone.side_effect = [{'acquired': 0}]
self.assertEqual(self.create()['code'], 409)
self.conn.commit.assert_not_called()
self.assertFalse(any('INSERT INTO' in c.args[0] for c in self.cur.execute.call_args_list))
self.conn.close.assert_called_once()
def test_negative_fractional_and_overflow_quantity_rejected(self):
for quantity in ('-1', '1.5', '2147483648'):
self.assertEqual(self.create({'sample_quantity': quantity})['code'], 400)
self.db.assert_not_called()
def test_audit_failure_does_not_report_already_saved_request_as_failed(self):
self.log.side_effect = RuntimeError('audit unavailable')
self.assertEqual(self.create()['code'], 0)
self.conn.commit.assert_called_once()
def test_insert_failure_rolls_back_and_releases_lock(self):
def execute(sql, params=None):
if 'INSERT INTO' in sql:
raise RuntimeError('database unavailable')
self.cur.execute.side_effect = execute
self.assertEqual(self.create()['code'], 500)
self.conn.commit.assert_not_called()
self.conn.rollback.assert_called()
self.assertIn('RELEASE_LOCK', self.cur.execute.call_args.args[0])
if __name__ == '__main__':
unittest.main()