Files
ShiErFeng/shierfeng-fastapi-backend/tests/module_business/test_pallet_scan_identity.py
T

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())