补强主数据可删除性查询测试

This commit is contained in:
焦龙言
2026-07-27 04:51:29 +08:00
parent 33f29df40e
commit 246e69f5c8
@@ -1,4 +1,5 @@
import inspect
import re
import unittest
from contextlib import ExitStack
from datetime import datetime
@@ -8,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from fastapi import FastAPI
from fastapi.routing import APIRoute
from sqlalchemy.dialects import mysql, postgresql
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
@@ -753,9 +755,12 @@ class TestMasterDao(unittest.IsolatedAsyncioTestCase):
async def test_master_removability_uses_the_same_reference_constraints_as_delete(self) -> None:
expected = {
'strain': (('biz_filling_record', ('strain_id',)),),
'customer': (('biz_customer_site', ('customer_id',)),),
'strain': ('biz_strain', 'strain_id', (('biz_filling_record', ('strain_id',)),)),
'customer': ('biz_customer', 'customer_id', (('biz_customer_site', ('customer_id',)),)),
'site': (
'biz_customer_site',
'site_id',
(
('biz_production_line', ('site_id',)),
('biz_user_site', ('site_id',)),
('biz_shipment_order', ('target_site_id',)),
@@ -764,12 +769,20 @@ class TestMasterDao(unittest.IsolatedAsyncioTestCase):
('biz_pallet_inventory', ('site_id',)),
('biz_pallet_event', ('before_site_id', 'after_site_id')),
),
),
'line': (
'biz_production_line',
'line_id',
(
('biz_usage_record', ('line_id',)),
('biz_pallet_inventory', ('current_line_id',)),
('biz_pallet_event', ('before_line_id', 'after_line_id')),
),
),
'warehouse': (
'biz_warehouse',
'warehouse_id',
(
('biz_user_warehouse', ('warehouse_id',)),
('biz_inbound_order', ('warehouse_id',)),
('biz_filling_record', ('warehouse_id',)),
@@ -778,11 +791,12 @@ class TestMasterDao(unittest.IsolatedAsyncioTestCase):
('biz_pallet_inventory', ('warehouse_id',)),
('biz_pallet_event', ('before_warehouse_id', 'after_warehouse_id')),
),
),
}
method = getattr(MasterDao, 'get_master_removability', None)
self.assertIsNotNone(method)
for resource, references in expected.items():
for resource, (outer_table, outer_id, references) in expected.items():
result = MagicMock()
result.all.return_value = [(1, True), (2, False)]
self.db.execute = AsyncMock(return_value=result)
@@ -790,11 +804,29 @@ class TestMasterDao(unittest.IsolatedAsyncioTestCase):
removability = await method(self.db, resource, [1, 2, 3])
self.assertEqual({1: True, 2: False, 3: False}, removability)
sql = compile_sql(self.db.execute.await_args.args[0])
statement = self.db.execute.await_args.args[0]
for dialect in (mysql.dialect(), postgresql.dialect()):
with self.subTest(resource=resource, dialect=dialect.name):
sql = ' '.join(
str(statement.compile(dialect=dialect, compile_kwargs={'literal_binds': True})).split()
)
self.assertEqual(len(references), len(re.findall(r'NOT\s+\(?EXISTS\s+\(', sql)))
for table, columns in references:
self.assertIn(f'FROM {table}', sql)
self.assertRegex(
sql,
rf'NOT\s+\(?EXISTS\s+\(SELECT {table}\.{columns[0]} FROM {table} WHERE ',
)
for column in columns:
self.assertIn(f'{table}.{column}', sql)
self.assertIn(f'{table}.{column} = {outer_table}.{outer_id}', sql)
async def test_master_removability_short_circuits_empty_resource_ids(self) -> None:
self.db.execute = AsyncMock()
for resource in RESOURCE_IDS:
with self.subTest(resource=resource):
self.assertEqual({}, await MasterDao.get_master_removability(self.db, resource, []))
self.db.execute.assert_not_awaited()
class TestMasterControllers(unittest.TestCase):