测试:校验菜单接口权限一致性
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import re
|
||||
import unittest
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
SQL_ROOT = Path(__file__).resolve().parents[2] / 'sql'
|
||||
INITIALIZERS = ('shierfeng-fastapi.sql', 'shierfeng-fastapi-pg.sql')
|
||||
PATCH_PATH = SQL_ROOT / 'patches/20260724_pallet_workflow_mysql.sql'
|
||||
SYS_MENU_VALUE_COUNT = 20
|
||||
SYS_MENU_PERMS_INDEX = 13
|
||||
REMOVED_AI_DICT_CODES = set(range(33, 70))
|
||||
|
||||
EXPECTED_TABLES = {
|
||||
@@ -408,6 +410,161 @@ EXPECTED_BUTTONS = {
|
||||
line.split() for line in BUTTON_SPECS.splitlines() if line.strip()
|
||||
)
|
||||
}
|
||||
EXPECTED_PAGE_PERMISSIONS = frozenset(
|
||||
[
|
||||
'master:strain:list',
|
||||
'master:customer:list',
|
||||
'master:site:list',
|
||||
'master:line:list',
|
||||
'master:warehouse:list',
|
||||
'master:pallet:list',
|
||||
'business:inbound:list',
|
||||
'business:filling:list',
|
||||
'business:shipment:list',
|
||||
'business:usage:list',
|
||||
'business:return:list',
|
||||
'report:palletinventory:list',
|
||||
'report:filling:list',
|
||||
'report:shipment:list',
|
||||
'report:consumption:list',
|
||||
'report:return:list',
|
||||
'report:pallettrace:list',
|
||||
]
|
||||
)
|
||||
EXPECTED_BUTTON_PERMISSIONS = frozenset(
|
||||
[
|
||||
'master:strain:query',
|
||||
'master:strain:add',
|
||||
'master:strain:edit',
|
||||
'master:strain:remove',
|
||||
'master:customer:query',
|
||||
'master:customer:add',
|
||||
'master:customer:edit',
|
||||
'master:customer:remove',
|
||||
'master:site:query',
|
||||
'master:site:add',
|
||||
'master:site:edit',
|
||||
'master:site:remove',
|
||||
'master:site:assign',
|
||||
'master:line:query',
|
||||
'master:line:add',
|
||||
'master:line:edit',
|
||||
'master:line:remove',
|
||||
'master:warehouse:query',
|
||||
'master:warehouse:add',
|
||||
'master:warehouse:edit',
|
||||
'master:warehouse:remove',
|
||||
'master:warehouse:assign',
|
||||
'master:pallet:query',
|
||||
'master:pallet:add',
|
||||
'master:pallet:edit',
|
||||
'master:pallet:remove',
|
||||
'master:pallet:print',
|
||||
'business:inbound:query',
|
||||
'business:inbound:add',
|
||||
'business:inbound:edit',
|
||||
'business:inbound:remove',
|
||||
'business:inbound:confirm',
|
||||
'business:filling:query',
|
||||
'business:filling:add',
|
||||
'business:shipment:query',
|
||||
'business:shipment:add',
|
||||
'business:shipment:edit',
|
||||
'business:shipment:remove',
|
||||
'business:shipment:confirm',
|
||||
'business:usage:query',
|
||||
'business:usage:add',
|
||||
'business:return:query',
|
||||
'business:return:request',
|
||||
'business:return:confirm',
|
||||
]
|
||||
)
|
||||
EXPECTED_PERMISSIONS = EXPECTED_PAGE_PERMISSIONS | EXPECTED_BUTTON_PERMISSIONS
|
||||
BUSINESS_MENU_SPECS = """
|
||||
121|1|基础资料维护|businessMaster||10||<NULL>
|
||||
122|1|业务管理|businessOperation||11||<NULL>
|
||||
123|1|报表查询|businessReport||12||<NULL>
|
||||
502|121|菌种资料|strain||1|master:strain:list|master/strain/index
|
||||
503|121|客户资料|customer||2|master:customer:list|master/customer/index
|
||||
504|121|客户现场|site||3|master:site:list|master/site/index
|
||||
505|121|产线资料|line||4|master:line:list|master/line/index
|
||||
506|121|仓库资料|warehouse||5|master:warehouse:list|master/warehouse/index
|
||||
507|121|托盘资料|pallet||6|master:pallet:list|master/pallet/index
|
||||
508|122|仓库入库|inbound||1|business:inbound:list|business/inbound/index
|
||||
509|122|工厂装填|filling||2|business:filling:list|business/filling/index
|
||||
510|122|发货出库|shipment||3|business:shipment:list|business/shipment/index
|
||||
511|122|客户现场使用|usage||4|business:usage:list|business/usage/index
|
||||
512|122|退回仓库|return||5|business:return:list|business/return/index
|
||||
513|123|托盘库存|palletInventory||1|report:palletinventory:list|report/palletInventory/index
|
||||
514|123|装填记录|reportFilling||2|report:filling:list|report/filling/index
|
||||
515|123|发货出库|reportShipment||3|report:shipment:list|report/shipment/index
|
||||
516|123|产线消耗|consumption||4|report:consumption:list|report/consumption/index
|
||||
517|123|退回仓库|reportReturn||5|report:return:list|report/return/index
|
||||
518|123|托盘追溯|palletTrace||6|report:pallettrace:list|report/palletTrace/index
|
||||
1065|502|菌种查询|||1|master:strain:query|
|
||||
1066|502|菌种新增|||2|master:strain:add|
|
||||
1067|502|菌种修改|||3|master:strain:edit|
|
||||
1068|502|菌种删除|||4|master:strain:remove|
|
||||
1069|503|客户查询|||1|master:customer:query|
|
||||
1070|503|客户新增|||2|master:customer:add|
|
||||
1071|503|客户修改|||3|master:customer:edit|
|
||||
1072|503|客户删除|||4|master:customer:remove|
|
||||
1073|504|现场查询|||1|master:site:query|
|
||||
1074|504|现场新增|||2|master:site:add|
|
||||
1075|504|现场修改|||3|master:site:edit|
|
||||
1076|504|现场删除|||4|master:site:remove|
|
||||
1077|504|分配用户|||5|master:site:assign|
|
||||
1078|505|产线查询|||1|master:line:query|
|
||||
1079|505|产线新增|||2|master:line:add|
|
||||
1080|505|产线修改|||3|master:line:edit|
|
||||
1081|505|产线删除|||4|master:line:remove|
|
||||
1082|506|仓库查询|||1|master:warehouse:query|
|
||||
1083|506|仓库新增|||2|master:warehouse:add|
|
||||
1084|506|仓库修改|||3|master:warehouse:edit|
|
||||
1085|506|仓库删除|||4|master:warehouse:remove|
|
||||
1086|506|分配用户|||5|master:warehouse:assign|
|
||||
1087|507|托盘查询|||1|master:pallet:query|
|
||||
1088|507|托盘新增|||2|master:pallet:add|
|
||||
1089|507|托盘修改|||3|master:pallet:edit|
|
||||
1090|507|托盘删除|||4|master:pallet:remove|
|
||||
1091|507|托盘打印|||5|master:pallet:print|
|
||||
1092|508|入库查询|||1|business:inbound:query|
|
||||
1093|508|入库新增|||2|business:inbound:add|
|
||||
1094|508|入库修改|||3|business:inbound:edit|
|
||||
1095|508|入库删除|||4|business:inbound:remove|
|
||||
1096|508|入库确认|||5|business:inbound:confirm|
|
||||
1097|509|装填查询|||1|business:filling:query|
|
||||
1098|509|装填新增|||2|business:filling:add|
|
||||
1099|510|发货查询|||1|business:shipment:query|
|
||||
1100|510|发货新增|||2|business:shipment:add|
|
||||
1101|510|发货修改|||3|business:shipment:edit|
|
||||
1102|510|发货删除|||4|business:shipment:remove|
|
||||
1103|510|发货确认|||5|business:shipment:confirm|
|
||||
1104|511|使用查询|||1|business:usage:query|
|
||||
1105|511|使用新增|||2|business:usage:add|
|
||||
1106|512|退回查询|||1|business:return:query|
|
||||
1107|512|退回申请|||2|business:return:request|
|
||||
1108|512|退回确认|||3|business:return:confirm|
|
||||
""".strip()
|
||||
|
||||
|
||||
def _expected_business_menu_fields() -> dict[int, tuple]:
|
||||
expected = {}
|
||||
for spec in BUSINESS_MENU_SPECS.splitlines():
|
||||
menu_id, parent_id, menu_name, path, route_name, order_num, perms, component = spec.split('|')
|
||||
expected[int(menu_id)] = (
|
||||
int(parent_id),
|
||||
menu_name,
|
||||
path,
|
||||
route_name,
|
||||
int(order_num),
|
||||
perms,
|
||||
None if component == '<NULL>' else component,
|
||||
)
|
||||
return expected
|
||||
|
||||
|
||||
EXPECTED_BUSINESS_MENU_FIELDS = _expected_business_menu_fields()
|
||||
BUSINESS_MENU_IDS = set(range(121, 124)) | set(range(502, 519)) | set(range(1065, 1109))
|
||||
MYSQL_MENU_STABLE_COLUMNS = (
|
||||
'menu_name',
|
||||
@@ -683,6 +840,38 @@ def _business_menu_rows(sql: str) -> dict[int, list]:
|
||||
return {int(row[0]): row for row in selected}
|
||||
|
||||
|
||||
def _assert_business_menu_fields(test_case: unittest.TestCase, sql: str) -> None:
|
||||
rows = _business_menu_rows(sql)
|
||||
invalid_widths = {menu_id: len(row) for menu_id, row in rows.items() if len(row) != SYS_MENU_VALUE_COUNT}
|
||||
test_case.assertEqual({}, invalid_widths)
|
||||
actual = {
|
||||
menu_id: (
|
||||
int(row[2]),
|
||||
row[1],
|
||||
row[4],
|
||||
row[7],
|
||||
int(row[3]),
|
||||
row[13],
|
||||
row[5],
|
||||
)
|
||||
for menu_id, row in rows.items()
|
||||
}
|
||||
test_case.assertEqual(EXPECTED_BUSINESS_MENU_FIELDS, actual)
|
||||
|
||||
|
||||
def _assert_business_permission_inventory(test_case: unittest.TestCase, sql: str) -> None:
|
||||
permission_rows = [
|
||||
row
|
||||
for row in _insert_rows(sql, 'sys_menu')
|
||||
if len(row) > SYS_MENU_PERMS_INDEX
|
||||
and row[SYS_MENU_PERMS_INDEX]
|
||||
and re.match(r'^(?:master|business|report):', row[SYS_MENU_PERMS_INDEX])
|
||||
]
|
||||
test_case.assertTrue(all(len(row) == SYS_MENU_VALUE_COUNT for row in permission_rows))
|
||||
actual_permissions = Counter(row[SYS_MENU_PERMS_INDEX] for row in permission_rows)
|
||||
test_case.assertEqual(Counter(EXPECTED_PERMISSIONS), actual_permissions)
|
||||
|
||||
|
||||
class SqlContractTest(unittest.TestCase):
|
||||
def test_initializers_define_exact_business_table_set(self) -> None:
|
||||
for filename in INITIALIZERS:
|
||||
@@ -731,13 +920,17 @@ class SqlContractTest(unittest.TestCase):
|
||||
self.assertFalse(_is_postgres_naive_timestamp_with_default_precision('occurred_at timestamp with time zone'))
|
||||
|
||||
def test_initializers_define_fixed_business_menus_and_permissions(self) -> None:
|
||||
expected_permissions = {page[-1] for page in EXPECTED_PAGES.values()} | {
|
||||
button[-1] for button in EXPECTED_BUTTONS.values()
|
||||
}
|
||||
self.assertEqual(61, len(expected_permissions))
|
||||
self.assertEqual(17, len(EXPECTED_PAGE_PERMISSIONS))
|
||||
self.assertEqual(44, len(EXPECTED_BUTTON_PERMISSIONS))
|
||||
self.assertFalse(EXPECTED_PAGE_PERMISSIONS & EXPECTED_BUTTON_PERMISSIONS)
|
||||
self.assertEqual(61, len(EXPECTED_PERMISSIONS))
|
||||
self.assertEqual(64, len(EXPECTED_BUSINESS_MENU_FIELDS))
|
||||
for filename in INITIALIZERS:
|
||||
with self.subTest(filename=filename):
|
||||
rows = _business_menu_rows((SQL_ROOT / filename).read_text(encoding='utf-8'))
|
||||
sql = (SQL_ROOT / filename).read_text(encoding='utf-8')
|
||||
_assert_business_menu_fields(self, sql)
|
||||
_assert_business_permission_inventory(self, sql)
|
||||
rows = _business_menu_rows(sql)
|
||||
self.assertTrue(all(len(row) == SYS_MENU_VALUE_COUNT for row in rows.values()))
|
||||
self.assertEqual(
|
||||
('基础资料维护', '1', '10', 'businessMaster', 'M', ''),
|
||||
@@ -770,15 +963,32 @@ class SqlContractTest(unittest.TestCase):
|
||||
('', '', '', '', '1', '0', 'F', '0', '0'),
|
||||
tuple(row[index] for index in (4, 5, 6, 7, 8, 9, 10, 11, 12)),
|
||||
)
|
||||
all_menu_rows = _insert_rows((SQL_ROOT / filename).read_text(encoding='utf-8'), 'sys_menu')
|
||||
actual_permissions = {
|
||||
row[13]
|
||||
for row in all_menu_rows
|
||||
if len(row) == SYS_MENU_VALUE_COUNT
|
||||
and row[13]
|
||||
and re.match(r'^(?:master|business|report):', row[13])
|
||||
}
|
||||
self.assertEqual(expected_permissions, actual_permissions)
|
||||
|
||||
def test_business_permission_inventory_rejects_duplicate_permission(self) -> None:
|
||||
sql = (SQL_ROOT / INITIALIZERS[0]).read_text(encoding='utf-8')
|
||||
sql += """
|
||||
insert into sys_menu values(
|
||||
'1999', '重复权限', '1', '1', '', '', '', '', 1, 0,
|
||||
'F', '0', '0', 'master:strain:list', '#', 'admin', sysdate(), '', null, ''
|
||||
);
|
||||
"""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
_assert_business_permission_inventory(self, sql)
|
||||
|
||||
def test_business_menu_contract_rejects_same_field_drift_in_both_initializers(self) -> None:
|
||||
for filename in INITIALIZERS:
|
||||
with self.subTest(filename=filename):
|
||||
sql = (SQL_ROOT / filename).read_text(encoding='utf-8')
|
||||
mutated, replacements = re.subn(
|
||||
r"(\(?\s*'?502'?\s*,\s*)'菌种资料'",
|
||||
r"\1'漂移菌种资料'",
|
||||
sql,
|
||||
count=1,
|
||||
)
|
||||
self.assertEqual(1, replacements)
|
||||
with self.assertRaises(AssertionError):
|
||||
_assert_business_menu_fields(self, mutated)
|
||||
|
||||
def test_mysql_and_postgresql_business_menu_fields_are_identical(self) -> None:
|
||||
mysql_rows = _business_menu_rows((SQL_ROOT / INITIALIZERS[0]).read_text(encoding='utf-8'))
|
||||
@@ -914,6 +1124,8 @@ class SqlContractTest(unittest.TestCase):
|
||||
self.assertEqual(EXPECTED_UNIQUE_KEYS[table], unique_keys)
|
||||
self.assertEqual(EXPECTED_FOREIGN_KEYS[table], foreign_keys)
|
||||
self.assertEqual(EXPECTED_CHECKS[table], checks)
|
||||
_assert_business_menu_fields(self, raw)
|
||||
_assert_business_permission_inventory(self, raw)
|
||||
patch_rows = _business_menu_rows(raw)
|
||||
mysql_rows = _business_menu_rows((SQL_ROOT / 'shierfeng-fastapi.sql').read_text(encoding='utf-8'))
|
||||
compared_indexes = tuple(range(15))
|
||||
|
||||
Reference in New Issue
Block a user