功能:隔离五类测试业务链路
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user