mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 16:00:43 +00:00
修管理页测试的引号敏感 + 补 netPanel id
重做管理页时把 HTML 从 class=pick(无引号)改成了 class="pick"(标准写法),但测试里写死了无引号形式, 于是 5 个断言集体误报失败。 HTML 两种写法都合法,测试不该关心这个 —— 给两个测试加了 _has(),带不带引号都认。 另外给新管理页的体检卡补回 id=netPanel(语义合理, 也顺手让测试钩子还在)。 同时删掉 _build/_missing_tail.py(恢复事故时的临时文件, 静态检查把它当成真模块查,报了 8 个 undefined name)。 验证:管理页网络体检 15/15、中转批量删除 24/24、 全局 32 个阶段全部通过。
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
修管理页测试的引号敏感 + 补 netPanel id
|
||||||
|
|
||||||
|
重做管理页时把 HTML 从 class=pick(无引号)改成了
|
||||||
|
class="pick"(标准写法),但测试里写死了无引号形式,
|
||||||
|
于是 5 个断言集体误报失败。
|
||||||
|
|
||||||
|
HTML 两种写法都合法,测试不该关心这个 ——
|
||||||
|
给两个测试加了 _has(),带不带引号都认。
|
||||||
|
|
||||||
|
另外给新管理页的体检卡补回 id=netPanel(语义合理,
|
||||||
|
也顺手让测试钩子还在)。
|
||||||
|
|
||||||
|
同时删掉 _build/_missing_tail.py(恢复事故时的临时文件,
|
||||||
|
静态检查把它当成真模块查,报了 8 个 undefined name)。
|
||||||
|
|
||||||
|
验证:管理页网络体检 15/15、中转批量删除 24/24、
|
||||||
|
全局 32 个阶段全部通过。
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
def _esc(text) -> str:
|
|
||||||
return (str(text).replace("&", "&").replace("<", "<")
|
|
||||||
.replace(">", ">").replace('"', """))
|
|
||||||
|
|
||||||
|
|
||||||
def _kb(value: int) -> str:
|
|
||||||
if value < 1024:
|
|
||||||
return f"{value} B"
|
|
||||||
if value < 1024 * 1024:
|
|
||||||
return f"{value / 1024:.1f} KB"
|
|
||||||
return f"{value / 1024 / 1024:.1f} MB"
|
|
||||||
|
|
||||||
|
|
||||||
def _uptime(seconds: int) -> str:
|
|
||||||
days, rest = divmod(int(seconds), 86400)
|
|
||||||
hours, rest = divmod(rest, 3600)
|
|
||||||
minutes = rest // 60
|
|
||||||
if days:
|
|
||||||
return f"{days} 天 {hours} 小时"
|
|
||||||
if hours:
|
|
||||||
return f"{hours} 小时 {minutes} 分"
|
|
||||||
return f"{minutes} 分"
|
|
||||||
|
|
||||||
|
|
||||||
# ======================================================================
|
|
||||||
# 启动
|
|
||||||
# ======================================================================
|
|
||||||
|
|
||||||
class Server(ThreadingHTTPServer):
|
|
||||||
daemon_threads = True
|
|
||||||
allow_reuse_address = True
|
|
||||||
request_queue_size = 64
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
try:
|
|
||||||
server = Server((BIND, PORT), Handler)
|
|
||||||
except OSError as exc:
|
|
||||||
print(f"监听 {BIND}:{PORT} 失败:{exc}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
log(f"中转服务器 v{VERSION} 启动,监听 {BIND}:{PORT}")
|
|
||||||
log(f"长轮询 {POLL_SECONDS:.0f} 秒 · 队列上限 "
|
|
||||||
f"{MAX_QUEUE_BYTES // 1024} KB · 数据目录 {DATA_DIR}")
|
|
||||||
key = admin_key()
|
|
||||||
log(f"管理页:http://<本机>:{PORT}/?key={key}")
|
|
||||||
log(f"(钥匙也在 {DATA_DIR / 'admin_key.txt'})")
|
|
||||||
|
|
||||||
try:
|
|
||||||
server.serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
log("收到中断,退出")
|
|
||||||
finally:
|
|
||||||
server.server_close()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''让管理页测试对"属性有没有引号"不敏感。
|
||||||
|
|
||||||
|
背景:
|
||||||
|
旧版管理页的 HTML 是 `class=pick`(无引号),
|
||||||
|
重做时改成了标准的 `class="pick"`(带引号)。
|
||||||
|
测试里写死了 `"class=pick" in html`,于是集体误报失败。
|
||||||
|
|
||||||
|
HTML 两种写法都合法,测试不该关心这个。
|
||||||
|
所以给测试加个 _has():两种写法都认。
|
||||||
|
'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
TESTS = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\_tests")
|
||||||
|
|
||||||
|
# 要改的测试文件和里面的断言
|
||||||
|
TARGETS = {
|
||||||
|
"test_admin_batch_delete.py": [
|
||||||
|
('check("有复选框(input.pick)", "class=pick" in html)',
|
||||||
|
'check("有复选框(input.pick)", _has(html, "class=pick"))'),
|
||||||
|
('check("有全选(pickAll)", "id=pickAll" in html)',
|
||||||
|
'check("有全选(pickAll)", _has(html, "id=pickAll"))'),
|
||||||
|
('check("有「删除选中」按钮", "id=delPicked" in html)',
|
||||||
|
'check("有「删除选中」按钮", _has(html, "id=delPicked"))'),
|
||||||
|
],
|
||||||
|
"test_admin_netcheck.py": [
|
||||||
|
('check("有网络体检面板", \'id=netPanel\' in html, "")',
|
||||||
|
'check("有网络体检面板", _has(html, "id=netPanel"), "")'),
|
||||||
|
('check("有「测一下」按钮", \'id=netBtn\' in html, "")',
|
||||||
|
'check("有「测一下」按钮", _has(html, "id=netBtn"), "")'),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# 插到 check() 定义附近
|
||||||
|
HELPER = '''
|
||||||
|
|
||||||
|
def _has(html: str, token: str) -> bool:
|
||||||
|
"""判断 html 里有没有这个属性,**带不带引号都算**。
|
||||||
|
|
||||||
|
旧版页面写的是 class=pick(无引号),
|
||||||
|
重做后改成了标准的 class="pick"(带引号)。
|
||||||
|
测试不该因为引号差异就报失败 —— HTML 两种都合法。
|
||||||
|
"""
|
||||||
|
if token in html:
|
||||||
|
return True
|
||||||
|
# class=pick -> class="pick" / id=netBtn -> id="netBtn"
|
||||||
|
match = re.match(r"^([A-Za-z-]+)=(.+)$", token)
|
||||||
|
if not match:
|
||||||
|
return False
|
||||||
|
attr, value = match.group(1), match.group(2)
|
||||||
|
return ('%s="%s"' % (attr, value)) in html
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_import_re(text: str) -> str:
|
||||||
|
if re.search(r"^import re$", text, re.M):
|
||||||
|
return text
|
||||||
|
# 插到第一个 import 之后
|
||||||
|
match = re.search(r"^(import \w+|from \w+ import .+)$", text, re.M)
|
||||||
|
if match:
|
||||||
|
return (text[:match.end()] + "\nimport re" + text[match.end():])
|
||||||
|
return "import re\n" + text
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
total = 0
|
||||||
|
for name, replacements in TARGETS.items():
|
||||||
|
path = TESTS / name
|
||||||
|
if not path.is_file():
|
||||||
|
print(" [跳过] %s 不存在" % name)
|
||||||
|
continue
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
if "_has(" in text and "def _has" in text:
|
||||||
|
print(" %s: 已经是新版" % name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
changed = 0
|
||||||
|
for old, new in replacements:
|
||||||
|
if old in text:
|
||||||
|
text = text.replace(old, new, 1)
|
||||||
|
changed += 1
|
||||||
|
if changed == 0:
|
||||||
|
print(" %s: 没找到要改的断言" % name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
text = ensure_import_re(text)
|
||||||
|
|
||||||
|
# 把 _has 插到 "def check(" 前面
|
||||||
|
marker = "def check("
|
||||||
|
index = text.find(marker)
|
||||||
|
if index < 0:
|
||||||
|
print(" [失败] %s 找不到 check()" % name)
|
||||||
|
continue
|
||||||
|
text = text[:index] + HELPER.strip() + "\n\n\n" + text[index:]
|
||||||
|
|
||||||
|
path.write_text(text, encoding="utf-8", newline="\n")
|
||||||
|
try:
|
||||||
|
ast.parse(text)
|
||||||
|
print(" %s: 改了 %d 处 + 加了 _has(),语法 OK" % (name, changed))
|
||||||
|
total += changed
|
||||||
|
except SyntaxError as exc:
|
||||||
|
print(" [失败] %s 行 %s: %s" % (name, exc.lineno, exc.msg))
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(" 共改 %d 处" % total)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
@@ -10,6 +10,7 @@
|
|||||||
6. 管理页上有复选框、全选、删除选中按钮
|
6. 管理页上有复选框、全选、删除选中按钮
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
import re
|
||||||
|
|
||||||
import http.cookiejar
|
import http.cookiejar
|
||||||
import json
|
import json
|
||||||
@@ -34,6 +35,23 @@ passed = 0
|
|||||||
failed = 0
|
failed = 0
|
||||||
|
|
||||||
|
|
||||||
|
def _has(html: str, token: str) -> bool:
|
||||||
|
"""判断 html 里有没有这个属性,**带不带引号都算**。
|
||||||
|
|
||||||
|
旧版页面写的是 class=pick(无引号),
|
||||||
|
重做后改成了标准的 class="pick"(带引号)。
|
||||||
|
测试不该因为引号差异就报失败 —— HTML 两种都合法。
|
||||||
|
"""
|
||||||
|
if token in html:
|
||||||
|
return True
|
||||||
|
# class=pick -> class="pick" / id=netBtn -> id="netBtn"
|
||||||
|
match = re.match(r"^([A-Za-z-]+)=(.+)$", token)
|
||||||
|
if not match:
|
||||||
|
return False
|
||||||
|
attr, value = match.group(1), match.group(2)
|
||||||
|
return ('%s="%s"' % (attr, value)) in html
|
||||||
|
|
||||||
|
|
||||||
def check(name, ok, detail=""):
|
def check(name, ok, detail=""):
|
||||||
global passed, failed
|
global passed, failed
|
||||||
if ok:
|
if ok:
|
||||||
@@ -120,9 +138,9 @@ def main() -> int:
|
|||||||
print("一、管理页上有批量删除的控件")
|
print("一、管理页上有批量删除的控件")
|
||||||
html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=_UA),
|
html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=_UA),
|
||||||
timeout=15).read().decode("utf-8")
|
timeout=15).read().decode("utf-8")
|
||||||
check("有复选框(input.pick)", "class=pick" in html)
|
check("有复选框(input.pick)", _has(html, "class=pick"))
|
||||||
check("有全选(pickAll)", "id=pickAll" in html)
|
check("有全选(pickAll)", _has(html, "id=pickAll"))
|
||||||
check("有「删除选中」按钮", "id=delPicked" in html)
|
check("有「删除选中」按钮", _has(html, "id=delPicked"))
|
||||||
check("调的是 delete_many 接口", "/v1/admin/delete_many" in html)
|
check("调的是 delete_many 接口", "/v1/admin/delete_many" in html)
|
||||||
check("复选框的 value 是 deviceId", 'value="pc-1"' in html, "")
|
check("复选框的 value 是 deviceId", 'value="pc-1"' in html, "")
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,23 @@ passed = 0
|
|||||||
failed = 0
|
failed = 0
|
||||||
|
|
||||||
|
|
||||||
|
def _has(html: str, token: str) -> bool:
|
||||||
|
"""判断 html 里有没有这个属性,**带不带引号都算**。
|
||||||
|
|
||||||
|
旧版页面写的是 class=pick(无引号),
|
||||||
|
重做后改成了标准的 class="pick"(带引号)。
|
||||||
|
测试不该因为引号差异就报失败 —— HTML 两种都合法。
|
||||||
|
"""
|
||||||
|
if token in html:
|
||||||
|
return True
|
||||||
|
# class=pick -> class="pick" / id=netBtn -> id="netBtn"
|
||||||
|
match = re.match(r"^([A-Za-z-]+)=(.+)$", token)
|
||||||
|
if not match:
|
||||||
|
return False
|
||||||
|
attr, value = match.group(1), match.group(2)
|
||||||
|
return ('%s="%s"' % (attr, value)) in html
|
||||||
|
|
||||||
|
|
||||||
def check(name, ok, detail=""):
|
def check(name, ok, detail=""):
|
||||||
global passed, failed
|
global passed, failed
|
||||||
if ok:
|
if ok:
|
||||||
@@ -127,8 +144,8 @@ def main() -> int:
|
|||||||
print("三、管理页上有这张卡片")
|
print("三、管理页上有这张卡片")
|
||||||
html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=UA),
|
html = opener.open(urllib.request.Request(BASE + "/zhongzhuanzhan", headers=UA),
|
||||||
timeout=10).read().decode("utf-8")
|
timeout=10).read().decode("utf-8")
|
||||||
check("有网络体检面板", 'id=netPanel' in html, "")
|
check("有网络体检面板", _has(html, "id=netPanel"), "")
|
||||||
check("有「测一下」按钮", 'id=netBtn' in html, "")
|
check("有「测一下」按钮", _has(html, "id=netBtn"), "")
|
||||||
check("JS 里调 netcheck 接口", "/v1/admin/netcheck" in html, "")
|
check("JS 里调 netcheck 接口", "/v1/admin/netcheck" in html, "")
|
||||||
check("会显示隧道连接数", "tunnelConnections" in html, "")
|
check("会显示隧道连接数", "tunnelConnections" in html, "")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -601,7 +601,7 @@ def render_admin(snap: dict, public: str, admin_path: str, codes: int,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
<div class="tool">
|
<div class="tool" id="netPanel">
|
||||||
<h4>网络体检</h4>
|
<h4>网络体检</h4>
|
||||||
<div class="d">看看这台机器是走局域网还是走隧道。</div>
|
<div class="d">看看这台机器是走局域网还是走隧道。</div>
|
||||||
<div class="out" id="netBody">还没测过。</div>
|
<div class="out" id="netBody">还没测过。</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user