Files
zkxw/backend/tests/test_market_mobile_menu_sql.py
T
2026-10-14 12:32:03 +08:00

189 lines
6.6 KiB
Python

import re
from pathlib import Path
MIGRATION = (
Path(__file__).resolve().parents[2]
/ "docs/sql/migrations/2026-10-10-market-mobile-menus.sql"
)
ROLES = {
"specialist": "市场专员",
"manager": "地区经理",
}
MODULES = {
"presign": "预签约订单",
"protocol": "协议单",
"payment-chase": "催缴信息",
"payment": "缴费信息",
"freight": "运费信息",
}
def _sql() -> str:
return MIGRATION.read_text(encoding="utf-8")
def _compact(sql: str) -> str:
return re.sub(r"\s+", " ", sql)
def _without_comments(sql: str) -> str:
return re.sub(r"--[^\n]*", "", sql)
def _insert_statements(sql: str) -> list[str]:
return [
_compact(statement)
for statement in _without_comments(sql).split(";")
if re.search(r"\bINSERT\s+INTO\s+sys_menu\b", statement, re.IGNORECASE)
]
def test_market_mobile_menu_migration_defines_two_empty_path_directories():
sql = _compact(_without_comments(_sql()))
for directory_name in ROLES.values():
assert re.search(
rf"'{directory_name}'\s*,\s*root\.id\s*,\s*'M'\s*,\s*''",
sql,
)
def test_market_mobile_menu_migration_defines_exactly_ten_fixed_leaves():
sql = _without_comments(_sql())
expected_paths = {
f"/pages/market/webview/index?entry={role}-{module}"
for role in ROLES
for module in MODULES
}
expected_perms = {
f"mini:market:{role}:{module}"
for role in ROLES
for module in MODULES
}
paths = re.findall(
r"['\"](/pages/market/webview/index\?entry=[a-z-]+)['\"]", sql
)
perms = re.findall(r"['\"](mini:market:[a-z-]+:[a-z-]+)['\"]", sql)
assert len(paths) == 10
assert set(paths) == expected_paths
assert len(perms) == 10
assert set(perms) == expected_perms
for role in ROLES:
for module in MODULES:
path = f"/pages/market/webview/index?entry={role}-{module}"
permission = f"mini:market:{role}:{module}"
assert re.search(
rf"'{re.escape(path)}'\s*(?:AS\s+path)?\s*,\s*"
rf"'{re.escape(permission)}'\s*(?:AS\s+perms)?",
sql,
re.IGNORECASE,
)
assert re.search(
r"entry\.menu_name\s*,\s*directory\.id\s*,\s*'C'\s*,\s*entry\.path",
_compact(sql),
)
def test_market_mobile_menu_migration_is_idempotent_and_tenant_aware():
sql = _sql()
compact = _compact(_without_comments(sql))
inserts = _insert_statements(sql)
assert len(inserts) == 3
assert all(re.search(r"\bNOT\s+EXISTS\b", item, re.IGNORECASE) for item in inserts)
assert re.search(r"root\.parent_id\s*=\s*'0'", compact)
assert re.search(r"root\.menu_name\s*=\s*'移动端菜单'", compact)
assert re.search(r"root\.del_flag\s*=\s*0", compact)
assert compact.count("root.tenant_no") >= len(inserts)
assert "'zkxw'" not in sql
assert re.search(r"existing\.parent_id\s*=\s*directory\.id", compact)
assert re.search(r"existing\.path\s*=\s*entry\.path", compact)
assert re.search(r"existing\.menu_type\s*=\s*'C'", compact)
def test_each_insert_uses_the_complete_active_natural_key_without_bare_id_or():
specialist, manager, leaves = _insert_statements(_sql())
for directory, name in ((specialist, "市场专员"), (manager, "地区经理")):
assert "existing.id" not in directory
assert re.search(r"existing\.tenant_no\s*=\s*root\.tenant_no", directory)
assert re.search(r"existing\.parent_id\s*=\s*root\.id", directory)
assert re.search(r"existing\.menu_name\s*=\s*'" + name + r"'", directory)
assert re.search(r"existing\.menu_type\s*=\s*'M'", directory)
assert re.search(r"existing\.path\s*=\s*''", directory)
assert re.search(r"existing\.del_flag\s*=\s*0", directory)
assert "existing.id" not in leaves
assert re.search(r"existing\.tenant_no\s*=\s*root\.tenant_no", leaves)
assert re.search(r"existing\.parent_id\s*=\s*directory\.id", leaves)
assert re.search(r"existing\.menu_name\s*=\s*entry\.menu_name", leaves)
assert re.search(r"existing\.menu_type\s*=\s*'C'", leaves)
assert re.search(r"existing\.path\s*=\s*entry\.path", leaves)
assert re.search(r"existing\.perms\s*=\s*entry\.perms", leaves)
assert re.search(r"existing\.del_flag\s*=\s*0", leaves)
def test_ids_and_directory_join_are_scoped_to_the_expected_tree_location():
specialist, manager, leaves = _insert_statements(_sql())
for directory in (specialist, manager):
id_expression = re.search(
r"\bSELECT\s+(MD5\(.*?\))\s*,\s*'(?:市场专员|地区经理)'",
directory,
).group(1)
assert "root.tenant_no" in id_expression
assert "root.id" in id_expression
leaf_id_expression = re.search(
r"\bSELECT\s+(MD5\(.*?\))\s*,\s*entry\.menu_name", leaves
).group(1)
assert "root.tenant_no" in leaf_id_expression
assert "root.id" in leaf_id_expression
assert "directory.id" in leaf_id_expression
assert "entry.path" in leaf_id_expression
assert re.search(r"directory\.path\s*=\s*''", leaves)
def test_same_id_wrong_or_soft_deleted_rows_are_repaired_instead_of_blocking_insert():
specialist, manager, leaves = _insert_statements(_sql())
for statement in (specialist, manager, leaves):
assert re.search(r"\bON\s+DUPLICATE\s+KEY\s+UPDATE\b", statement)
repair = re.split(
r"\bON\s+DUPLICATE\s+KEY\s+UPDATE\b", statement, maxsplit=1
)[1]
for field in (
"tenant_no",
"parent_id",
"menu_name",
"menu_type",
"path",
"perms",
"del_flag",
):
assert re.search(
rf"\b{field}\s*=\s*VALUES\s*\(\s*{field}\s*\)", repair
), field
def test_market_mobile_menu_migration_does_not_grant_roles_automatically():
assert "sys_role_menu" not in _without_comments(_sql())
def test_hidden_sample_menu_migration_revokes_existing_role_links():
migration = (
Path(__file__).resolve().parents[2]
/ 'docs/sql/migrations/2026-10-13-hide-market-mobile-sample-menus.sql'
).read_text(encoding='utf-8')
compact = _compact(_without_comments(migration))
assert re.search(r'UPDATE\s+sys_role_menu', compact, re.IGNORECASE)
assert re.search(r'UPDATE\s+sys_role_menu\s+SET\s+del_flag\s*=\s*1', compact, re.IGNORECASE)
assert re.search(r'menu_id\s+IN\s*\(\s*SELECT\s+id\s+FROM\s+sys_menu', compact, re.IGNORECASE)
assert 'mini:market:specialist:sample' in compact
assert 'mini:market:manager:sample' in compact