功能:隔离五类测试业务链路
This commit is contained in:
@@ -31,6 +31,7 @@ from module_business.dao.inventory_dao import InventoryDao
|
||||
from module_business.dao.master_dao import MasterDao
|
||||
from module_business.dao.operation_dao import OperationDao
|
||||
from module_business.domain.enums import PalletEventType, PalletFlowState
|
||||
from module_business.domain.test_batch_scope import TestBatchScope
|
||||
from module_business.entity.do.master_do import BizCustomerSite, BizPallet, BizProductionLine
|
||||
from module_business.entity.do.operation_do import (
|
||||
BizFillingRecord,
|
||||
@@ -48,6 +49,7 @@ from module_business.entity.vo.operation_vo import (
|
||||
UsageSuccessResponseModel,
|
||||
)
|
||||
from module_business.service.access_service import AccessService
|
||||
from tests.module_business.test_batch_fixtures import make_test_scope
|
||||
|
||||
SERVICE_SPEC = importlib.util.find_spec('module_business.service.usage_service')
|
||||
CONTROLLER_SPEC = importlib.util.find_spec('module_business.controller.usage_controller')
|
||||
@@ -223,6 +225,17 @@ class TestUsageBootstrap(unittest.TestCase):
|
||||
|
||||
@unittest.skipUnless(UsageService is not None, 'UsageService 尚未实现')
|
||||
class TestUsagePublicContract(unittest.TestCase):
|
||||
def test_public_business_methods_require_explicit_test_scope(self) -> None:
|
||||
for method_name in (
|
||||
'get_usage_list_services',
|
||||
'usage_detail_services',
|
||||
'get_line_options_services',
|
||||
'scan_usage_pallet_services',
|
||||
'record_usage_services',
|
||||
):
|
||||
with self.subTest(method=method_name):
|
||||
self.assertIn('test_scope', inspect.signature(getattr(UsageService, method_name)).parameters)
|
||||
|
||||
def test_service_exposes_complete_usage_workflow(self) -> None:
|
||||
expected = {
|
||||
'get_usage_list_services',
|
||||
@@ -235,11 +248,12 @@ class TestUsagePublicContract(unittest.TestCase):
|
||||
|
||||
def test_record_signature_and_types_are_exact(self) -> None:
|
||||
signature = inspect.signature(UsageService.record_usage_services)
|
||||
self.assertEqual(('query_db', 'page_object', 'current_user'), tuple(signature.parameters))
|
||||
self.assertEqual(('query_db', 'page_object', 'current_user', 'test_scope'), tuple(signature.parameters))
|
||||
hints = get_type_hints(UsageService.record_usage_services)
|
||||
self.assertIs(AsyncSession, hints['query_db'])
|
||||
self.assertIs(UsageCreateModel, hints['page_object'])
|
||||
self.assertIs(CurrentUserModel, hints['current_user'])
|
||||
self.assertIs(TestBatchScope, hints['test_scope'])
|
||||
self.assertIs(CrudResponseModel, hints['return'])
|
||||
|
||||
def test_request_hash_is_stable_normalized_and_excludes_idempotency_key(self) -> None:
|
||||
@@ -319,9 +333,15 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[11, 9, 9])),
|
||||
patch.object(OperationDao, 'get_usage_list', new=get_list),
|
||||
):
|
||||
result = await UsageService.get_usage_list_services(self.db, query, self.user)
|
||||
result = await UsageService.get_usage_list_services(self.db, query, self.user, TestBatchScope.formal())
|
||||
|
||||
get_list.assert_awaited_once_with(self.db, query, True, site_ids=[9, 11])
|
||||
get_list.assert_awaited_once_with(
|
||||
self.db,
|
||||
query,
|
||||
site_ids=[9, 11],
|
||||
scope=TestBatchScope.formal(),
|
||||
is_page=True,
|
||||
)
|
||||
self.assertEqual(1, result.total)
|
||||
self.assertIsInstance(result.rows[0], UsageModel)
|
||||
|
||||
@@ -337,6 +357,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
UsagePageQueryModel(pageNum=2, pageSize=7),
|
||||
self.user,
|
||||
TestBatchScope.formal(),
|
||||
is_page=is_page,
|
||||
)
|
||||
|
||||
@@ -360,6 +381,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
UsagePageQueryModel(siteId=10),
|
||||
self.user,
|
||||
TestBatchScope.formal(),
|
||||
)
|
||||
|
||||
self.assertEqual('无权访问该客户现场', raised.exception.message)
|
||||
@@ -377,11 +399,18 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
query,
|
||||
make_user(1),
|
||||
TestBatchScope.formal(),
|
||||
is_page=False,
|
||||
)
|
||||
|
||||
get_scope.assert_not_awaited()
|
||||
get_list.assert_awaited_once_with(self.db, query, False, site_ids=None)
|
||||
get_list.assert_awaited_once_with(
|
||||
self.db,
|
||||
query,
|
||||
site_ids=None,
|
||||
scope=TestBatchScope.formal(),
|
||||
is_page=False,
|
||||
)
|
||||
self.assertEqual([], result)
|
||||
|
||||
async def test_detail_is_scoped_and_hides_missing_but_admin_gets_precise_error(self) -> None:
|
||||
@@ -396,9 +425,14 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(OperationDao, 'get_usage_by_id', new=get_detail),
|
||||
self.assertRaises(expected_exception) as raised,
|
||||
):
|
||||
await UsageService.usage_detail_services(self.db, 61, user)
|
||||
await UsageService.usage_detail_services(self.db, 61, user, TestBatchScope.formal())
|
||||
|
||||
get_detail.assert_awaited_once_with(self.db, 61, site_ids=expected_scope)
|
||||
get_detail.assert_awaited_once_with(
|
||||
self.db,
|
||||
61,
|
||||
TestBatchScope.formal(),
|
||||
site_ids=expected_scope,
|
||||
)
|
||||
self.assertEqual(expected_message, raised.exception.message)
|
||||
|
||||
async def test_line_options_require_site_access_and_only_return_enabled_lines_for_site(self) -> None:
|
||||
@@ -415,14 +449,15 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(MasterDao, 'get_site', new=AsyncMock(return_value=make_site())),
|
||||
patch.object(MasterDao, 'list_line', new=list_line),
|
||||
):
|
||||
result = await UsageService.get_line_options_services(self.db, 9, self.user)
|
||||
result = await UsageService.get_line_options_services(self.db, 9, self.user, TestBatchScope.formal())
|
||||
|
||||
access.assert_awaited_once_with(self.db, self.user, 9)
|
||||
query = list_line.await_args.args[1]
|
||||
self.assertIsInstance(query, LinePageQueryModel)
|
||||
self.assertEqual(9, query.site_id)
|
||||
self.assertEqual('0', query.status)
|
||||
self.assertFalse(list_line.await_args.args[2])
|
||||
self.assertEqual(TestBatchScope.formal(), list_line.await_args.args[2])
|
||||
self.assertFalse(list_line.await_args.args[3])
|
||||
self.assertEqual([3, 6], [option.id for option in result])
|
||||
self.assertTrue(all(isinstance(option, BusinessOptionModel) for option in result))
|
||||
|
||||
@@ -439,7 +474,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(MasterDao, 'list_line', new=list_line),
|
||||
self.assertRaises(PermissionException),
|
||||
):
|
||||
await UsageService.get_line_options_services(self.db, 9, self.user)
|
||||
await UsageService.get_line_options_services(self.db, 9, self.user, TestBatchScope.formal())
|
||||
|
||||
get_site.assert_not_awaited()
|
||||
list_line.assert_not_awaited()
|
||||
@@ -466,10 +501,10 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_pallets_by_ids', new=pallet_lock),
|
||||
patch.object(OperationDao, 'get_active_filling_for_update', new=filling_lock),
|
||||
):
|
||||
result = await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user)
|
||||
result = await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user, TestBatchScope.formal())
|
||||
|
||||
access.assert_awaited_once_with(self.db, self.user, 9)
|
||||
get_filling.assert_awaited_once_with(self.db, 31)
|
||||
get_filling.assert_awaited_once_with(self.db, 31, TestBatchScope.formal())
|
||||
inventory_lock.assert_not_awaited()
|
||||
pallet_lock.assert_not_awaited()
|
||||
filling_lock.assert_not_awaited()
|
||||
@@ -489,7 +524,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=None)),
|
||||
self.assertRaises(PermissionException) as missing,
|
||||
):
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'missing', self.user)
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'missing', self.user, TestBatchScope.formal())
|
||||
self.assertEqual('无权访问该托盘', missing.exception.message)
|
||||
|
||||
with (
|
||||
@@ -497,7 +532,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(MasterDao, 'get_pallet_inventory', new=AsyncMock(return_value=make_inventory(site_id=None))),
|
||||
self.assertRaises(PermissionException) as non_site,
|
||||
):
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user)
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user, TestBatchScope.formal())
|
||||
self.assertEqual('无权访问该托盘', non_site.exception.message)
|
||||
|
||||
with (
|
||||
@@ -510,7 +545,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
),
|
||||
self.assertRaises(PermissionException) as unauthorized,
|
||||
):
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user)
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user, TestBatchScope.formal())
|
||||
self.assertEqual('无权访问该托盘', unauthorized.exception.message)
|
||||
|
||||
async def test_scan_admin_keeps_precise_missing_error(self) -> None:
|
||||
@@ -518,7 +553,7 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=None)),
|
||||
self.assertRaises(ServiceException) as raised,
|
||||
):
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'missing', make_user(1))
|
||||
await UsageService.scan_usage_pallet_services(self.db, 'missing', make_user(1), TestBatchScope.formal())
|
||||
self.assertEqual('托盘不存在', raised.exception.message)
|
||||
|
||||
|
||||
@@ -644,6 +679,7 @@ class TestUsageControllerResponse(unittest.IsolatedAsyncioTestCase):
|
||||
db = MagicMock(spec=AsyncSession)
|
||||
user = make_user()
|
||||
page = make_page()
|
||||
test_scope = make_test_scope(9)
|
||||
service = AsyncMock(
|
||||
return_value=CrudResponseModel(
|
||||
is_success=True,
|
||||
@@ -661,9 +697,10 @@ class TestUsageControllerResponse(unittest.IsolatedAsyncioTestCase):
|
||||
add_usage=page,
|
||||
query_db=db,
|
||||
current_user=user,
|
||||
test_scope=test_scope,
|
||||
)
|
||||
|
||||
service.assert_awaited_once_with(db, page, user)
|
||||
service.assert_awaited_once_with(db, page, user, test_scope)
|
||||
payload = json.loads(response.body)
|
||||
self.assertEqual('使用成功', payload['msg'])
|
||||
self.assertIn('data', payload)
|
||||
@@ -738,12 +775,13 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
async def read_line(
|
||||
db: AsyncSession,
|
||||
line_id: int,
|
||||
scope: TestBatchScope,
|
||||
*,
|
||||
for_update: bool = False,
|
||||
) -> BizProductionLine | None:
|
||||
if for_update:
|
||||
return await line_lock(db, line_id, for_update=True)
|
||||
return await preflight_line_read(db, line_id)
|
||||
return await line_lock(db, line_id, scope, for_update=True)
|
||||
return await preflight_line_read(db, line_id, scope)
|
||||
|
||||
stack.enter_context(patch.object(AccessDao, 'list_user_site_ids', new=scope_read))
|
||||
stack.enter_context(patch.object(AccessService, 'assert_site_access', new=site_access))
|
||||
@@ -884,7 +922,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
patch('module_business.service.usage_service.datetime') as mocked_datetime,
|
||||
):
|
||||
mocked_datetime.now.return_value = self.fixed_time
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
@@ -987,6 +1025,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
make_page(used_packages=3),
|
||||
self.user,
|
||||
TestBatchScope.formal(),
|
||||
)
|
||||
|
||||
self.assertTrue(result.is_success)
|
||||
@@ -1028,6 +1067,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
make_page(used_packages=2),
|
||||
self.user,
|
||||
TestBatchScope.formal(),
|
||||
)
|
||||
self.assertTrue(result.is_success)
|
||||
self.assertEqual(4, same_inventory.current_line_id)
|
||||
@@ -1040,7 +1080,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
stack, mocks = self._patch_record_path(inventory=other_inventory)
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
self.assertIn('已绑定其他产线', raised.exception.message)
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
mocks['add_event'].assert_not_awaited()
|
||||
@@ -1060,7 +1100,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
mocks['preflight_line_read'].return_value = None
|
||||
mocks['line_lock'].return_value = None
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
self.assertEqual(message, raised.exception.message)
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
mocks['add_event'].assert_not_awaited()
|
||||
@@ -1091,7 +1131,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
self.db.reset_mock()
|
||||
stack, mocks = self._patch_record_path(inventory=inventory)
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, page, self.user)
|
||||
await UsageService.record_usage_services(self.db, page, self.user, TestBatchScope.formal())
|
||||
self.assertEqual(message, raised.exception.message)
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
mocks['add_event'].assert_not_awaited()
|
||||
@@ -1102,7 +1142,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
access = AsyncMock(side_effect=PermissionException(data='', message='无权访问该客户现场'))
|
||||
stack, mocks = self._patch_record_path(access=access)
|
||||
with stack, self.assertRaises(PermissionException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual('无权访问该托盘', raised.exception.message)
|
||||
mocks['inventory_lock'].assert_awaited_once()
|
||||
@@ -1127,10 +1167,15 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
access = AsyncMock(side_effect=access_check)
|
||||
stack, mocks = self._patch_record_path(site=make_site(status='1'), access=access)
|
||||
with stack, self.assertRaises(PermissionException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual('无权访问该托盘', raised.exception.message)
|
||||
mocks['site_lock'].assert_awaited_once_with(self.db, 9, for_update=True)
|
||||
mocks['site_lock'].assert_awaited_once_with(
|
||||
self.db,
|
||||
9,
|
||||
TestBatchScope.formal(),
|
||||
for_update=True,
|
||||
)
|
||||
access.assert_awaited_with(self.db, self.user, 9, for_update=True)
|
||||
mocks['line_lock'].assert_not_awaited()
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
@@ -1144,7 +1189,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
if site is None:
|
||||
mocks['site_lock'].return_value = None
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, make_user(1))
|
||||
await UsageService.record_usage_services(self.db, self.page, make_user(1), TestBatchScope.formal())
|
||||
self.assertEqual(message, raised.exception.message)
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
self.assertEqual(2, self.db.rollback.await_count)
|
||||
@@ -1162,7 +1207,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
self.db.reset_mock()
|
||||
stack, mocks = self._patch_record_path(**overrides)
|
||||
with stack, self.assertRaises(ServiceException):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
mocks['update_filling'].assert_not_awaited()
|
||||
mocks['add_event'].assert_not_awaited()
|
||||
@@ -1173,7 +1218,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
inventory = make_inventory(version=-1)
|
||||
stack, mocks = self._patch_record_path(inventory=inventory)
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual('托盘库存版本无效', raised.exception.message)
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
@@ -1188,7 +1233,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=None)),
|
||||
self.assertRaises(PermissionException) as normal_missing,
|
||||
):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
self.assertEqual('无权访问该托盘', normal_missing.exception.message)
|
||||
self.db.rollback.assert_awaited_once()
|
||||
|
||||
@@ -1199,14 +1244,14 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=None)),
|
||||
self.assertRaises(ServiceException) as admin_missing,
|
||||
):
|
||||
await UsageService.record_usage_services(self.db, self.page, make_user(1))
|
||||
await UsageService.record_usage_services(self.db, self.page, make_user(1), TestBatchScope.formal())
|
||||
self.assertEqual('托盘不存在', admin_missing.exception.message)
|
||||
self.db.rollback.assert_awaited_once()
|
||||
|
||||
self.db.reset_mock()
|
||||
stack, mocks = self._patch_record_path(inventory=make_inventory(site_id=None))
|
||||
with stack, self.assertRaises(PermissionException) as non_site:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
self.assertEqual('无权访问该托盘', non_site.exception.message)
|
||||
mocks['pallet_lock'].assert_not_awaited()
|
||||
self.assertEqual(2, self.db.rollback.await_count)
|
||||
@@ -1329,14 +1374,15 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_inventory_for_update', new=inventory_lock),
|
||||
patch.object(OperationDao, 'add_usage_record', new=AsyncMock()) as add_usage,
|
||||
):
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
get_key.assert_awaited_once_with(
|
||||
self.db,
|
||||
'usage-key',
|
||||
TestBatchScope.formal(),
|
||||
site_ids=[9],
|
||||
)
|
||||
site_lock.assert_awaited_once_with(self.db, 9, for_update=True)
|
||||
site_lock.assert_awaited_once_with(self.db, 9, TestBatchScope.formal(), for_update=True)
|
||||
access.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
|
||||
inventory_lock.assert_not_awaited()
|
||||
add_usage.assert_not_awaited()
|
||||
@@ -1367,10 +1413,15 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(MasterDao, 'get_site', new=AsyncMock(return_value=make_site())),
|
||||
patch.object(AccessService, 'assert_site_access', new=AsyncMock()),
|
||||
):
|
||||
result = await UsageService.record_usage_services(self.db, self.page, user)
|
||||
result = await UsageService.record_usage_services(self.db, self.page, user, TestBatchScope.formal())
|
||||
|
||||
line_read.assert_awaited_once_with(self.db, 4)
|
||||
get_key.assert_awaited_once_with(self.db, 'usage-key', site_ids=[9])
|
||||
line_read.assert_awaited_once_with(self.db, 4, TestBatchScope.formal())
|
||||
get_key.assert_awaited_once_with(
|
||||
self.db,
|
||||
'usage-key',
|
||||
TestBatchScope.formal(),
|
||||
site_ids=[9],
|
||||
)
|
||||
self.assertEqual(61, result.result.usage_id)
|
||||
if user.user and user.user.admin:
|
||||
scope_read.assert_not_awaited()
|
||||
@@ -1384,7 +1435,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(InventoryDao, 'get_inventory_for_update', new=AsyncMock()) as inventory_lock,
|
||||
self.assertRaises(ServiceException) as raised,
|
||||
):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertIn('幂等键', raised.exception.message)
|
||||
inventory_lock.assert_not_awaited()
|
||||
@@ -1401,7 +1452,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(AccessService, 'assert_site_access', new=access),
|
||||
self.assertRaises(PermissionException),
|
||||
):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
access.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
|
||||
self.db.commit.assert_not_awaited()
|
||||
@@ -1460,6 +1511,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
self.db,
|
||||
self.page,
|
||||
self.user,
|
||||
TestBatchScope.formal(),
|
||||
)
|
||||
except ServiceException as exc:
|
||||
self.fail(f'精确并发重试被旧库存状态提前拒绝:{exc.message}')
|
||||
@@ -1495,7 +1547,12 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
mocks['filling_lock'].assert_awaited_once()
|
||||
else:
|
||||
mocks['filling_lock'].assert_not_awaited()
|
||||
mocks['site_lock'].assert_awaited_once_with(self.db, 9, for_update=True)
|
||||
mocks['site_lock'].assert_awaited_once_with(
|
||||
self.db,
|
||||
9,
|
||||
TestBatchScope.formal(),
|
||||
for_update=True,
|
||||
)
|
||||
mocks['line_lock'].assert_not_awaited()
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
mocks['add_event'].assert_not_awaited()
|
||||
@@ -1541,7 +1598,9 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
with stack:
|
||||
try:
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
result = await UsageService.record_usage_services(
|
||||
self.db, self.page, self.user, TestBatchScope.formal()
|
||||
)
|
||||
except ServiceException as exc:
|
||||
self.fail(f'锁等待期间新增授权后未使用 fresh scope:{exc.message}')
|
||||
|
||||
@@ -1590,7 +1649,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
with stack:
|
||||
try:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
except PermissionException as exc:
|
||||
self.assertEqual('无权访问该托盘', exc.message)
|
||||
else:
|
||||
@@ -1653,13 +1712,13 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
self.db.rollback.side_effect = rollback
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual('托盘二维码与库存不一致', raised.exception.message)
|
||||
self.assertTrue(scanned.expired)
|
||||
self.assertEqual(1, scanned.pallet_id_reads)
|
||||
mocks['inventory_lock'].assert_awaited_once_with(self.db, 2)
|
||||
mocks['pallet_lock'].assert_awaited_once_with(self.db, [2])
|
||||
mocks['inventory_lock'].assert_awaited_once_with(self.db, 2, TestBatchScope.formal())
|
||||
mocks['pallet_lock'].assert_awaited_once_with(self.db, [2], TestBatchScope.formal())
|
||||
self.assertEqual(2, scope.await_count)
|
||||
self.assertEqual(
|
||||
[{'site_ids': [9]}, {'site_ids': [9]}],
|
||||
@@ -1688,7 +1747,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
access = mocks['access']
|
||||
with stack:
|
||||
try:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
except PermissionException as exc:
|
||||
self.assertEqual('无权访问该使用记录', exc.message)
|
||||
except ServiceException as exc:
|
||||
@@ -1701,7 +1760,12 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
[call.kwargs for call in key_read.await_args_list],
|
||||
)
|
||||
self.assertEqual(2, mocks['scope_read'].await_count)
|
||||
mocks['site_lock'].assert_awaited_once_with(self.db, 9, for_update=True)
|
||||
mocks['site_lock'].assert_awaited_once_with(
|
||||
self.db,
|
||||
9,
|
||||
TestBatchScope.formal(),
|
||||
for_update=True,
|
||||
)
|
||||
self.assertEqual([False, True], [call.kwargs.get('for_update', False) for call in access.await_args_list])
|
||||
mocks['line_lock'].assert_not_awaited()
|
||||
mocks['add_usage'].assert_not_awaited()
|
||||
@@ -1734,7 +1798,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
key_read = mocks['key_read']
|
||||
with stack:
|
||||
try:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
except ServiceException as exc:
|
||||
self.assertEqual(
|
||||
'使用失败,幂等键已被其他请求使用',
|
||||
@@ -1768,7 +1832,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
add_usage=add_usage,
|
||||
)
|
||||
with stack:
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
result = await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertIsInstance(result.result, UsageSuccessResponseModel)
|
||||
self.assertEqual(
|
||||
@@ -1800,7 +1864,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
add_usage=AsyncMock(side_effect=integrity_error()),
|
||||
)
|
||||
with stack, self.assertRaises(ServiceException) as raised:
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
self.assertTrue('幂等' in raised.exception.message or '冲突' in raised.exception.message)
|
||||
self.assertEqual(
|
||||
[{'site_ids': [9]}, {'site_ids': [9]}, {'site_ids': [9]}],
|
||||
@@ -1818,7 +1882,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
add_usage=AsyncMock(side_effect=integrity_error()),
|
||||
)
|
||||
with stack, self.assertRaises(PermissionException):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual(3, scope.await_count)
|
||||
self.assertEqual(2, key_read.await_count)
|
||||
@@ -1835,7 +1899,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
add_event=AsyncMock(side_effect=RuntimeError('event failed')),
|
||||
)
|
||||
with stack, self.assertRaises(RuntimeError):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.assertEqual(2, self.db.rollback.await_count)
|
||||
self.db.commit.assert_not_awaited()
|
||||
@@ -1845,7 +1909,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(UsageService, '_build_request_hash', side_effect=RuntimeError('hash failed')),
|
||||
self.assertRaises(RuntimeError),
|
||||
):
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user)
|
||||
await UsageService.record_usage_services(self.db, self.page, self.user, TestBatchScope.formal())
|
||||
|
||||
self.db.rollback.assert_awaited_once()
|
||||
self.db.commit.assert_not_awaited()
|
||||
|
||||
Reference in New Issue
Block a user