修复:补齐测试批次恢复锁定

This commit is contained in:
焦龙言
2026-09-23 09:01:50 +08:00
parent f5df2150d8
commit 97b4dc8508
15 changed files with 212 additions and 16 deletions
@@ -446,6 +446,7 @@ class TestFillPalletService(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
actions.append('event-flush')
event.event_id = 91
@@ -1092,6 +1092,7 @@ class TestInboundConfirmService(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
actions.append(f'event-{inventory.pallet_id}')
events.append(event)
@@ -72,7 +72,7 @@ class TestDaoPublicContract(unittest.TestCase):
'get_pallet_by_qr_token': ('db', 'qr_token', 'scope'),
'get_pallets_by_ids': ('db', 'pallet_ids', 'scope'),
'get_event_by_idempotency_key': ('db', 'pallet_id', 'idempotency_key', 'scope'),
'add_event': ('db', 'inventory', 'event'),
'add_event': ('db', 'inventory', 'event', 'scope'),
}
for method_name, parameter_names in expected.items():
with self.subTest(method=method_name):
@@ -401,7 +401,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
self.db.flush.side_effect = assign_event_id
actual = await InventoryDao.add_event(self.db, inventory, event)
actual = await InventoryDao.add_event(self.db, inventory, event, TestBatchScope.formal())
self.assertIs(event, actual)
self.assertEqual(7, event.pallet_id)
@@ -417,6 +417,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
self.assertNotIn('max(', sql.lower())
self.assertIn('biz_pallet_event.event_id = 40', sql)
self.assertIn('biz_pallet_event.pallet_id = 7', sql)
self.assertIn('biz_pallet_event.test_batch_id IS NULL', sql)
self.assertIn('FOR UPDATE', sql)
self.assertNotIn('ORDER BY', sql)
self.assertNotIn('LIMIT', sql)
@@ -429,7 +430,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
inventory = BizPalletInventory(pallet_id=8, last_event_id=None)
event = BizPalletEvent(pallet_id=8, idempotency_key='first-key', event_id=51)
await InventoryDao.add_event(self.db, inventory, event)
await InventoryDao.add_event(self.db, inventory, event, TestBatchScope.formal())
self.assertEqual(1, event.event_seq)
self.assertEqual(51, inventory.last_event_id)
@@ -445,7 +446,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
event = BizPalletEvent(pallet_id=8, idempotency_key='broken-pointer')
with self.assertRaisesRegex(RuntimeError, '托盘库存事件指针不一致'):
await InventoryDao.add_event(self.db, inventory, event)
await InventoryDao.add_event(self.db, inventory, event, TestBatchScope.formal())
self.db.execute.assert_awaited_once()
self.db.add.assert_not_called()
@@ -24,7 +24,7 @@ from module_business.dao.access_dao import AccessDao
from module_business.dao.inventory_dao import InventoryDao
from module_business.dao.master_dao import MasterDao
from module_business.dao.operation_dao import OperationDao
from module_business.domain.enums import PalletEventType, PalletFlowState, ReturnStatus
from module_business.domain.enums import PalletEventType, PalletFlowState, ReturnStatus, TestBatchStatus
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 (
@@ -44,6 +44,7 @@ from module_business.entity.vo.operation_vo import (
ReturnScanQueryModel,
)
from module_business.service.access_service import AccessService
from module_business.service.test_batch_access_service import TestBatchAccessService
RETURN_SERVICE_SPEC = importlib.util.find_spec('module_business.service.return_service')
RETURN_CONTROLLER_SPEC = importlib.util.find_spec('module_business.controller.return_controller')
@@ -741,6 +742,7 @@ class ReturnRequestTransactionRedTest(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
_inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
captured_events.append(event)
return event
@@ -790,6 +792,48 @@ class ReturnRequestTransactionRedTest(unittest.IsolatedAsyncioTestCase):
warehouse_access.assert_not_awaited()
self.db.commit.assert_awaited_once()
async def test_request_integrity_error_recovers_same_test_batch_idempotently(self) -> None:
scope = TestBatchScope(batch_id=4, mode='TEST', status=TestBatchStatus.ACTIVE, writable=True)
winner = make_return_order(request_hash=ReturnService._build_request_hash(self.page))
winner.test_batch_id = 4
for item in winner.items:
item.test_batch_id = 4
inventories = [
make_inventory(2),
make_inventory(5, remaining=0, state=PalletFlowState.AT_SITE_EMPTY.value),
]
pallets = [make_pallet(2), make_pallet(5)]
for inventory in inventories:
inventory.test_batch_id = 4
for pallet in pallets:
pallet.test_batch_id = 4
filling = make_filling(2)
filling.test_batch_id = 4
key_read = AsyncMock(side_effect=[None, None, winner])
order_lock = AsyncMock(return_value=winner)
batch_lock = AsyncMock()
with (
patch.object(TestBatchAccessService, 'lock_active_batch', new=batch_lock),
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[9])),
patch.object(OperationDao, 'get_return_by_request_idempotency_key', new=key_read),
patch.object(InventoryDao, 'get_inventories_for_update', new=AsyncMock(return_value=inventories)),
patch.object(InventoryDao, 'get_pallets_by_ids', new=AsyncMock(return_value=pallets)),
patch.object(OperationDao, 'get_active_filling_for_update', new=AsyncMock(return_value=filling)),
patch.object(OperationDao, 'get_return_for_update', new=order_lock),
patch.object(AccessService, 'assert_site_access', new=AsyncMock()),
patch.object(MasterDao, 'get_site', new=AsyncMock(return_value=SimpleNamespace(status='0'))),
patch.object(MasterDao, 'get_warehouse', new=AsyncMock(return_value=SimpleNamespace(status='0'))),
patch.object(OperationDao, 'add_return_order', new=AsyncMock(side_effect=integrity_error())),
):
result = await ReturnService.request_return_services(self.db, self.page, self.user, scope)
self.assertTrue(result.is_success)
self.assertEqual(13, result.result)
self.assertEqual(3, batch_lock.await_count)
order_lock.assert_awaited_once_with(self.db, 13, scope, site_ids=[9], warehouse_ids=[])
self.assertEqual(3, self.db.rollback.await_count)
async def test_concurrent_same_request_returns_winner_after_inventory_wait(self) -> None: # noqa: PLR0915
request_hash = ReturnService._build_request_hash(self.page)
winner = make_return_order(request_hash=request_hash)
@@ -1265,6 +1309,7 @@ class ReturnRequestTransactionRedTest(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
_inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
events.append(event)
return event
@@ -1595,6 +1640,7 @@ class ReturnConfirmTransactionRedTest(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
_inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
events.append(event)
return event
@@ -1626,6 +1626,7 @@ class ShipmentConfirmServiceTest(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
actions.append(f'event-{inventory.pallet_id}')
event.event_id = 200 + inventory.pallet_id
@@ -884,6 +884,7 @@ class TestRecordUsageCore(unittest.IsolatedAsyncioTestCase):
_db: AsyncSession,
inventory: BizPalletInventory,
event: BizPalletEvent,
_scope: TestBatchScope,
) -> BizPalletEvent:
actions.append('event-flush')
event.event_id = 91