功能:隔离五类测试业务链路

This commit is contained in:
焦龙言
2026-09-23 08:27:14 +08:00
parent 43fe17cc70
commit f5df2150d8
18 changed files with 1206 additions and 831 deletions
@@ -22,7 +22,7 @@ from common.vo import (
PageResponseModel,
ResponseBaseModel,
)
from exceptions.exception import PermissionException, ServiceException
from exceptions.exception import PermissionException, ServiceException, ServiceWarning
from module_admin.entity.vo.user_vo import CurrentUserModel, UserInfoModel
from module_admin.service.log_service import LogQueueService
from module_admin.service.login_service import LoginService
@@ -33,6 +33,7 @@ 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.pallet_state_machine import PalletStateMachine
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.do.master_do import BizPallet
from module_business.entity.do.operation_do import (
BizInboundItem,
@@ -129,6 +130,20 @@ def make_inbound(
class TestInboundPublicContract(unittest.TestCase):
def test_public_business_methods_require_explicit_test_scope(self) -> None:
for method_name in (
'get_inbound_list_services',
'inbound_detail_services',
'get_warehouse_options_services',
'scan_inbound_pallet_services',
'add_inbound_services',
'edit_inbound_services',
'delete_inbound_services',
'confirm_inbound_services',
):
with self.subTest(method=method_name):
self.assertIn('test_scope', inspect.signature(getattr(InboundService, method_name)).parameters)
def test_service_and_controller_are_available(self) -> None:
self.assertIsNotNone(InboundService)
self.assertIsNotNone(inbound_controller_module.inbound_controller)
@@ -136,7 +151,7 @@ class TestInboundPublicContract(unittest.TestCase):
def test_confirm_signature_and_types_are_exact(self) -> None:
signature = inspect.signature(InboundService.confirm_inbound_services)
self.assertEqual(
('query_db', 'inbound_id', 'page_object', 'current_user'),
('query_db', 'inbound_id', 'page_object', 'current_user', 'test_scope'),
tuple(signature.parameters),
)
hints = get_type_hints(InboundService.confirm_inbound_services)
@@ -228,7 +243,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(inbound_service_module, 'datetime') as service_datetime,
):
service_datetime.now.return_value = self.fixed_time
result = await InboundService.add_inbound_services(self.db, page, self.user)
result = await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.assertEqual(
[
@@ -242,8 +257,8 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
],
actions,
)
inventory_lock.assert_awaited_once_with(self.db, [2, 5])
pallet_lock.assert_awaited_once_with(self.db, [2, 5])
inventory_lock.assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
pallet_lock.assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
token_hex.assert_called_once_with(16)
order = captured['order']
self.assertEqual('IN-fixed-random', order.inbound_no)
@@ -288,7 +303,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaises(ServiceException),
):
await InboundService.add_inbound_services(self.db, page, self.user)
await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
add_order.assert_not_awaited()
replace_items.assert_not_awaited()
@@ -313,7 +328,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=lock),
self.assertRaises(exception_type),
):
await InboundService.add_inbound_services(self.db, page, self.user)
await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
lock.assert_not_awaited()
self.db.rollback.assert_awaited_once()
@@ -333,7 +348,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=lock),
self.assertRaises(PermissionException),
):
await InboundService.add_inbound_services(self.db, page, self.user)
await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
lock.assert_not_awaited()
self.db.rollback.assert_awaited_once()
@@ -351,7 +366,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=pallet_lock),
self.assertRaises(Exception) as raised,
):
await InboundService.add_inbound_services(self.db, page, self.user)
await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.assertIsInstance(raised.exception, PermissionException)
self.assertEqual('无权访问该仓库', raised.exception.message)
@@ -385,11 +400,11 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_inbound_items', new=replace_items),
self.assertRaises(PermissionException) as raised,
):
await InboundService.add_inbound_services(self.db, page, self.user)
await InboundService.add_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该仓库', raised.exception.message)
self.assertEqual(2, warehouse_read.await_count)
warehouse_read.assert_awaited_with(self.db, 7, for_update=True)
warehouse_read.assert_awaited_with(self.db, 7, TestBatchScope.formal(), for_update=True)
self.assertEqual(2, warehouse_mapping.await_count)
warehouse_mapping.assert_awaited_with(self.db, self.user, 7, for_update=True)
add_order.assert_not_awaited()
@@ -427,12 +442,13 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(inbound_service_module, 'datetime') as service_datetime,
):
service_datetime.now.return_value = self.fixed_time
result = await InboundService.edit_inbound_services(self.db, page, self.user)
result = await InboundService.edit_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.default_scope.assert_awaited_once_with(self.db, 7)
lock_order.assert_awaited_once_with(
self.db,
11,
TestBatchScope.formal(),
warehouse_ids=[2, 5, 7, 8, 11],
)
self.assertEqual(
@@ -443,8 +459,8 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
],
access.await_args_list,
)
inventory_lock.assert_awaited_once_with(self.db, [2, 5])
pallet_lock.assert_awaited_once_with(self.db, [2, 5])
inventory_lock.assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
pallet_lock.assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
self.assertEqual(8, inbound.warehouse_id)
self.assertEqual('user-7', inbound.update_by)
self.assertEqual(self.fixed_time, inbound.update_time)
@@ -478,7 +494,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
MasterDao,
'get_warehouse',
new=AsyncMock(
side_effect=lambda _db, warehouse_id, **_kwargs: SimpleNamespace(
side_effect=lambda _db, warehouse_id, _scope, **_kwargs: SimpleNamespace(
warehouse_id=warehouse_id,
status='0',
)
@@ -493,7 +509,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_inbound_items', new=replace),
self.assertRaises(PermissionException) as raised,
):
await InboundService.edit_inbound_services(self.db, page, self.user)
await InboundService.edit_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该入库单', raised.exception.message)
warehouse_mapping.assert_any_await(self.db, self.user, 7, for_update=True)
@@ -519,7 +535,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'update_inbound', new=update),
self.assertRaises(ServiceException),
):
await InboundService.edit_inbound_services(self.db, page, self.user)
await InboundService.edit_inbound_services(self.db, page, self.user, TestBatchScope.formal())
lock_pallets.assert_not_awaited()
update.assert_not_awaited()
@@ -537,12 +553,12 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=inventory_lock),
self.assertRaises(Exception) as raised,
):
await InboundService.edit_inbound_services(self.db, page, self.user)
await InboundService.edit_inbound_services(self.db, page, self.user, TestBatchScope.formal())
self.assertIsInstance(raised.exception, PermissionException)
self.assertEqual('无权访问该入库单', raised.exception.message)
scope.assert_awaited_once_with(self.db, 7)
lock_order.assert_awaited_once_with(self.db, 404, warehouse_ids=[7])
lock_order.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=[7])
get_warehouse.assert_not_awaited()
inventory_lock.assert_not_awaited()
self.db.rollback.assert_awaited_once()
@@ -556,130 +572,54 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_for_update', new=lock_order),
self.assertRaises(ServiceException) as raised,
):
await InboundService.edit_inbound_services(self.db, page, make_user(1))
await InboundService.edit_inbound_services(self.db, page, make_user(1), TestBatchScope.formal())
self.assertEqual('入库单不存在', raised.exception.message)
scope.assert_not_awaited()
lock_order.assert_awaited_once_with(self.db, 404, warehouse_ids=None)
lock_order.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=None)
self.db.rollback.assert_awaited_once()
async def test_batch_delete_locks_and_preflights_all_before_ordered_delete(self) -> None:
actions: list[str] = []
async def lock(
_db: AsyncSession,
inbound_id: int,
warehouse_ids: list[int] | None = None,
) -> BizInboundOrder:
self.assertEqual([2, 5, 7, 8, 11], warehouse_ids)
actions.append(f'lock-{inbound_id}')
return make_inbound(inbound_id=inbound_id, warehouse_id=inbound_id)
async def delete(_db: AsyncSession, inbound_id: int) -> None:
actions.append(f'delete-{inbound_id}')
async def commit() -> None:
actions.append('commit')
self.db.commit.side_effect = commit
access = AsyncMock()
lock = AsyncMock()
delete = AsyncMock()
with (
patch.object(OperationDao, 'get_inbound_for_update', new=AsyncMock(side_effect=lock)),
patch.object(
MasterDao,
'get_warehouse',
new=AsyncMock(
side_effect=lambda _db, warehouse_id, **_kwargs: SimpleNamespace(
warehouse_id=warehouse_id,
status='0',
)
),
),
patch.object(AccessService, 'assert_warehouse_access', new=access),
patch.object(OperationDao, 'delete_inbound', new=AsyncMock(side_effect=delete)),
patch.object(OperationDao, 'get_inbound_for_update', new=lock),
patch.object(OperationDao, 'delete_inbound', new=delete),
self.assertRaises(ServiceWarning),
):
result = await InboundService.delete_inbound_services(self.db, [5, 2], self.user)
await InboundService.delete_inbound_services(self.db, [5, 2], self.user, TestBatchScope.formal())
self.assertEqual(
['lock-2', 'lock-5', 'delete-2', 'delete-5', 'commit'],
actions,
)
self.default_scope.assert_awaited_once_with(self.db, 7)
self.assertEqual([2, 5], [call.args[2] for call in access.await_args_list])
self.assertTrue(all(call.kwargs.get('for_update') for call in access.await_args_list))
self.assertTrue(result.is_success)
self.db.rollback.assert_not_awaited()
lock.assert_not_awaited()
delete.assert_not_awaited()
self.db.commit.assert_not_awaited()
async def test_batch_delete_rechecks_all_current_warehouses_before_first_delete(self) -> None:
revoked_warehouse_id = 8
documents = {
11: make_inbound(inbound_id=11, warehouse_id=revoked_warehouse_id),
12: make_inbound(inbound_id=12, warehouse_id=7),
}
async def warehouse_access(
_db: AsyncSession,
_user: CurrentUserModel,
warehouse_id: int,
**kwargs: Any,
) -> None:
if warehouse_id == revoked_warehouse_id and kwargs.get('for_update'):
raise PermissionException(data='', message='无权访问该仓库')
lock = AsyncMock()
delete = AsyncMock()
warehouse_mapping = AsyncMock(side_effect=warehouse_access)
with (
patch.object(
OperationDao,
'get_inbound_for_update',
new=AsyncMock(side_effect=lambda _db, inbound_id, **_kwargs: documents[inbound_id]),
),
patch.object(
MasterDao,
'get_warehouse',
new=AsyncMock(
side_effect=lambda _db, warehouse_id, **_kwargs: SimpleNamespace(
warehouse_id=warehouse_id,
status='0',
)
),
),
patch.object(AccessService, 'assert_warehouse_access', new=warehouse_mapping),
patch.object(OperationDao, 'get_inbound_for_update', new=lock),
patch.object(OperationDao, 'delete_inbound', new=delete),
self.assertRaises(PermissionException) as raised,
self.assertRaises(ServiceWarning),
):
await InboundService.delete_inbound_services(self.db, [12, 11], self.user)
await InboundService.delete_inbound_services(self.db, [12, 11], self.user, TestBatchScope.formal())
self.assertEqual('无权访问该入库单', raised.exception.message)
self.assertEqual([7, 8], [call.args[2] for call in warehouse_mapping.await_args_list])
self.assertTrue(all(call.kwargs.get('for_update') for call in warehouse_mapping.await_args_list))
lock.assert_not_awaited()
delete.assert_not_awaited()
self.db.commit.assert_not_awaited()
self.db.rollback.assert_awaited_once()
async def test_batch_delete_is_atomic_when_later_document_is_confirmed(self) -> None:
lock = AsyncMock()
delete = AsyncMock()
records = {
2: make_inbound(inbound_id=2, warehouse_id=2),
5: make_inbound(inbound_id=5, warehouse_id=5, status='CONFIRMED'),
}
with (
patch.object(
OperationDao,
'get_inbound_for_update',
new=AsyncMock(
side_effect=lambda _db, inbound_id, warehouse_ids=None: records[inbound_id],
),
),
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock()),
patch.object(OperationDao, 'get_inbound_for_update', new=lock),
patch.object(OperationDao, 'delete_inbound', new=delete),
self.assertRaises(ServiceException),
self.assertRaises(ServiceWarning),
):
await InboundService.delete_inbound_services(self.db, [5, 2], self.user)
await InboundService.delete_inbound_services(self.db, [5, 2], self.user, TestBatchScope.formal())
lock.assert_not_awaited()
delete.assert_not_awaited()
self.db.commit.assert_not_awaited()
self.db.rollback.assert_awaited_once()
async def test_delete_scopes_parent_lock_and_hides_unmatched_document(self) -> None:
lock_order = AsyncMock(return_value=None)
@@ -688,16 +628,13 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7])) as scope,
patch.object(OperationDao, 'get_inbound_for_update', new=lock_order),
patch.object(OperationDao, 'delete_inbound', new=delete),
self.assertRaises(Exception) as raised,
self.assertRaises(ServiceWarning),
):
await InboundService.delete_inbound_services(self.db, [404], self.user)
await InboundService.delete_inbound_services(self.db, [404], self.user, TestBatchScope.formal())
self.assertIsInstance(raised.exception, PermissionException)
self.assertEqual('无权访问该入库单', raised.exception.message)
scope.assert_awaited_once_with(self.db, 7)
lock_order.assert_awaited_once_with(self.db, 404, warehouse_ids=[7])
scope.assert_not_awaited()
lock_order.assert_not_awaited()
delete.assert_not_awaited()
self.db.rollback.assert_awaited_once()
async def test_admin_missing_delete_keeps_precise_not_found(self) -> None:
scope = AsyncMock()
@@ -705,14 +642,12 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=scope),
patch.object(OperationDao, 'get_inbound_for_update', new=lock_order),
self.assertRaises(ServiceException) as raised,
self.assertRaises(ServiceWarning),
):
await InboundService.delete_inbound_services(self.db, [404], make_user(1))
await InboundService.delete_inbound_services(self.db, [404], make_user(1), TestBatchScope.formal())
self.assertEqual('入库单不存在', raised.exception.message)
scope.assert_not_awaited()
lock_order.assert_awaited_once_with(self.db, 404, warehouse_ids=None)
self.db.rollback.assert_awaited_once()
lock_order.assert_not_awaited()
async def test_all_draft_mutations_convert_integrity_errors(self) -> None:
add_page = InboundCreateModel(warehouseId=7, palletIds=[2])
@@ -732,7 +667,7 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'add_inbound', new=AsyncMock(side_effect=integrity_error())),
self.assertRaises(ServiceException),
):
await InboundService.add_inbound_services(self.db, add_page, self.user)
await InboundService.add_inbound_services(self.db, add_page, self.user, TestBatchScope.formal())
self.db.rollback.assert_awaited_once()
self.db.reset_mock()
@@ -749,10 +684,9 @@ class TestInboundDraftService(unittest.IsolatedAsyncioTestCase):
),
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock()),
patch.object(OperationDao, 'delete_inbound', new=AsyncMock(side_effect=integrity_error())),
self.assertRaises(ServiceException),
self.assertRaises(ServiceWarning),
):
await InboundService.delete_inbound_services(self.db, [11], self.user)
self.db.rollback.assert_awaited_once()
await InboundService.delete_inbound_services(self.db, [11], self.user, TestBatchScope.formal())
class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
@@ -774,10 +708,18 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7, 3, 7])) as scope,
patch.object(OperationDao, 'get_inbound_list', new=get_list),
):
actual = await InboundService.get_inbound_list_services(self.db, query, self.user, is_page=True)
actual = await InboundService.get_inbound_list_services(
self.db, query, self.user, TestBatchScope.formal(), is_page=True
)
scope.assert_awaited_once_with(self.db, 7)
get_list.assert_awaited_once_with(self.db, query, True, warehouse_ids=[3, 7])
get_list.assert_awaited_once_with(
self.db,
query,
warehouse_ids=[3, 7],
scope=TestBatchScope.formal(),
is_page=True,
)
self.assertIsInstance(actual, PageModel)
self.assertIsInstance(actual.rows[0], InboundModel)
@@ -790,10 +732,18 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(AccessDao, 'list_user_warehouse_ids', new=scope),
patch.object(OperationDao, 'get_inbound_list', new=get_list),
):
await InboundService.get_inbound_list_services(self.db, query, make_user(1), is_page=True)
await InboundService.get_inbound_list_services(
self.db, query, make_user(1), TestBatchScope.formal(), is_page=True
)
scope.assert_not_awaited()
get_list.assert_awaited_once_with(self.db, query, True, warehouse_ids=None)
get_list.assert_awaited_once_with(
self.db,
query,
warehouse_ids=None,
scope=TestBatchScope.formal(),
is_page=True,
)
async def test_explicit_unauthorized_warehouse_is_403_before_query(self) -> None:
query = InboundPageQueryModel(warehouseId=9)
@@ -803,7 +753,9 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_list', new=get_list),
self.assertRaises(PermissionException),
):
await InboundService.get_inbound_list_services(self.db, query, self.user, is_page=True)
await InboundService.get_inbound_list_services(
self.db, query, self.user, TestBatchScope.formal(), is_page=True
)
get_list.assert_not_awaited()
async def test_user_without_scope_gets_empty_paginated_result_without_total_leak(self) -> None:
@@ -814,9 +766,17 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[])),
patch.object(OperationDao, 'get_inbound_list', new=get_list),
):
actual = await InboundService.get_inbound_list_services(self.db, query, self.user, is_page=True)
actual = await InboundService.get_inbound_list_services(
self.db, query, self.user, TestBatchScope.formal(), is_page=True
)
get_list.assert_awaited_once_with(self.db, query, True, warehouse_ids=[])
get_list.assert_awaited_once_with(
self.db,
query,
warehouse_ids=[],
scope=TestBatchScope.formal(),
is_page=True,
)
self.assertEqual([], actual.rows)
self.assertEqual(0, actual.total)
self.assertFalse(actual.has_next)
@@ -834,10 +794,10 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_by_id', new=get_detail),
patch.object(AccessService, 'assert_warehouse_access', new=access),
):
actual = await InboundService.inbound_detail_services(self.db, 11, self.user)
actual = await InboundService.inbound_detail_services(self.db, 11, self.user, TestBatchScope.formal())
scope.assert_awaited_once_with(self.db, 7)
get_detail.assert_awaited_once_with(self.db, 11, warehouse_ids=[3, 7])
get_detail.assert_awaited_once_with(self.db, 11, TestBatchScope.formal(), warehouse_ids=[3, 7])
access.assert_not_awaited()
self.assertIsInstance(actual, InboundModel)
@@ -848,11 +808,11 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_by_id', new=get_detail),
self.assertRaises(Exception) as raised,
):
await InboundService.inbound_detail_services(self.db, 404, self.user)
await InboundService.inbound_detail_services(self.db, 404, self.user, TestBatchScope.formal())
self.assertIsInstance(raised.exception, PermissionException)
self.assertEqual('无权访问该入库单', raised.exception.message)
get_detail.assert_awaited_once_with(self.db, 404, warehouse_ids=[7])
get_detail.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=[7])
async def test_admin_missing_detail_keeps_precise_not_found(self) -> None:
scope = AsyncMock()
@@ -862,11 +822,11 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_by_id', new=get_detail),
self.assertRaises(ServiceException) as raised,
):
await InboundService.inbound_detail_services(self.db, 404, make_user(1))
await InboundService.inbound_detail_services(self.db, 404, make_user(1), TestBatchScope.formal())
self.assertEqual('入库单不存在', raised.exception.message)
scope.assert_not_awaited()
get_detail.assert_awaited_once_with(self.db, 404, warehouse_ids=None)
get_detail.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=None)
async def test_warehouse_options_filter_disabled_and_unauthorized_records(self) -> None:
warehouses = [
@@ -878,11 +838,12 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(MasterDao, 'list_warehouse', new=AsyncMock(return_value=warehouses)) as list_warehouse,
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[2, 3])),
):
actual = await InboundService.get_warehouse_options_services(self.db, self.user)
actual = await InboundService.get_warehouse_options_services(self.db, self.user, TestBatchScope.formal())
query = list_warehouse.await_args.args[1]
self.assertEqual('0', query.status)
self.assertIs(False, list_warehouse.await_args.args[2])
self.assertEqual(TestBatchScope.formal(), list_warehouse.await_args.args[2])
self.assertIs(False, list_warehouse.await_args.args[3])
self.assertEqual([BusinessOptionModel(id=2, code='W2', name='二仓')], actual)
self.assertEqual({'id', 'code', 'name'}, set(BusinessOptionModel.model_fields))
@@ -896,7 +857,7 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(MasterDao, 'list_warehouse', new=AsyncMock(return_value=warehouses)),
patch.object(AccessDao, 'list_user_warehouse_ids', new=scope),
):
actual = await InboundService.get_warehouse_options_services(self.db, make_user(1))
actual = await InboundService.get_warehouse_options_services(self.db, make_user(1), TestBatchScope.formal())
scope.assert_not_awaited()
self.assertEqual([2, 5], [option.id for option in actual])
@@ -914,7 +875,7 @@ class TestInboundReadService(unittest.IsolatedAsyncioTestCase):
patch.object(MasterDao, 'list_warehouse', new=AsyncMock(return_value=warehouses)),
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[2])),
):
actual = await InboundService.get_warehouse_options_services(self.db, self.user)
actual = await InboundService.get_warehouse_options_services(self.db, self.user, TestBatchScope.formal())
self.assertEqual([BusinessOptionModel(id=2, code='W2', name='二仓')], actual)
@@ -945,11 +906,12 @@ class TestInboundScanService(unittest.IsolatedAsyncioTestCase):
'persisted-token',
self.query,
self.user,
TestBatchScope.formal(),
)
access.assert_awaited_once_with(self.db, self.user, 7)
get_pallet.assert_awaited_once_with(self.db, 'persisted-token')
get_inventory.assert_awaited_once_with(self.db, 2)
get_pallet.assert_awaited_once_with(self.db, 'persisted-token', TestBatchScope.formal())
get_inventory.assert_awaited_once_with(self.db, 2, TestBatchScope.formal())
locking_read.assert_not_awaited()
add_event.assert_not_awaited()
self.db.commit.assert_not_awaited()
@@ -987,6 +949,7 @@ class TestInboundScanService(unittest.IsolatedAsyncioTestCase):
'persisted-token',
self.query,
self.user,
TestBatchScope.formal(),
)
async def test_scan_rejects_unauthorized_disabled_or_missing_target_warehouse_first(self) -> None:
@@ -1001,7 +964,9 @@ class TestInboundScanService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=get_pallet),
self.assertRaises(PermissionException),
):
await InboundService.scan_inbound_pallet_services(self.db, 'token', self.query, self.user)
await InboundService.scan_inbound_pallet_services(
self.db, 'token', self.query, self.user, TestBatchScope.formal()
)
get_pallet.assert_not_awaited()
for warehouse in (None, SimpleNamespace(warehouse_id=7, status='1')):
@@ -1013,7 +978,9 @@ class TestInboundScanService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=get_pallet),
self.assertRaises(ServiceException),
):
await InboundService.scan_inbound_pallet_services(self.db, 'token', self.query, self.user)
await InboundService.scan_inbound_pallet_services(
self.db, 'token', self.query, self.user, TestBatchScope.formal()
)
get_pallet.assert_not_awaited()
@@ -1045,12 +1012,14 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=inventory_lock),
self.assertRaises(Exception) as raised,
):
await InboundService.confirm_inbound_services(self.db, 404, self.command, self.user)
await InboundService.confirm_inbound_services(
self.db, 404, self.command, self.user, TestBatchScope.formal()
)
self.assertIsInstance(raised.exception, PermissionException)
self.assertEqual('无权访问该入库单', raised.exception.message)
scope.assert_awaited_once_with(self.db, 7)
get_detail.assert_awaited_once_with(self.db, 404, warehouse_ids=[7])
get_detail.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=[7])
get_by_key.assert_not_awaited()
lock_order.assert_not_awaited()
inventory_lock.assert_not_awaited()
@@ -1068,11 +1037,13 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_for_update', new=lock_order),
self.assertRaises(ServiceException) as raised,
):
await InboundService.confirm_inbound_services(self.db, 404, self.command, make_user(1))
await InboundService.confirm_inbound_services(
self.db, 404, self.command, make_user(1), TestBatchScope.formal()
)
self.assertEqual('入库单不存在', raised.exception.message)
scope.assert_not_awaited()
get_detail.assert_awaited_once_with(self.db, 404, warehouse_ids=None)
get_detail.assert_awaited_once_with(self.db, 404, TestBatchScope.formal(), warehouse_ids=None)
get_by_key.assert_not_awaited()
lock_order.assert_not_awaited()
self.db.rollback.assert_awaited_once()
@@ -1169,7 +1140,9 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patches[10] as warehouse_lock,
):
service_datetime.now.return_value = self.fixed_time
result = await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
result = await InboundService.confirm_inbound_services(
self.db, 11, self.command, self.user, TestBatchScope.formal()
)
return {
'result': result,
@@ -1209,17 +1182,33 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
captured['actions'],
)
self.default_scope.assert_awaited_once_with(self.db, 7)
captured['detail_read'].assert_awaited_once_with(self.db, 11, warehouse_ids=[7])
captured['detail_read'].assert_awaited_once_with(
self.db,
11,
TestBatchScope.formal(),
warehouse_ids=[7],
)
captured['key_read'].assert_awaited_once_with(
self.db,
'confirm-key',
TestBatchScope.formal(),
warehouse_ids=[7],
)
captured['order_lock'].assert_awaited_once_with(self.db, 11, warehouse_ids=[7])
captured['warehouse_lock'].assert_awaited_once_with(self.db, 7, for_update=True)
captured['order_lock'].assert_awaited_once_with(
self.db,
11,
TestBatchScope.formal(),
warehouse_ids=[7],
)
captured['warehouse_lock'].assert_awaited_once_with(
self.db,
7,
TestBatchScope.formal(),
for_update=True,
)
captured['access_probe'].assert_awaited_once_with(self.db, self.user, 7, for_update=True)
captured['inventory_lock'].assert_awaited_once_with(self.db, [2, 5])
captured['pallet_lock'].assert_awaited_once_with(self.db, [2, 5])
captured['inventory_lock'].assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
captured['pallet_lock'].assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
captured['ensure_transition'].assert_has_calls(
[
unittest.mock.call(PalletFlowState.UNSTOCKED, PalletFlowState.EMPTY_IN_WAREHOUSE),
@@ -1299,10 +1288,10 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaises(PermissionException) as raised,
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该入库单', raised.exception.message)
warehouse_lock.assert_awaited_once_with(self.db, 7, for_update=True)
warehouse_lock.assert_awaited_once_with(self.db, 7, TestBatchScope.formal(), for_update=True)
warehouse_mapping.assert_awaited_once_with(self.db, self.user, 7, for_update=True)
self.assertEqual('DRAFT', inbound.status)
self.assertEqual('UNSTOCKED', inventory.flow_state)
@@ -1328,9 +1317,9 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaises(ServiceException),
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
pallet_lock.assert_awaited_once_with(self.db, [2, 5])
pallet_lock.assert_awaited_once_with(self.db, [2, 5], TestBatchScope.formal())
self.assertEqual('DRAFT', inbound.status)
self.assertEqual('UNSTOCKED', inventories[0].flow_state)
update.assert_not_awaited()
@@ -1360,7 +1349,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaisesRegex(ValueError, 'invalid'),
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
self.assertEqual('DRAFT', inbound.status)
self.assertEqual('UNSTOCKED', inventory.flow_state)
@@ -1391,10 +1380,12 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
new=AsyncMock(return_value=SimpleNamespace(warehouse_id=7, status='0')),
) as warehouse_lock,
):
result = await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
result = await InboundService.confirm_inbound_services(
self.db, 11, self.command, self.user, TestBatchScope.formal()
)
lock.assert_awaited_once_with(self.db, 11, warehouse_ids=[7])
warehouse_lock.assert_awaited_once_with(self.db, 7, for_update=True)
lock.assert_awaited_once_with(self.db, 11, TestBatchScope.formal(), warehouse_ids=[7])
warehouse_lock.assert_awaited_once_with(self.db, 7, TestBatchScope.formal(), for_update=True)
self.assertEqual(
[
unittest.mock.call(self.db, self.user, 7),
@@ -1424,6 +1415,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
async def read_key(
_db: AsyncSession,
_key: str,
_scope: TestBatchScope,
*,
warehouse_ids: list[int] | None = None,
) -> BizInboundOrder | None:
@@ -1455,13 +1447,15 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
11,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
get_detail.assert_awaited_once_with(self.db, 11, warehouse_ids=[7, 8])
get_detail.assert_awaited_once_with(self.db, 11, TestBatchScope.formal(), warehouse_ids=[7, 8])
key_read.assert_awaited_once_with(
self.db,
'confirm-key',
TestBatchScope.formal(),
warehouse_ids=[7],
)
self.db.rollback.assert_awaited_once()
@@ -1496,10 +1490,10 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(AccessService, 'assert_warehouse_access', new=warehouse_mapping),
self.assertRaises(PermissionException) as raised,
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该入库单', raised.exception.message)
warehouse_lock.assert_awaited_once_with(self.db, 7, for_update=True)
warehouse_lock.assert_awaited_once_with(self.db, 7, TestBatchScope.formal(), for_update=True)
warehouse_mapping.assert_awaited_with(self.db, self.user, 7, for_update=True)
inventory_lock.assert_not_awaited()
self.db.commit.assert_not_awaited()
@@ -1526,7 +1520,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_inbound_for_update', new=lock),
self.assertRaises(ServiceException),
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
access.assert_awaited_once_with(self.db, self.user, 8)
lock.assert_not_awaited()
@@ -1548,7 +1542,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'get_inventories_for_update', new=inventory_lock),
self.assertRaises(ServiceException),
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
inventory_lock.assert_not_awaited()
self.db.rollback.assert_awaited_once()
@@ -1574,10 +1568,12 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'update_inbound', new=AsyncMock()),
patch.object(InventoryDao, 'add_event', new=AsyncMock()),
):
result = await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
result = await InboundService.confirm_inbound_services(
self.db, 11, self.command, self.user, TestBatchScope.formal()
)
get_detail.assert_awaited_once_with(self.db, 11, warehouse_ids=[7])
inventory_lock.assert_awaited_once_with(self.db, [5])
get_detail.assert_awaited_once_with(self.db, 11, TestBatchScope.formal(), warehouse_ids=[7])
inventory_lock.assert_awaited_once_with(self.db, [5], TestBatchScope.formal())
self.assertEqual('EMPTY_IN_WAREHOUSE', inventory.flow_state)
self.assertTrue(result.is_success)
@@ -1608,7 +1604,9 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'update_inbound', new=AsyncMock()),
patch.object(InventoryDao, 'add_event', new=AsyncMock()),
):
result = await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
result = await InboundService.confirm_inbound_services(
self.db, 11, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual(2, parent_lock.await_count)
self.assertEqual(
@@ -1621,8 +1619,8 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
self.assertEqual(2, key_reads.await_count)
key_reads.assert_has_awaits(
[
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', TestBatchScope.formal(), warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', TestBatchScope.formal(), warehouse_ids=[7]),
]
)
self.assertEqual(2, self.db.rollback.await_count)
@@ -1647,6 +1645,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
async def read_key(
_db: AsyncSession,
_key: str,
_scope: TestBatchScope,
*,
warehouse_ids: list[int] | None = None,
) -> BizInboundOrder | None:
@@ -1683,6 +1682,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
11,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
@@ -1690,8 +1690,8 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
self.assertEqual(2, parent_lock.await_count)
self.assertEqual(
[
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', TestBatchScope.formal(), warehouse_ids=[7]),
unittest.mock.call(self.db, 'confirm-key', TestBatchScope.formal(), warehouse_ids=[7]),
],
key_read.await_args_list,
)
@@ -1743,11 +1743,11 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=AsyncMock()),
self.assertRaises(PermissionException) as raised,
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该入库单', raised.exception.message)
self.assertEqual(2, parent_lock.await_count)
parent_lock.assert_awaited_with(self.db, 11, warehouse_ids=[7])
parent_lock.assert_awaited_with(self.db, 11, TestBatchScope.formal(), warehouse_ids=[7])
self.assertEqual(2, warehouse_lock.await_count)
self.assertEqual(2, locked_access_count)
self.assertEqual(2, self.db.rollback.await_count)
@@ -1782,7 +1782,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=AsyncMock()),
self.assertRaises(ServiceException),
):
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user)
await InboundService.confirm_inbound_services(self.db, 11, self.command, self.user, TestBatchScope.formal())
self.assertEqual(2, self.db.rollback.await_count)
@@ -1836,6 +1836,7 @@ class TestInboundLogPermission(unittest.IsolatedAsyncioTestCase):
add_inbound=InboundCreateModel(warehouseId=404, palletIds=[2]),
query_db=db,
current_user=user,
test_scope=TestBatchScope.formal(),
)
finally:
RequestContext.reset_current_user(user_token)