| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- 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
|