78 lines
4.2 KiB
Python
78 lines
4.2 KiB
Python
"""在隔离内存库执行真实解析查询,不接触业务数据库。"""
|
|
|
|
import unittest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from sqlalchemy import Column, MetaData, Table, create_engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
from exceptions.exception import PermissionException, ServiceException
|
|
from module_business.dao.inventory_dao import InventoryDao
|
|
from module_business.dao.master_dao import MasterDao
|
|
from module_business.domain.test_batch_scope import TestBatchScope
|
|
from module_business.entity.do.master_do import BizPallet
|
|
from module_business.service.access_service import AccessService
|
|
from module_business.service.filling_service import FillingService
|
|
from tests.module_business.test_batch_fixtures import make_test_scope
|
|
from tests.module_business.test_filling_service import make_inventory, make_pallet, make_user
|
|
|
|
|
|
class TestPalletScanIdentity(unittest.IsolatedAsyncioTestCase):
|
|
def setUp(self):
|
|
self.engine = create_engine('sqlite:///:memory:')
|
|
metadata = MetaData()
|
|
# 本测试只执行托盘查询,不复制业务数据库或外键依赖。
|
|
self.table = Table('biz_pallet', metadata, *[
|
|
Column(column.name, column.type, primary_key=column.primary_key)
|
|
for column in BizPallet.__table__.columns
|
|
])
|
|
metadata.create_all(self.engine)
|
|
self.session = Session(self.engine)
|
|
self.db = MagicMock()
|
|
self.db.execute = AsyncMock(side_effect=self.session.execute)
|
|
self.session.execute(self.table.insert(), [
|
|
{'pallet_id': 1, 'pallet_code': '123', 'qr_token': 'qr-one', 'test_batch_id': None},
|
|
{'pallet_id': 2, 'pallet_code': '456', 'qr_token': 'qr-two', 'test_batch_id': 7},
|
|
])
|
|
|
|
def tearDown(self):
|
|
self.session.close()
|
|
self.engine.dispose()
|
|
|
|
async def test_printed_number_and_qrcode_resolve_same_pallet(self):
|
|
by_code = await InventoryDao.get_pallet_by_qr_token(self.db, '123', TestBatchScope.formal())
|
|
by_qr = await InventoryDao.get_pallet_by_qr_token(self.db, 'qr-one', TestBatchScope.formal())
|
|
self.assertIsNotNone(by_code)
|
|
self.assertEqual(by_code.pallet_id, by_qr.pallet_id)
|
|
self.assertEqual(by_code.qr_token, 'qr-one')
|
|
|
|
async def test_resolver_never_crosses_formal_and_test_scopes(self):
|
|
self.assertIsNone(await InventoryDao.get_pallet_by_qr_token(self.db, '456', TestBatchScope.formal()))
|
|
self.assertIsNone(await InventoryDao.get_pallet_by_qr_token(self.db, '123', make_test_scope(7)))
|
|
pallet = await InventoryDao.get_pallet_by_qr_token(self.db, '456', make_test_scope(7))
|
|
self.assertIsNotNone(pallet)
|
|
self.assertEqual(pallet.pallet_id, 2)
|
|
|
|
async def test_ambiguous_number_and_token_are_rejected(self):
|
|
self.session.execute(self.table.insert(), {'pallet_id': 3, 'pallet_code': '789', 'qr_token': '123', 'test_batch_id': None})
|
|
with self.assertRaises(ServiceException):
|
|
await InventoryDao.get_pallet_by_qr_token(self.db, '123', TestBatchScope.formal())
|
|
|
|
async def test_scan_returns_canonical_token_after_warehouse_authorization(self):
|
|
with (
|
|
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=make_pallet())),
|
|
patch.object(MasterDao, 'get_pallet_inventory', new=AsyncMock(return_value=make_inventory())),
|
|
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock()),
|
|
):
|
|
result = await FillingService.scan_filling_pallet_services(self.db, 'P-002', make_user(), TestBatchScope.formal())
|
|
self.assertEqual(result.qr_token, 'qr-2')
|
|
|
|
async def test_printed_number_does_not_bypass_warehouse_access(self):
|
|
with (
|
|
patch.object(InventoryDao, 'get_pallet_by_qr_token', new=AsyncMock(return_value=make_pallet())),
|
|
patch.object(MasterDao, 'get_pallet_inventory', new=AsyncMock(return_value=make_inventory())),
|
|
patch.object(AccessService, 'assert_warehouse_access', new=AsyncMock(side_effect=PermissionException(data='', message='无权访问'))),
|
|
self.assertRaises(PermissionException),
|
|
):
|
|
await FillingService.scan_filling_pallet_services(self.db, 'P-002', make_user(), TestBatchScope.formal())
|