"""在隔离内存库执行真实解析查询,不接触业务数据库。""" 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())