安全:补齐业务批次归属校验

This commit is contained in:
焦龙言
2026-09-23 11:49:35 +08:00
parent 97b4dc8508
commit 5a76fe7521
10 changed files with 509 additions and 58 deletions
@@ -100,6 +100,7 @@ def make_pallet(
qr_token: str = 'qr-2',
capacity_packages: int = 20,
asset_status: str = '0',
test_batch_id: int | None = None,
) -> BizPallet:
return BizPallet(
pallet_id=pallet_id,
@@ -107,6 +108,7 @@ def make_pallet(
qr_token=qr_token,
capacity_packages=capacity_packages,
asset_status=asset_status,
test_batch_id=test_batch_id,
)
@@ -119,6 +121,7 @@ def make_inventory(
remaining_packages: int = 8,
flow_state: str = PalletFlowState.AT_SITE_UNUSED.value,
version: int = 3,
test_batch_id: int | None = None,
) -> BizPalletInventory:
return BizPalletInventory(
pallet_id=pallet_id,
@@ -131,6 +134,7 @@ def make_inventory(
last_event_id=40,
version=version,
updated_at=datetime(2026, 7, 25, 9, 0),
test_batch_id=test_batch_id,
)
@@ -139,6 +143,7 @@ def make_filling(
*,
pallet_id: int = 2,
active_slot: str | None = 'ACTIVE',
test_batch_id: int | None = None,
) -> BizFillingRecord:
return BizFillingRecord(
filling_id=filling_id,
@@ -157,16 +162,23 @@ def make_filling(
request_hash='f' * 64,
operator_id=7,
operated_at=datetime(2026, 7, 25, 8, 0),
test_batch_id=test_batch_id,
)
def make_site(site_id: int = 9, *, status: str = '0') -> BizCustomerSite:
def make_site(
site_id: int = 9,
*,
status: str = '0',
test_batch_id: int | None = None,
) -> BizCustomerSite:
return BizCustomerSite(
site_id=site_id,
customer_id=3,
site_code=f'SITE-{site_id}',
site_name=f'现场 {site_id}',
status=status,
test_batch_id=test_batch_id,
)
@@ -175,6 +187,7 @@ def make_line(
*,
site_id: int = 9,
status: str = '0',
test_batch_id: int | None = None,
) -> BizProductionLine:
return BizProductionLine(
line_id=line_id,
@@ -182,6 +195,7 @@ def make_line(
line_code=f'L-{line_id}',
line_name=f'产线 {line_id}',
status=status,
test_batch_id=test_batch_id,
)
@@ -192,6 +206,7 @@ def make_usage(
line_id: int = 4,
idempotency_key: str = 'usage-key',
request_hash: str = 'a' * 64,
test_batch_id: int | None = None,
) -> BizUsageRecord:
return BizUsageRecord(
usage_id=usage_id,
@@ -206,6 +221,7 @@ def make_usage(
request_hash=request_hash,
operator_id=7,
occurred_at=datetime(2026, 7, 26, 11, 30),
test_batch_id=test_batch_id,
)
@@ -519,6 +535,29 @@ class TestUsageReadServices(unittest.IsolatedAsyncioTestCase):
self.assertEqual(5, result.remaining_packages)
self.assertEqual(['usage'], result.allowed_actions)
async def test_test_scope_scan_rejects_cross_batch_pallet(self) -> None:
scope = make_test_scope(4)
with (
patch.object(
InventoryDao,
'get_pallet_by_qr_token',
new=AsyncMock(return_value=make_pallet(test_batch_id=5)),
),
patch.object(
MasterDao,
'get_pallet_inventory',
new=AsyncMock(return_value=make_inventory(test_batch_id=4)),
),
patch.object(AccessService, 'assert_site_access', new=AsyncMock()),
patch.object(
OperationDao,
'get_filling_by_id',
new=AsyncMock(return_value=make_filling(test_batch_id=4)),
),
self.assertRaisesRegex(ServiceException, '不属于当前数据模式或测试批次'),
):
await UsageService.scan_usage_pallet_services(self.db, 'qr-2', self.user, scope)
async def test_scan_hides_missing_non_site_and_unauthorized_qr_for_normal_user(self) -> None:
with (
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=None)),
@@ -1683,6 +1722,7 @@ class TestUsageIdempotency(unittest.IsolatedAsyncioTestCase):
self.expired = False
self.pallet_id_reads = 0
self._pallet_id = pallet_id
self.test_batch_id = None
@property
def pallet_id(self) -> int: