安全:补齐业务批次归属校验
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user