142 lines
5.5 KiB
Python
142 lines
5.5 KiB
Python
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(r"D:\www\sport-era\admin")
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
MATCH_API = ROOT / "src" / "api" / "match.ts"
|
|
MATCH_INDEX = ROOT / "src" / "views" / "match" / "lists" / "index.vue"
|
|
MATCH_LIVE_TABLE = ROOT / "src" / "views" / "match" / "lists" / "components" / "MatchLiveTable.vue"
|
|
MENU_SQL = REPO_ROOT / "docs" / "sql" / "insert_menu_match_live.sql"
|
|
|
|
|
|
def assert_contains(path: Path, needle: str) -> None:
|
|
content = path.read_text(encoding="utf-8")
|
|
assert needle in content, f"{path} missing: {needle}"
|
|
|
|
|
|
def main() -> None:
|
|
match_api = MATCH_API.read_text(encoding="utf-8")
|
|
match_live_table = MATCH_LIVE_TABLE.read_text(encoding="utf-8")
|
|
assert MENU_SQL.is_file(), f"missing menu seed file: {MENU_SQL}"
|
|
menu_sql = MENU_SQL.read_text(encoding="utf-8")
|
|
normalized_menu_sql = " ".join(menu_sql.split())
|
|
first_insert_block = menu_sql.split(";", 1)[0] + ";"
|
|
normalized_first_insert = " ".join(first_insert_block.split())
|
|
|
|
assert_contains(MATCH_INDEX, 'type="expand"')
|
|
assert_contains(MATCH_INDEX, "MatchLiveTable")
|
|
|
|
for needle in (
|
|
"matchLiveLists",
|
|
"matchLiveDetail",
|
|
"matchLiveAdd",
|
|
"matchLiveEdit",
|
|
"matchLiveDelete",
|
|
):
|
|
assert needle in match_api, f"{MATCH_API} missing: {needle}"
|
|
|
|
for needle in (
|
|
"play_url_m3u8",
|
|
"fetch_status",
|
|
"source_url",
|
|
):
|
|
assert needle in match_live_table, f"{MATCH_LIVE_TABLE} missing: {needle}"
|
|
|
|
readonly_fields = (
|
|
'play_url_m3u8" readonly',
|
|
'play_url_hd_m3u8" readonly',
|
|
'play_url_flv" readonly',
|
|
'play_url_hd_flv" readonly',
|
|
'formatFetchStatus(editData.fetch_status)" readonly',
|
|
'editData.last_fetch_at" readonly',
|
|
)
|
|
for needle in readonly_fields:
|
|
assert needle in match_live_table, f"{MATCH_LIVE_TABLE} missing readonly guard: {needle}"
|
|
|
|
handle_edit_block = match_live_table.split("const handleEdit = async (id: number) => {", 1)[1].split(
|
|
"const handleSubmit = async () => {", 1
|
|
)[0]
|
|
assert "resetEditData()" in handle_edit_block, f"{MATCH_LIVE_TABLE} missing resetEditData usage in handleEdit"
|
|
assert "const res = await matchLiveDetail" in handle_edit_block, f"{MATCH_LIVE_TABLE} missing detail request"
|
|
assert (
|
|
handle_edit_block.index("resetEditData()")
|
|
< handle_edit_block.index("const res = await matchLiveDetail")
|
|
), "handleEdit must reset editData before matchLiveDetail response is applied"
|
|
|
|
assert "const params = {" in match_live_table, f"{MATCH_LIVE_TABLE} missing submit payload"
|
|
for needle in (
|
|
"id: editData.id",
|
|
"match_id: props.matchId",
|
|
"title: editData.title",
|
|
"source_url: editData.source_url",
|
|
):
|
|
assert needle in match_live_table, f"{MATCH_LIVE_TABLE} missing submit field: {needle}"
|
|
forbidden_submit_fields = (
|
|
"play_url_m3u8:",
|
|
"play_url_hd_m3u8:",
|
|
"play_url_flv:",
|
|
"play_url_hd_flv:",
|
|
"fetch_status:",
|
|
"last_fetch_at:",
|
|
"create_time:",
|
|
"update_time:",
|
|
)
|
|
params_slice = match_live_table.split("const params = {", 1)[1].split("\n }", 1)[0]
|
|
for needle in forbidden_submit_fields:
|
|
assert needle not in params_slice, f"submit payload must not include readonly field: {needle}"
|
|
|
|
for needle in (
|
|
"status === 0",
|
|
"status === 1",
|
|
"status === 2",
|
|
"status === '0'",
|
|
"status === '1'",
|
|
"status === '2'",
|
|
):
|
|
assert needle in match_live_table, f"{MATCH_LIVE_TABLE} missing fetch_status mapping: {needle}"
|
|
|
|
for needle in (
|
|
"match.matchLive/lists",
|
|
"match.matchLive/detail",
|
|
"match.matchLive/add",
|
|
"match.matchLive/edit",
|
|
"match.match/lists",
|
|
"match.matchLive/delete",
|
|
):
|
|
assert needle in menu_sql, f"{MENU_SQL} missing permission token: {needle}"
|
|
|
|
forbidden_menu_fallbacks = (
|
|
"COALESCE(",
|
|
", 0), 'C', '直播线路管理'",
|
|
"SELECT 0, 'C', '直播线路管理'",
|
|
"VALUES (0, 'C', '直播线路管理'",
|
|
)
|
|
for needle in forbidden_menu_fallbacks:
|
|
assert needle not in menu_sql, f"{MENU_SQL} must not contain top-level fallback: {needle}"
|
|
|
|
assert (
|
|
"FROM `la_system_menu` parent" in menu_sql
|
|
and "parent.`perms` = 'match.match/lists'" in menu_sql
|
|
), f"{MENU_SQL} must bind the match live menu to parent match.match/lists explicitly"
|
|
assert (
|
|
"SELECT parent.`id`, 'C', '直播线路管理'" in menu_sql
|
|
), f"{MENU_SQL} must insert the list menu only when the parent match.match/lists record exists"
|
|
assert (
|
|
"WHERE parent.`perms` = 'match.match/lists' AND NOT EXISTS (" in normalized_first_insert
|
|
), f"{MENU_SQL} first insert must combine parent binding with AND NOT EXISTS in a single WHERE predicate"
|
|
assert (
|
|
"WHERE parent.`perms` = 'match.match/lists' WHERE NOT EXISTS (" not in normalized_first_insert
|
|
), f"{MENU_SQL} first insert must not contain a second standalone WHERE NOT EXISTS after the parent WHERE"
|
|
assert normalized_first_insert.count("WHERE") == 2, (
|
|
f"{MENU_SQL} first insert should only have the outer WHERE plus the nested WHERE inside NOT EXISTS"
|
|
)
|
|
assert (
|
|
"SELECT (SELECT `id` FROM `la_system_menu` WHERE `perms` = 'match.matchLive/lists' LIMIT 1)" in normalized_menu_sql
|
|
), f"{MENU_SQL} child permissions must bind to the inserted match.matchLive/lists menu"
|
|
|
|
print("match live admin static checks passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|