"""现场派车日历菜单迁移:仅现场角色可获得只读入口,保留管理员撤权。""" from pathlib import Path import re import sqlite3 from uuid import uuid4 import pytest ROOT = Path(__file__).resolve().parents[2] MIGRATION = ROOT / 'docs/sql/migrations/2026-10-30-mini-site-dispatch-calendar-menu.sql' PERM = 'mini:pl:site-dispatch-calendar:list' def database(): db = sqlite3.connect(':memory:') db.create_function('UUID', 0, lambda: str(uuid4())) db.create_function('NOW', 0, lambda: '2026-10-30 12:00:00') common = "tenant_no TEXT DEFAULT 'shanxia2.0', order_num INT, remark TEXT, create_by TEXT, create_dept_id TEXT, create_time TEXT, update_time TEXT, del_flag INT DEFAULT 0" db.execute(f"CREATE TABLE sys_menu (id TEXT PRIMARY KEY, menu_name TEXT, parent_id TEXT DEFAULT '0', menu_type TEXT, path TEXT, component TEXT, perms TEXT, is_frame INT, visible TEXT DEFAULT '1', status TEXT DEFAULT '1', {common})") db.execute(f"CREATE TABLE sys_role (id TEXT PRIMARY KEY, role_name TEXT, status TEXT DEFAULT '1', {common})") db.execute(f"CREATE TABLE sys_role_menu (id TEXT PRIMARY KEY, role_id TEXT, menu_id TEXT, {common})") db.execute("INSERT INTO sys_menu (id,path) VALUES ('mini-root','/mini')") db.execute("INSERT INTO sys_menu (id,parent_id,path) VALUES ('MINI_PL_ROOT','mini-root','/mini/pl')") for role_id, name, tenant, status, deleted in [ ('site', '现场人员', 'shanxia2.0', '1', 0), ('site2', '现场人员', 'shanxia2.0', '1', 0), ('driver', '司机', 'shanxia2.0', '1', 0), ('admin', '管理员', 'shanxia2.0', '1', 0), ('similar', '现场人员管理', 'shanxia2.0', '1', 0), ('foreign', '现场人员', 'other', '1', 0), ('disabled', '现场人员', 'shanxia2.0', '0', 0), ('deleted', '现场人员', 'shanxia2.0', '1', 1), ]: db.execute('INSERT INTO sys_role (id,role_name,tenant_no,status,del_flag) VALUES (?,?,?,?,?)', (role_id, name, tenant, status, deleted)) return db def migrate(db): assert MIGRATION.exists(), '现场派车日历迁移尚未创建' for sql in re.findall(r'INSERT INTO sys_(?:menu|role_menu)\s*\(.*?;', MIGRATION.read_text(), re.S): db.execute(sql) def test_only_active_exact_site_roles_get_leaf_and_ancestors_once(): with database() as db: migrate(db) migrate(db) leaf = db.execute('SELECT id FROM sys_menu WHERE perms=?', (PERM,)).fetchone()[0] assert set(db.execute('SELECT role_id,menu_id FROM sys_role_menu')) == { (role, menu) for role in ['site', 'site2'] for menu in [leaf, 'MINI_PL_ROOT', 'mini-root'] } assert db.execute('SELECT COUNT(*) FROM sys_role_menu').fetchone()[0] == 6 assert list(db.execute('SELECT perms FROM sys_menu WHERE perms IS NOT NULL')) == [(PERM,)] @pytest.mark.parametrize('part', ['leaf', 'MINI_PL_ROOT', 'mini-root']) def test_deleted_grants_are_never_restored(part): with database() as db: migrate(db) if part == 'leaf': part = db.execute('SELECT id FROM sys_menu WHERE perms=?', (PERM,)).fetchone()[0] db.execute('UPDATE sys_role_menu SET del_flag=1 WHERE role_id=? AND menu_id=?', ('site', part)) migrate(db) assert list(db.execute('SELECT del_flag FROM sys_role_menu WHERE role_id=? AND menu_id=?', ('site', part))) == [(1,)] @pytest.mark.parametrize('field,value', [('del_flag', 1), ('status', '0'), ('visible', '0')]) @pytest.mark.parametrize('part', ['leaf', 'MINI_PL_ROOT', 'mini-root']) def test_inactive_menus_do_not_create_new_grants(field, value, part): with database() as db: migrate(db) if part == 'leaf': part = db.execute('SELECT id FROM sys_menu WHERE perms=?', (PERM,)).fetchone()[0] db.execute(f'UPDATE sys_menu SET {field}=? WHERE id=?', (value, part)) db.execute("INSERT INTO sys_role (id,role_name) VALUES ('new-site','现场人员')") migrate(db) assert db.execute("SELECT COUNT(*) FROM sys_role_menu WHERE role_id='new-site'").fetchone()[0] == 0 assert db.execute('SELECT COUNT(*) FROM sys_menu WHERE perms=?', (PERM,)).fetchone()[0] == 1 @pytest.mark.parametrize('change', [ "UPDATE sys_menu SET path='/web' WHERE id='mini-root'", "UPDATE sys_menu SET parent_id='not-root' WHERE id='mini-root'", "UPDATE sys_menu SET tenant_no='other'", "UPDATE sys_menu SET del_flag=1 WHERE id='MINI_PL_ROOT'", ]) def test_wrong_tree_or_tenant_does_not_create_menu(change): with database() as db: db.execute(change) migrate(db) assert db.execute('SELECT COUNT(*) FROM sys_menu WHERE perms=?', (PERM,)).fetchone()[0] == 0 assert db.execute('SELECT COUNT(*) FROM sys_role_menu').fetchone()[0] == 0 def test_mini_entry_uses_dedicated_permission_and_h5_path(): menu = (ROOT / 'miniprogram/config/workbench-menus.js').read_text() route = (ROOT / 'miniprogram/config/api.js').read_text() assert re.search(r"name: '现场派车日历'.*?code: 'pl-site-dispatch-calendar-webview'.*?perms: '" + PERM + "'", menu, re.S) assert "'pl-site-dispatch-calendar-webview': PAGE_PATHS.mpWebview + '?path=' + encodeURIComponent('/m/pl/site-dispatch-calendar')" in route