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

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
@@ -13,6 +13,7 @@ from module_business.dao import inventory_dao as inventory_dao_module
from module_business.dao import operation_dao as operation_dao_module
from module_business.dao.inventory_dao import InventoryDao
from module_business.dao.operation_dao import OperationDao
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 (
BizFillingRecord,
@@ -66,11 +67,11 @@ class TestDaoPublicContract(unittest.TestCase):
def test_inventory_dao_critical_parameter_names_are_stable(self) -> None:
expected = {
'get_inventory_for_update': ('db', 'pallet_id'),
'get_inventories_for_update': ('db', 'pallet_ids'),
'get_pallet_by_qr_token': ('db', 'qr_token'),
'get_pallets_by_ids': ('db', 'pallet_ids'),
'get_event_by_idempotency_key': ('db', 'pallet_id', 'idempotency_key'),
'get_inventory_for_update': ('db', 'pallet_id', 'scope'),
'get_inventories_for_update': ('db', 'pallet_ids', 'scope'),
'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'),
}
for method_name, parameter_names in expected.items():
@@ -121,51 +122,55 @@ class TestDaoPublicContract(unittest.TestCase):
def test_operation_dao_critical_parameter_names_and_defaults_are_stable(self) -> None:
expected = {
'get_inbound_for_update': ('db', 'inbound_id', 'warehouse_ids'),
'get_shipment_for_update': ('db', 'shipment_id', 'warehouse_ids', 'site_ids'),
'get_return_for_update': ('db', 'return_id', 'site_ids', 'warehouse_ids'),
'get_active_filling_for_update': ('db', 'filling_id'),
'get_inbound_for_update': ('db', 'inbound_id', 'scope', 'warehouse_ids'),
'get_shipment_for_update': ('db', 'shipment_id', 'scope', 'warehouse_ids', 'site_ids'),
'get_return_for_update': ('db', 'return_id', 'scope', 'site_ids', 'warehouse_ids'),
'get_active_filling_for_update': ('db', 'filling_id', 'scope'),
'get_inbound_by_confirm_idempotency_key': (
'db',
'idempotency_key',
'scope',
'warehouse_ids',
),
'get_shipment_by_confirm_idempotency_key': (
'db',
'idempotency_key',
'scope',
'warehouse_ids',
'site_ids',
),
'get_filling_by_idempotency_key': ('db', 'idempotency_key', 'warehouse_ids'),
'get_usage_by_idempotency_key': ('db', 'idempotency_key', 'site_ids'),
'get_filling_by_idempotency_key': ('db', 'idempotency_key', 'scope', 'warehouse_ids'),
'get_usage_by_idempotency_key': ('db', 'idempotency_key', 'scope', 'site_ids'),
'get_return_by_request_idempotency_key': (
'db',
'idempotency_key',
'source_site_id',
'target_warehouse_id',
'scope',
),
'get_return_by_confirm_idempotency_key': (
'db',
'idempotency_key',
'source_site_id',
'target_warehouse_id',
'scope',
),
'get_inbound_by_id': ('db', 'inbound_id', 'warehouse_ids'),
'get_inbound_by_id': ('db', 'inbound_id', 'scope', 'warehouse_ids'),
'add_inbound': ('db', 'inbound'),
'update_inbound': ('db', 'inbound'),
'delete_inbound': ('db', 'inbound_id'),
'replace_inbound_items': ('db', 'inbound_id', 'items'),
'get_filling_by_id': ('db', 'filling_id', 'warehouse_ids'),
'get_filling_by_id': ('db', 'filling_id', 'scope', 'warehouse_ids'),
'add_filling_record': ('db', 'filling'),
'update_filling_record': ('db', 'filling'),
'get_shipment_by_id': ('db', 'shipment_id', 'warehouse_ids', 'site_ids'),
'get_shipment_by_id': ('db', 'shipment_id', 'scope', 'warehouse_ids', 'site_ids'),
'add_shipment': ('db', 'shipment'),
'update_shipment': ('db', 'shipment'),
'delete_shipment': ('db', 'shipment_id'),
'replace_shipment_items': ('db', 'shipment_id', 'items'),
'get_usage_by_id': ('db', 'usage_id', 'site_ids'),
'get_usage_by_id': ('db', 'usage_id', 'scope', 'site_ids'),
'add_usage_record': ('db', 'usage'),
'get_return_by_id': ('db', 'return_id', 'site_ids', 'warehouse_ids'),
'get_return_by_id': ('db', 'return_id', 'scope', 'site_ids', 'warehouse_ids'),
'add_return_order': ('db', 'return_order'),
'update_return_order': ('db', 'return_order'),
'add_return_items': ('db', 'items'),
@@ -177,11 +182,10 @@ class TestDaoPublicContract(unittest.TestCase):
inbound_list_signature = inspect.signature(OperationDao.get_inbound_list)
self.assertEqual(
('db', 'query_object', 'is_page', 'warehouse_ids'),
('db', 'query_object', 'warehouse_ids', 'scope', 'is_page'),
tuple(inbound_list_signature.parameters),
)
self.assertIs(True, inbound_list_signature.parameters['is_page'].default)
self.assertIsNone(inbound_list_signature.parameters['warehouse_ids'].default)
for method_name in (
'get_inbound_for_update',
@@ -208,28 +212,24 @@ class TestDaoPublicContract(unittest.TestCase):
filling_list_signature = inspect.signature(OperationDao.get_filling_list)
self.assertEqual(
('db', 'query_object', 'is_page', 'warehouse_ids'),
('db', 'query_object', 'warehouse_ids', 'scope', 'is_page'),
tuple(filling_list_signature.parameters),
)
self.assertIs(True, filling_list_signature.parameters['is_page'].default)
self.assertIsNone(filling_list_signature.parameters['warehouse_ids'].default)
shipment_list_signature = inspect.signature(OperationDao.get_shipment_list)
self.assertEqual(
('db', 'query_object', 'is_page', 'warehouse_ids', 'site_ids'),
('db', 'query_object', 'warehouse_ids', 'site_ids', 'scope', 'is_page'),
tuple(shipment_list_signature.parameters),
)
self.assertIs(True, shipment_list_signature.parameters['is_page'].default)
self.assertIsNone(shipment_list_signature.parameters['warehouse_ids'].default)
self.assertIsNone(shipment_list_signature.parameters['site_ids'].default)
usage_list_signature = inspect.signature(OperationDao.get_usage_list)
self.assertEqual(
('db', 'query_object', 'is_page', 'site_ids'),
('db', 'query_object', 'site_ids', 'scope', 'is_page'),
tuple(usage_list_signature.parameters),
)
self.assertIs(True, usage_list_signature.parameters['is_page'].default)
self.assertIsNone(usage_list_signature.parameters['site_ids'].default)
for method_name in (
'get_usage_by_idempotency_key',
@@ -243,14 +243,13 @@ class TestDaoPublicContract(unittest.TestCase):
return_list_signature = inspect.signature(OperationDao.get_return_list)
self.assertEqual(
('db', 'query_object', 'is_page', 'site_ids', 'warehouse_ids'),
('db', 'query_object', 'site_ids', 'warehouse_ids', 'scope', 'is_page'),
tuple(return_list_signature.parameters),
)
self.assertIs(True, return_list_signature.parameters['is_page'].default)
for method_name in (
'get_return_for_update',
'get_return_by_id',
'get_return_list',
):
with self.subTest(method=method_name):
signature = inspect.signature(getattr(OperationDao, method_name))
@@ -284,7 +283,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = inventory
self.db.execute.return_value = result
actual = await InventoryDao.get_inventory_for_update(self.db, pallet_id=7)
actual = await InventoryDao.get_inventory_for_update(self.db, pallet_id=7, scope=TestBatchScope.formal())
self.assertIs(inventory, actual)
statement = self.db.execute.await_args.args[0]
@@ -305,7 +304,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
result.scalars.return_value.all.return_value = inventories
self.db.execute.return_value = result
actual = await InventoryDao.get_inventories_for_update(self.db, [9, 2, 5, 2])
actual = await InventoryDao.get_inventories_for_update(self.db, [9, 2, 5, 2], scope=TestBatchScope.formal())
self.assertEqual([2, 5, 9], [inventory.pallet_id for inventory in actual])
statement = self.db.execute.await_args.args[0]
@@ -318,7 +317,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
self.assertIn('FOR UPDATE', sql)
async def test_get_inventories_for_update_empty_input_skips_sql(self) -> None:
self.assertEqual([], await InventoryDao.get_inventories_for_update(self.db, []))
self.assertEqual([], await InventoryDao.get_inventories_for_update(self.db, [], scope=TestBatchScope.formal()))
self.db.execute.assert_not_awaited()
async def test_get_pallet_by_qr_token_uses_exact_token_without_lock(self) -> None:
@@ -327,7 +326,9 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = pallet
self.db.execute.return_value = result
actual = await InventoryDao.get_pallet_by_qr_token(self.db, qr_token='fixed-token')
actual = await InventoryDao.get_pallet_by_qr_token(
self.db, qr_token='fixed-token', scope=TestBatchScope.formal()
)
self.assertIs(pallet, actual)
statement = self.db.execute.await_args.args[0]
@@ -346,7 +347,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
result.scalars.return_value.all.return_value = pallets
self.db.execute.return_value = result
actual = await InventoryDao.get_pallets_by_ids(self.db, [9, 2, 5, 2])
actual = await InventoryDao.get_pallets_by_ids(self.db, [9, 2, 5, 2], scope=TestBatchScope.formal())
self.assertEqual([2, 5, 9], [pallet.pallet_id for pallet in actual])
statement = self.db.execute.await_args.args[0]
@@ -359,7 +360,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
self.assertIn('FOR UPDATE', sql)
async def test_get_pallets_by_ids_empty_input_skips_sql(self) -> None:
self.assertEqual([], await InventoryDao.get_pallets_by_ids(self.db, []))
self.assertEqual([], await InventoryDao.get_pallets_by_ids(self.db, [], scope=TestBatchScope.formal()))
self.db.execute.assert_not_awaited()
async def test_get_event_by_idempotency_key_uses_pallet_scoped_key(self) -> None:
@@ -372,6 +373,7 @@ class TestInventoryDao(unittest.IsolatedAsyncioTestCase):
self.db,
pallet_id=7,
idempotency_key='event-key',
scope=TestBatchScope.formal(),
)
self.assertIs(event, actual)
@@ -474,7 +476,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = record
self.db.execute.return_value = result
actual = await method(self.db, record_id)
actual = await method(self.db, record_id, scope=TestBatchScope.formal())
self.assertIs(record, actual)
statement = self.db.execute.await_args.args[0]
@@ -504,6 +506,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
12,
warehouse_ids=[7, 3, 7],
site_ids=[11, 9, 11],
scope=TestBatchScope.formal(),
)
self.assertIs(shipment, actual)
@@ -540,6 +543,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
12,
warehouse_ids=[],
site_ids=[9],
scope=TestBatchScope.formal(),
)
self.assertIsNone(actual)
@@ -562,7 +566,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
item_result.scalars.return_value.all.return_value = items
self.db.execute.side_effect = [inbound_result, item_result]
actual = await OperationDao.get_inbound_for_update(self.db, inbound_id=11)
actual = await OperationDao.get_inbound_for_update(self.db, inbound_id=11, scope=TestBatchScope.formal())
self.assertIs(inbound, actual)
self.assertEqual(items, actual.items)
@@ -599,6 +603,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.db,
inbound_id=11,
warehouse_ids=[7, 3, 7],
scope=TestBatchScope.formal(),
)
self.assertIsNone(actual)
@@ -647,7 +652,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = record
self.db.execute.return_value = result
actual = await method(self.db, 'same-key', **kwargs)
actual = await method(self.db, 'same-key', scope=TestBatchScope.formal(), **kwargs)
self.assertIs(record, actual)
statement = self.db.execute.await_args.args[0]
@@ -673,6 +678,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.db,
'confirm-key',
warehouse_ids=[7, 3, 7],
scope=TestBatchScope.formal(),
)
self.assertIsNone(actual)
@@ -807,7 +813,16 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
marker = object()
is_page = index % 2 == 0
with patch.object(PageUtil, 'paginate', new=AsyncMock(return_value=marker)) as paginate:
actual = await method(self.db, query, is_page=is_page)
kwargs = {
'scope': TestBatchScope.formal(),
'is_page': is_page,
}
parameters = inspect.signature(method).parameters
if 'warehouse_ids' in parameters:
kwargs['warehouse_ids'] = None
if 'site_ids' in parameters:
kwargs['site_ids'] = None
actual = await method(self.db, query, **kwargs)
self.assertIs(marker, actual)
args = paginate.await_args.args
@@ -829,6 +844,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query,
is_page=True,
warehouse_ids=[5, 2, 5],
scope=TestBatchScope.formal(),
)
self.assertIs(marker, actual)
@@ -848,6 +864,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query,
is_page=True,
warehouse_ids=[],
scope=TestBatchScope.formal(),
)
self.assertIs(marker, actual)
@@ -866,6 +883,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query,
is_page=True,
warehouse_ids=None,
scope=TestBatchScope.formal(),
)
sql = compile_sql(paginate.await_args.args[1])
@@ -882,6 +900,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
is_page=True,
warehouse_ids=[7, 3, 7],
site_ids=[11, 9, 11],
scope=TestBatchScope.formal(),
)
self.assertIs(marker, actual)
@@ -898,6 +917,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query,
warehouse_ids=[7],
site_ids=[],
scope=TestBatchScope.formal(),
)
empty_statement = empty_paginate.await_args.args[1]
self.assertIn('false = 1', compile_sql(empty_statement, mysql.dialect()))
@@ -914,6 +934,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
12,
warehouse_ids=[7, 3],
site_ids=[11, 9],
scope=TestBatchScope.formal(),
)
)
self.db.execute.assert_awaited_once()
@@ -927,6 +948,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
'confirm-key',
warehouse_ids=[7, 3],
site_ids=[11, 9],
scope=TestBatchScope.formal(),
)
)
key_statement = self.db.execute.await_args.args[0]
@@ -949,6 +971,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query,
is_page=True,
site_ids=[11, 9, 11],
scope=TestBatchScope.formal(),
)
self.assertIs(marker, actual)
@@ -957,7 +980,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
scalar_result = MagicMock()
scalar_result.scalar_one_or_none.return_value = None
self.db.execute.return_value = scalar_result
await OperationDao.get_usage_by_id(self.db, 15, site_ids=[11, 9, 11])
await OperationDao.get_usage_by_id(self.db, 15, site_ids=[11, 9, 11], scope=TestBatchScope.formal())
detail_statement = self.db.execute.await_args.args[0]
self.db.reset_mock()
@@ -966,6 +989,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.db,
'usage-key',
site_ids=[11, 9, 11],
scope=TestBatchScope.formal(),
)
key_statement = self.db.execute.await_args.args[0]
@@ -976,17 +1000,19 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.assertIn('biz_usage_record.site_id IN (9, 11)', sql)
with patch.object(PageUtil, 'paginate', new=AsyncMock(return_value=marker)) as empty_paginate:
await OperationDao.get_usage_list(self.db, query, site_ids=[])
await OperationDao.get_usage_list(self.db, query, site_ids=[], scope=TestBatchScope.formal())
empty_list = empty_paginate.await_args.args[1]
self.db.reset_mock()
self.db.execute.return_value = scalar_result
await OperationDao.get_usage_by_id(self.db, 15, site_ids=[])
await OperationDao.get_usage_by_id(self.db, 15, site_ids=[], scope=TestBatchScope.formal())
empty_detail = self.db.execute.await_args.args[0]
self.db.reset_mock()
self.db.execute.return_value = scalar_result
await OperationDao.get_usage_by_idempotency_key(self.db, 'usage-key', site_ids=[])
await OperationDao.get_usage_by_idempotency_key(
self.db, 'usage-key', site_ids=[], scope=TestBatchScope.formal()
)
empty_key = self.db.execute.await_args.args[0]
for dialect in (mysql.dialect(), postgresql.dialect()):
@@ -999,7 +1025,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
query = UsagePageQueryModel()
marker = object()
with patch.object(PageUtil, 'paginate', new=AsyncMock(return_value=marker)) as paginate:
await OperationDao.get_usage_list(self.db, query, site_ids=None)
await OperationDao.get_usage_list(self.db, query, site_ids=None, scope=TestBatchScope.formal())
sql = compile_sql(paginate.await_args.args[1])
self.assertNotIn('site_id IN', sql)
@@ -1023,6 +1049,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.db,
'usage-key',
site_ids=site_ids,
scope=TestBatchScope.formal(),
)
statement = self.db.execute.await_args.args[0]
@@ -1081,7 +1108,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
item_result.scalars.return_value.all.return_value = items
self.db.execute.side_effect = [order_result, item_result]
actual = await method(self.db, order_id)
actual = await method(self.db, order_id, scope=TestBatchScope.formal())
self.assertIs(order, actual)
self.assertEqual(items, order.items)
@@ -1102,7 +1129,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = None
self.db.execute.return_value = result
self.assertIsNone(await OperationDao.get_inbound_by_id(self.db, inbound_id=404))
self.assertIsNone(await OperationDao.get_inbound_by_id(self.db, inbound_id=404, scope=TestBatchScope.formal()))
self.db.execute.assert_awaited_once()
@@ -1119,6 +1146,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
self.db,
inbound_id=11,
warehouse_ids=[],
scope=TestBatchScope.formal(),
)
self.assertIsNone(actual)
@@ -1142,7 +1170,7 @@ class TestOperationDaoReads(unittest.IsolatedAsyncioTestCase):
result.scalar_one_or_none.return_value = record
self.db.execute.return_value = result
actual = await method(self.db, record_id)
actual = await method(self.db, record_id, scope=TestBatchScope.formal())
self.assertIs(record, actual)
statement = self.db.execute.await_args.args[0]