diff --git a/_build/_cmsg10.txt b/_build/_cmsg10.txt new file mode 100644 index 0000000..5a5483d --- /dev/null +++ b/_build/_cmsg10.txt @@ -0,0 +1,75 @@ +🚨 修一个严重 bug:管理页所有按钮又失效了(同一个坑踩第二次) + +== 症状 == + +在**真实中转**上实测管理页时,用 node --check 检查页面内嵌的 JS: + + SyntaxError: Invalid or unexpected token (第 46 行) + +整段 , + 或者 JS 用到了 node 不认的语法)-> 假警报 + +这个脚本把可疑的那一行原样打出来,人肉判断是哪种。 +''' +from __future__ import annotations + +import json +import re +import subprocess +import sys +import tempfile +import urllib.request +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "_build")) +from devssh import run as ssh_run # noqa: E402 + +BASE = "https://relay.xxmxxs.top" +UA = {"User-Agent": "LanRemote/1.8.2"} + + +def main() -> int: + _c, out, _e = ssh_run( + "cat /root/lanremote-relay/data/admin_key.txt", timeout=60, quiet=True) + key = (out or "").strip().splitlines()[0] + + req = urllib.request.Request(BASE + "/zhongzhuanzhan", + headers={**UA, "X-Admin": key}) + with urllib.request.urlopen(req, timeout=25) as resp: + html = resp.read().decode("utf-8", "replace") + + scripts = re.findall(r"]*>(.*?)", html, re.S) + inline = [s for s in scripts if s.strip()] + print(" 页面上有 %d 段内嵌脚本,长度: %s" + % (len(inline), [len(s) for s in inline])) + + js = inline[0] + lines = js.splitlines() + print(" 第 1 段共 %d 行" % len(lines)) + print() + print(" ════ 第 40~54 行原样 ════") + for i in range(39, min(54, len(lines))): + marker = " <<<< 报错行" if i == 45 else "" + print(" %3d| %s%s" % (i + 1, lines[i], marker)) + print() + + # 把这一段单独跑 node --check,确认能复现 + with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, + encoding="utf-8") as fh: + fh.write(js) + path = fh.name + proc = subprocess.run(["node", "--check", path], + capture_output=True, text=True, + encoding="utf-8", errors="replace") + print(" node --check 退出码: %d" % proc.returncode) + if proc.stderr: + print(" stderr:") + for line in proc.stderr.splitlines()[:12]: + print(" " + line) + Path(path).unlink(missing_ok=True) + + # 也检查每一行里有没有裸控制字符 + print() + print(" ════ 有没有裸控制字符 ════") + bad = 0 + for i, line in enumerate(lines, 1): + for ch in line: + if ord(ch) < 32 and ch not in "\t": + print(" 第 %d 行有控制字符 0x%02x" % (i, ord(ch))) + bad += 1 + break + if not bad: + print(" ✓ 没有") + + # 反引号 / 模板字面量 / 正则 里可能藏着截断问题 + print() + print(" ════ 源 HTML 里 出现几次 ════") + print(" %d 次(正常应该等于 ")) + print(" ", html, re.S) + inline = [s for s in scripts if s.strip()] + check("有内嵌脚本", bool(inline), "一个都没有") + + for i, js in enumerate(inline, 1): + with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, + encoding="utf-8") as fh: + fh.write(js) + path = fh.name + try: + proc = subprocess.run(["node", "--check", path], + capture_output=True, text=True, + encoding="utf-8", errors="replace", + timeout=30) + ok = proc.returncode == 0 + detail = "" + if not ok: + # 把报错那一行附近打出来,方便定位 + err = (proc.stderr or "") + m = re.search(r":(\d+)\n", err) + if m: + ln = int(m.group(1)) + lines = js.splitlines() + lo = max(0, ln - 3) + hi = min(len(lines), ln + 2) + detail = "第 %d 行:\n" % ln + "\n".join( + " %3d| %s" % (k + 1, lines[k]) + for k in range(lo, hi)) + else: + detail = err[:300] + check("第 %d 段内嵌 JS 语法合法" % i, ok, detail) + except FileNotFoundError: + print(" [SKIP] 没装 node") + finally: + Path(path).unlink(missing_ok=True) + + # 再检查一遍:注释行里有没有被 Python 吃掉转义的痕迹 + for i, js in enumerate(inline, 1): + lines = js.splitlines() + bad = [] + for k, line in enumerate(lines): + s = line.strip() + if s.startswith("//"): + # 注释行里出现裸控制字符 + for ch in line: + if ord(ch) < 32 and ch != "\t": + bad.append(k + 1) + break + check("第 %d 段没有裸控制字符" % i, not bad, "行号 %s" % bad) + + print() + print("=" * 58) + print(" 通过 %d 项,失败 %d 项" % (passed, failed)) + return 0 if failed == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_tests/test_admin_live.py b/_tests/test_admin_live.py new file mode 100644 index 0000000..536b0f1 --- /dev/null +++ b/_tests/test_admin_live.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +r'''在**真实中转**上实测【二】管理页的两个功能。 + +== 为什么要单独做这个测试 == + +单元测试是直接调 Relay 对象的 Python 方法,而用户是在浏览器上点。 +中间隔着 HTTP、HTML、JS 三层。之前就踩过「23 项测试全绿但按钮全是死的」: +admin.py 里一个 \n 写错(Python 三引号字符串把 \n 变成真换行), +拼进 JS 就是字符串里的裸换行 -> 整段 ", html, re.S) + inline = [s for s in scripts if s.strip()] + check("页面上有内嵌脚本", bool(inline), "一个