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

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
@@ -23,7 +23,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.login_service import LoginService
from module_business.controller import shipment_controller as shipment_controller_module
@@ -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 BizCustomerSite, BizPallet, BizWarehouse
from module_business.entity.do.operation_do import (
BizFillingRecord,
@@ -152,6 +153,21 @@ def make_shipment(
class ShipmentServiceBootstrapTest(unittest.TestCase):
def test_public_business_methods_require_explicit_test_scope(self) -> None:
for method_name in (
'get_shipment_list_services',
'shipment_detail_services',
'get_warehouse_options_services',
'get_site_options_services',
'scan_shipment_pallet_services',
'add_shipment_services',
'edit_shipment_services',
'delete_shipment_services',
'confirm_shipment_services',
):
with self.subTest(method=method_name):
self.assertIn('test_scope', inspect.signature(getattr(ShipmentService, method_name)).parameters)
def test_shipment_service_module_exists(self) -> None:
spec = importlib.util.find_spec('module_business.service.shipment_service')
@@ -164,7 +180,7 @@ class ShipmentServiceBootstrapTest(unittest.TestCase):
)
signature = inspect.signature(ShipmentService.confirm_shipment_services)
self.assertEqual(
('query_db', 'shipment_id', 'page_object', 'current_user'),
('query_db', 'shipment_id', 'page_object', 'current_user', 'test_scope'),
tuple(signature.parameters),
)
hints = get_type_hints(ShipmentService.confirm_shipment_services)
@@ -215,25 +231,29 @@ class ShipmentInfrastructureContractTest(unittest.TestCase):
OperationDao.get_shipment_list: (
'db',
'query_object',
'is_page',
'warehouse_ids',
'site_ids',
'scope',
'is_page',
),
OperationDao.get_shipment_by_id: (
'db',
'shipment_id',
'scope',
'warehouse_ids',
'site_ids',
),
OperationDao.get_shipment_for_update: (
'db',
'shipment_id',
'scope',
'warehouse_ids',
'site_ids',
),
OperationDao.get_shipment_by_confirm_idempotency_key: (
'db',
'idempotency_key',
'scope',
'warehouse_ids',
'site_ids',
),
@@ -271,6 +291,7 @@ class ShipmentDetailDaoTest(unittest.IsolatedAsyncioTestCase):
12,
warehouse_ids=[7],
site_ids=[9],
scope=TestBatchScope.formal(),
)
self.assertIs(shipment, actual)
@@ -322,6 +343,7 @@ class ShipmentDetailDaoTest(unittest.IsolatedAsyncioTestCase):
12,
warehouse_ids=[7],
site_ids=[9],
scope=TestBatchScope.formal(),
)
self.assertIs(shipment, actual)
@@ -472,6 +494,7 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
self.db,
query,
self.user,
TestBatchScope.formal(),
is_page=True,
)
except NotImplementedError:
@@ -500,9 +523,10 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
self.db,
ShipmentPageQueryModel(),
self.user,
TestBatchScope.formal(),
)
try:
detail = await ShipmentService.shipment_detail_services(self.db, 12, self.user)
detail = await ShipmentService.shipment_detail_services(self.db, 12, self.user, TestBatchScope.formal())
except NotImplementedError:
self.fail('双范围单据详情尚未实现')
@@ -511,13 +535,15 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
get_list.assert_awaited_once_with(
self.db,
unittest.mock.ANY,
True,
warehouse_ids=[7],
site_ids=[9],
scope=TestBatchScope.formal(),
is_page=True,
)
get_detail.assert_awaited_once_with(
self.db,
12,
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
)
@@ -540,8 +566,12 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(MasterDao, 'list_site', new=AsyncMock(return_value=sites)),
):
try:
warehouse_options = await ShipmentService.get_warehouse_options_services(self.db, self.user)
site_options = await ShipmentService.get_site_options_services(self.db, self.user)
warehouse_options = await ShipmentService.get_warehouse_options_services(
self.db, self.user, TestBatchScope.formal()
)
site_options = await ShipmentService.get_site_options_services(
self.db, self.user, TestBatchScope.formal()
)
except NotImplementedError:
self.fail('发货仓库/现场选项尚未实现')
@@ -598,6 +628,7 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
'qr-2',
query,
self.user,
TestBatchScope.formal(),
)
except NotImplementedError:
self.fail('整托发货扫码尚未实现')
@@ -606,7 +637,11 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(PalletFlowState.FILLED_IN_WAREHOUSE, result.flow_state)
self.assertEqual(8, result.remaining_packages)
self.assertEqual(['shipment'], result.allowed_actions)
get_filling.assert_awaited_once_with(self.db, inventory.active_filling_id)
get_filling.assert_awaited_once_with(
self.db,
inventory.active_filling_id,
TestBatchScope.formal(),
)
lock_inventory.assert_not_awaited()
lock_pallets.assert_not_awaited()
lock_filling.assert_not_awaited()
@@ -627,7 +662,7 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_shipment_list', new=get_list),
self.assertRaises(PermissionException) as raised,
):
await ShipmentService.get_shipment_list_services(self.db, query, self.user)
await ShipmentService.get_shipment_list_services(self.db, query, self.user, TestBatchScope.formal())
self.assertEqual(expected_message, raised.exception.message)
get_list.assert_not_awaited()
@@ -640,12 +675,13 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_shipment_by_id', new=get_detail),
self.assertRaises(PermissionException) as user_raised,
):
await ShipmentService.shipment_detail_services(self.db, 404, self.user)
await ShipmentService.shipment_detail_services(self.db, 404, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该发货单', user_raised.exception.message)
get_detail.assert_awaited_once_with(
self.db,
404,
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
)
@@ -655,11 +691,12 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_shipment_by_id', new=get_detail),
self.assertRaises(ServiceException) as admin_raised,
):
await ShipmentService.shipment_detail_services(self.db, 404, make_user(1))
await ShipmentService.shipment_detail_services(self.db, 404, make_user(1), TestBatchScope.formal())
self.assertEqual('发货单不存在', admin_raised.exception.message)
get_detail.assert_awaited_once_with(
self.db,
404,
TestBatchScope.formal(),
warehouse_ids=None,
site_ids=None,
)
@@ -715,6 +752,7 @@ class ShipmentReadServiceTest(unittest.IsolatedAsyncioTestCase):
'hidden-token',
ShipmentScanQueryModel(sourceWarehouseId=7, targetSiteId=9),
self.user,
TestBatchScope.formal(),
)
self.assertEqual('无权访问该托盘', raised.exception.message)
@@ -747,6 +785,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
self.db,
[2],
7,
TestBatchScope.formal(),
)
self.assertEqual([inventory], inventories)
@@ -794,7 +833,11 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
actions.append('pallet-lock')
return pallets
async def lock_filling(_db: AsyncSession, filling_id: int) -> BizFillingRecord:
async def lock_filling(
_db: AsyncSession,
filling_id: int,
_scope: TestBatchScope,
) -> BizFillingRecord:
actions.append(f'filling-lock-{filling_id}')
return fillings[filling_id]
@@ -844,7 +887,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
service_datetime.now.return_value = self.fixed_time
service_secrets.token_hex.return_value = 'draft-token'
try:
result = await ShipmentService.add_shipment_services(self.db, page, self.user)
result = await ShipmentService.add_shipment_services(self.db, page, self.user, TestBatchScope.formal())
except NotImplementedError:
self.fail('发货草稿新增尚未实现')
@@ -854,8 +897,8 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual([2, 5], [item.pallet_id for item in captured['items']])
self.assertTrue(all(item.active_filling_id is None for item in captured['items']))
self.assertTrue(all(item.package_qty is None for item in captured['items']))
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(
[
'warehouse-access',
@@ -928,11 +971,11 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_shipment_items', new=replace_items),
self.assertRaises(PermissionException) as raised,
):
await ShipmentService.add_shipment_services(self.db, page, self.user)
await ShipmentService.add_shipment_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()
@@ -980,10 +1023,10 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_shipment_items', new=replace_items),
self.assertRaises(ServiceException) as raised,
):
await ShipmentService.add_shipment_services(self.db, page, self.user)
await ShipmentService.add_shipment_services(self.db, page, self.user, TestBatchScope.formal())
self.assertEqual('来源仓库已停用', raised.exception.message)
warehouse_read.assert_awaited_with(self.db, 7, for_update=True)
warehouse_read.assert_awaited_with(self.db, 7, TestBatchScope.formal(), for_update=True)
add_order.assert_not_awaited()
replace_items.assert_not_awaited()
self.db.commit.assert_not_awaited()
@@ -1062,7 +1105,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
):
service_datetime.now.return_value = self.fixed_time
try:
result = await ShipmentService.edit_shipment_services(self.db, page, self.user)
result = await ShipmentService.edit_shipment_services(self.db, page, self.user, TestBatchScope.formal())
except NotImplementedError:
self.fail('发货草稿编辑尚未实现')
@@ -1070,6 +1113,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
lock_order.assert_awaited_once_with(
self.db,
12,
TestBatchScope.formal(),
warehouse_ids=[7, 8],
site_ids=[9, 10],
)
@@ -1129,6 +1173,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_warehouse(
_db: AsyncSession,
warehouse_id: int,
_scope: TestBatchScope,
**_kwargs: Any,
) -> BizWarehouse:
return BizWarehouse(
@@ -1141,6 +1186,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_site(
_db: AsyncSession,
site_id: int,
_scope: TestBatchScope,
_scenario: str = scenario,
_new_site_id: int = new_site_id,
**kwargs: Any,
@@ -1179,13 +1225,13 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_shipment_items', new=replace_items),
self.assertRaises(exception_type) as raised,
):
await ShipmentService.edit_shipment_services(db, page, self.user)
await ShipmentService.edit_shipment_services(db, page, self.user, TestBatchScope.formal())
self.assertEqual(expected_message, raised.exception.message)
if scenario == 'current-warehouse-revoked':
warehouse_mapping.assert_any_await(db, self.user, 7, for_update=True)
else:
site_read.assert_any_await(db, 10, for_update=True)
site_read.assert_any_await(db, 10, TestBatchScope.formal(), for_update=True)
if scenario == 'new-site-revoked':
site_mapping.assert_any_await(db, self.user, 10, for_update=True)
update.assert_not_awaited()
@@ -1263,6 +1309,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_warehouse(
_db: AsyncSession,
warehouse_id: int,
_scope: TestBatchScope,
_disabled_ids: set[int] = disabled_warehouse_ids,
_locks: list[str] = locks,
**kwargs: Any,
@@ -1279,6 +1326,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_site(
_db: AsyncSession,
site_id: int,
_scope: TestBatchScope,
_disabled_ids: set[int] = disabled_site_ids,
_locks: list[str] = locks,
**kwargs: Any,
@@ -1316,7 +1364,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'replace_shipment_items', new=replace_items),
self.assertRaises(PermissionException) as raised,
):
await ShipmentService.edit_shipment_services(db, page, self.user)
await ShipmentService.edit_shipment_services(db, page, self.user, TestBatchScope.formal())
self.assertEqual('无权访问该发货单', raised.exception.message)
self.assertEqual(expected_locks, locks)
@@ -1361,6 +1409,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_warehouse(
_db: AsyncSession,
warehouse_id: int,
_scope: TestBatchScope,
**kwargs: Any,
) -> BizWarehouse:
if kwargs.get('for_update'):
@@ -1375,6 +1424,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
async def get_site(
_db: AsyncSession,
site_id: int,
_scope: TestBatchScope,
**kwargs: Any,
) -> BizCustomerSite:
if kwargs.get('for_update'):
@@ -1415,7 +1465,7 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'update_shipment', new=AsyncMock(side_effect=update)),
patch.object(OperationDao, 'replace_shipment_items', new=AsyncMock(side_effect=replace)),
):
result = await ShipmentService.edit_shipment_services(db, page, self.user)
result = await ShipmentService.edit_shipment_services(db, page, self.user, TestBatchScope.formal())
self.assertTrue(result.is_success)
self.assertEqual(
@@ -1437,222 +1487,55 @@ class ShipmentDraftServiceTest(unittest.IsolatedAsyncioTestCase):
)
async def test_batch_delete_preflights_all_drafts_before_any_delete(self) -> None:
documents = {
11: make_shipment(11, status='DRAFT'),
12: make_shipment(12, status='CONFIRMED'),
}
async def lock_order(
_db: AsyncSession,
shipment_id: int,
**_kwargs: Any,
) -> BizShipmentOrder:
return documents[shipment_id]
lock = AsyncMock()
delete = AsyncMock()
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7])),
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[9])),
patch.object(OperationDao, 'get_shipment_for_update', new=AsyncMock(side_effect=lock_order)) as lock,
patch.object(OperationDao, 'get_shipment_for_update', new=lock),
patch.object(OperationDao, 'delete_shipment', new=delete),
self.assertRaises(ServiceWarning) as raised,
):
try:
with self.assertRaises(ServiceException):
await ShipmentService.delete_shipment_services(self.db, [12, 11], self.user)
except NotImplementedError:
self.fail('发货草稿原子批量删除尚未实现')
await ShipmentService.delete_shipment_services(self.db, [12, 11], self.user, TestBatchScope.formal())
self.assertEqual([11, 12], [call.args[1] for call in lock.await_args_list])
self.assertEqual('不支持单条物理删除,请通过测试数据批次清理', raised.exception.message)
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_rechecks_all_current_routes_before_first_delete(self) -> None:
documents = {
11: make_shipment(11, source_warehouse_id=8, target_site_id=10, status='DRAFT'),
12: make_shipment(12, source_warehouse_id=7, target_site_id=9, status='DRAFT'),
}
revoked_warehouse_id = documents[11].source_warehouse_id
parent_lock_order: list[int] = []
async def lock_order(
_db: AsyncSession,
shipment_id: int,
**_kwargs: Any,
) -> BizShipmentOrder:
parent_lock_order.append(shipment_id)
return documents[shipment_id]
async def get_warehouse(
_db: AsyncSession,
warehouse_id: int,
**_kwargs: Any,
) -> BizWarehouse:
return BizWarehouse(
warehouse_id=warehouse_id,
warehouse_code=f'W-{warehouse_id}',
warehouse_name=f'{warehouse_id}号仓',
status='0',
)
async def get_site(
_db: AsyncSession,
site_id: int,
**_kwargs: Any,
) -> BizCustomerSite:
return BizCustomerSite(
site_id=site_id,
customer_id=1,
site_code=f'S-{site_id}',
site_name=f'{site_id}号现场',
status='0',
)
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)
site_lock = AsyncMock(side_effect=get_site)
site_mapping = AsyncMock()
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7, 8])),
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[9, 10])),
patch.object(OperationDao, 'get_shipment_for_update', new=AsyncMock(side_effect=lock_order)),
patch.object(MasterDao, 'get_warehouse', new=AsyncMock(side_effect=get_warehouse)),
patch.object(AccessService, 'assert_warehouse_access', new=warehouse_mapping),
patch.object(MasterDao, 'get_site', new=site_lock),
patch.object(AccessService, 'assert_site_access', new=site_mapping),
patch.object(OperationDao, 'get_shipment_for_update', new=lock),
patch.object(OperationDao, 'delete_shipment', new=delete),
self.assertRaises(PermissionException) as raised,
self.assertRaises(ServiceWarning) as raised,
):
await ShipmentService.delete_shipment_services(self.db, [12, 11], self.user)
await ShipmentService.delete_shipment_services(self.db, [12, 11], self.user, TestBatchScope.formal())
self.assertEqual('无权访问该发货单', raised.exception.message)
self.assertEqual([11, 12], parent_lock_order)
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))
self.assertEqual([9, 10], [call.args[1] for call in site_lock.await_args_list])
self.assertEqual([9, 10], [call.args[2] for call in site_mapping.await_args_list])
self.assertTrue(all(call.kwargs.get('for_update') for call in site_mapping.await_args_list))
self.assertEqual('不支持单条物理删除,请通过测试数据批次清理', raised.exception.message)
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_locks_deduplicated_routes_stably_without_requiring_active(self) -> None:
db = MagicMock(spec=AsyncSession)
documents = {
11: make_shipment(11, source_warehouse_id=8, target_site_id=10, status='DRAFT'),
12: make_shipment(12, source_warehouse_id=7, target_site_id=10, status='DRAFT'),
13: make_shipment(13, source_warehouse_id=8, target_site_id=9, status='DRAFT'),
}
actions: list[str] = []
async def lock_order(
_db: AsyncSession,
shipment_id: int,
**_kwargs: Any,
) -> BizShipmentOrder:
actions.append(f'parent-{shipment_id}')
return documents[shipment_id]
async def get_warehouse(
_db: AsyncSession,
warehouse_id: int,
**kwargs: Any,
) -> BizWarehouse:
self.assertTrue(kwargs.get('for_update'))
actions.append(f'warehouse-master-{warehouse_id}')
return BizWarehouse(
warehouse_id=warehouse_id,
warehouse_code=f'W-{warehouse_id}',
warehouse_name=f'{warehouse_id}号仓',
status='1',
)
async def warehouse_access(
_db: AsyncSession,
_user: CurrentUserModel,
warehouse_id: int,
**kwargs: Any,
) -> None:
self.assertTrue(kwargs.get('for_update'))
actions.append(f'warehouse-mapping-{warehouse_id}')
async def get_site(
_db: AsyncSession,
site_id: int,
**kwargs: Any,
) -> BizCustomerSite:
self.assertTrue(kwargs.get('for_update'))
actions.append(f'site-master-{site_id}')
return BizCustomerSite(
site_id=site_id,
customer_id=1,
site_code=f'S-{site_id}',
site_name=f'{site_id}号现场',
status='1',
)
async def site_access(
_db: AsyncSession,
_user: CurrentUserModel,
site_id: int,
**kwargs: Any,
) -> None:
self.assertTrue(kwargs.get('for_update'))
actions.append(f'site-mapping-{site_id}')
async def delete(_db: AsyncSession, shipment_id: int) -> None:
actions.append(f'delete-{shipment_id}')
async def commit() -> None:
actions.append('commit')
db.commit.side_effect = commit
lock = AsyncMock()
delete = AsyncMock()
with (
patch.object(AccessDao, 'list_user_warehouse_ids', new=AsyncMock(return_value=[7, 8])),
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[9, 10])),
patch.object(OperationDao, 'get_shipment_for_update', new=AsyncMock(side_effect=lock_order)),
patch.object(MasterDao, 'get_warehouse', new=AsyncMock(side_effect=get_warehouse)),
patch.object(
AccessService,
'assert_warehouse_access',
new=AsyncMock(side_effect=warehouse_access),
),
patch.object(MasterDao, 'get_site', new=AsyncMock(side_effect=get_site)),
patch.object(AccessService, 'assert_site_access', new=AsyncMock(side_effect=site_access)),
patch.object(OperationDao, 'delete_shipment', new=AsyncMock(side_effect=delete)),
patch.object(OperationDao, 'get_shipment_for_update', new=lock),
patch.object(OperationDao, 'delete_shipment', new=delete),
self.assertRaises(ServiceWarning) as raised,
):
result = await ShipmentService.delete_shipment_services(db, [13, 11, 12], self.user)
await ShipmentService.delete_shipment_services(
db,
[13, 11, 12],
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
self.assertEqual(
[
'parent-11',
'parent-12',
'parent-13',
'warehouse-master-7',
'warehouse-mapping-7',
'warehouse-master-8',
'warehouse-mapping-8',
'site-master-9',
'site-mapping-9',
'site-master-10',
'site-mapping-10',
'delete-11',
'delete-12',
'delete-13',
'commit',
],
actions,
)
self.assertEqual('不支持单条物理删除,请通过测试数据批次清理', raised.exception.message)
lock.assert_not_awaited()
delete.assert_not_awaited()
db.commit.assert_not_awaited()
class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
@@ -1695,7 +1578,11 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
actions.append('pallet-lock')
return pallets
async def filling_lock(_db: AsyncSession, filling_id: int) -> BizFillingRecord:
async def filling_lock(
_db: AsyncSession,
filling_id: int,
_scope: TestBatchScope,
) -> BizFillingRecord:
actions.append(f'filling-lock-{filling_id}')
return fillings[filling_id]
@@ -1791,6 +1678,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
except NotImplementedError:
self.fail('多托盘整托确认尚未实现')
@@ -1821,6 +1709,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
detail_read.assert_awaited_once_with(
self.db,
12,
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
)
@@ -1923,12 +1812,13 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
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_access.assert_awaited_once_with(self.db, self.user, 7, for_update=True)
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)
site_access.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
inventory_lock.assert_not_awaited()
update.assert_not_awaited()
@@ -2009,12 +1899,13 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
warehouse_lock.assert_awaited_once_with(db, 7, for_update=True)
warehouse_lock.assert_awaited_once_with(db, 7, TestBatchScope.formal(), for_update=True)
warehouse_access.assert_awaited_once_with(db, self.user, 7, for_update=True)
site_lock.assert_awaited_once_with(db, 9, for_update=True)
site_lock.assert_awaited_once_with(db, 9, TestBatchScope.formal(), for_update=True)
site_access.assert_awaited_once_with(db, self.user, 9, for_update=True)
self.assertEqual(
original_snapshots,
@@ -2081,12 +1972,13 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
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)
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)
site_mapping.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
inventory_lock.assert_not_awaited()
update.assert_not_awaited()
@@ -2146,10 +2038,11 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
lock_validate.assert_awaited_once_with(self.db, [2], 7)
lock_validate.assert_awaited_once_with(self.db, [2], 7, TestBatchScope.formal())
self.assertEqual((102, 6), (locked.items[0].active_filling_id, locked.items[0].package_qty))
self.assertIsNone(initial.items[0].active_filling_id)
self.assertIsNone(initial.items[0].package_qty)
@@ -2175,7 +2068,9 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(OperationDao, 'get_shipment_for_update', new=parent_lock),
self.assertRaises(ServiceException) as raised,
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual('确认发货失败,幂等键已被其他请求使用', raised.exception.message)
parent_lock.assert_not_awaited()
@@ -2199,6 +2094,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
async def read_key(
_db: AsyncSession,
_key: str,
_scope: TestBatchScope,
*,
warehouse_ids: list[int] | None = None,
site_ids: list[int] | None = None,
@@ -2254,18 +2150,21 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
get_detail.assert_awaited_once_with(
self.db,
12,
TestBatchScope.formal(),
warehouse_ids=[7, 8],
site_ids=[9, 10],
)
key_read.assert_awaited_once_with(
self.db,
'confirm-key',
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
)
@@ -2295,7 +2194,9 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
),
self.assertRaises(ServiceException) as raised,
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual('确认发货失败,幂等键已被其他请求使用', raised.exception.message)
self.assertEqual(2, self.db.rollback.await_count)
@@ -2359,19 +2260,22 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(AccessService, 'assert_site_access', new=AsyncMock()),
self.assertRaises(PermissionException) as raised,
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, 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,
12,
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
)
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)
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)
self.db.commit.assert_not_awaited()
self.assertEqual(2, self.db.rollback.await_count)
@@ -2425,14 +2329,16 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaises(ServiceException) as raised,
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual('来源仓库已停用', raised.exception.message)
self.assertEqual('DRAFT', shipment.status)
self.assertEqual('FILLED_IN_WAREHOUSE', inventory.flow_state)
self.assertEqual(4, inventory.version)
mapping.assert_awaited_once_with(self.db, self.user, 7, for_update=True)
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)
site_mapping.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
update.assert_not_awaited()
add_event.assert_not_awaited()
@@ -2491,6 +2397,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
@@ -2523,6 +2430,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
async def read_key(
_db: AsyncSession,
_key: str,
_scope: TestBatchScope,
*,
warehouse_ids: list[int] | None = None,
site_ids: list[int] | None = None,
@@ -2584,6 +2492,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
12,
self.command,
self.user,
TestBatchScope.formal(),
)
self.assertTrue(result.is_success)
@@ -2595,12 +2504,14 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
unittest.mock.call(
self.db,
'confirm-key',
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
),
unittest.mock.call(
self.db,
'confirm-key',
TestBatchScope.formal(),
warehouse_ids=[7],
site_ids=[9],
),
@@ -2667,7 +2578,9 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaises(PermissionException) as raised,
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual('无权访问该发货单', raised.exception.message)
self.assertEqual('DRAFT', shipment.status)
@@ -2675,7 +2588,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(7, inventory.warehouse_id)
self.assertIsNone(inventory.site_id)
self.assertEqual(4, inventory.version)
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)
site_mapping.assert_awaited_once_with(self.db, self.user, 9, for_update=True)
transition.assert_not_called()
update.assert_not_awaited()
@@ -2690,7 +2603,11 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
update = AsyncMock()
add_event = AsyncMock()
async def get_filling(_db: AsyncSession, filling_id: int) -> BizFillingRecord:
async def get_filling(
_db: AsyncSession,
filling_id: int,
_scope: TestBatchScope,
) -> BizFillingRecord:
return fillings[filling_id]
transition = MagicMock(side_effect=[None, ValueError('second pallet invalid')])
@@ -2747,7 +2664,9 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
patch.object(InventoryDao, 'add_event', new=add_event),
self.assertRaisesRegex(ValueError, 'second pallet invalid'),
):
await ShipmentService.confirm_shipment_services(self.db, 12, self.command, self.user)
await ShipmentService.confirm_shipment_services(
self.db, 12, self.command, self.user, TestBatchScope.formal()
)
self.assertEqual('DRAFT', shipment.status)
self.assertTrue(all(item.active_filling_id is None and item.package_qty is None for item in shipment.items))