Files
ykxmz/_tests/test_v230_new_commands.py
xmz7891 2061f9525f 2.3.0:手表界面精简 + 播放器 + 鼠标重做
手表端
- 功能页从 10 个杂项精简到 7 个(屏幕/鼠标/媒体/文件/电脑/语音/电源)
- 删掉:游戏封锁、待办、掷骰子、抽奖、壁纸、显示器开关、定时关机、
  剪贴板历史、性能/磁盘/电量、文件夹浏览、教室限制等
- 改成显式列按钮,不再把电脑端所有功能组无脑铺到表盘
- 远程鼠标控制重做:滑动区 + 左键/滚轮/右键三按钮
  滚轮点按=中键、上下滑=滚动
- 新增视频/音乐播放器(手表本地播放):进度拖动、左右滑快进退10秒、
  点画面播放暂停、上一/下一、播完自动下一首

电脑端
- 新增 kill_all(结束所有进程),白名单保护系统关键进程和自身
- 新增 media_list / media_pull(播放器数据源)
  安全边界:只允许媒体目录,用 relative_to 判断,防同前缀兄弟目录绕过

中转站
- known_devices 从 ID 集合改成「设备ID -> 配对码」映射
- 修复:管理员改码后关机超过 6 小时,改码会被超时清理冲掉
- 老格式 state 文件平滑升级
2026-10-18 11:39:14 +08:00

130 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""2.3.0 新命令的单元测试:media_list / media_pull / kill_all。
重点验**安全边界**,不是"能跑就行":
* `media_pull` 必须拒绝媒体目录之外的文件(否则就是个远程任意文件读取器)
* 同前缀的兄弟目录(`音乐-evil`)不能被误判成子目录
* `kill_all` 必须保护自己、父进程链、系统关键进程
"""
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "windows"))
passed = 0
failed = 0
def check(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" [PASS] " + name)
else:
failed += 1
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
return ok
def main() -> int:
print("=" * 62)
print(" 2.3.0 新命令测试")
print("=" * 62)
# ---------- 1. 静态检查:实现存在且关键点齐 ----��------
print()
print("一、代码存在性")
server = (ROOT / "windows" / "lanremote" / "server.py").read_text(
encoding="utf-8")
features = (ROOT / "windows" / "lanremote" / "features.py").read_text(
encoding="utf-8")
check("server.py 有 _media_list", "def _media_list(self)" in server)
check("server.py 有 _media_pull", "def _media_pull(self" in server)
check("路由 media_list", 'cmd == "media_list"' in server)
check("路由 media_pull", 'cmd == "media_pull"' in server)
check("features.py 有 _do_kill_all", "def _do_kill_all(self" in features)
check("注册了 kill_all 功能", 'add("kill_all"' in features)
# ---------- 2. 安全边界:只允许媒体目录 ----------
print()
print("二、media_pull 的安全边界")
check("用 relative_to 判断(不是字符串前缀)",
"wanted.relative_to(root)" in server,
"字符串前缀会被「音乐-evil」这种同前缀兄弟目录绕过")
check("有「不在允许的媒体目录里」的拒绝",
"不在允许的媒体目录里" in server)
check("有文件大小上限(512MB)", "512 * 1024 * 1024" in server)
check("拒绝空文件", "文件是空的" in server)
# ---------- 3. kill_all 的保护名单 ----------
print()
print("三、kill_all 的保护名单")
for name in ("csrss.exe", "winlogon.exe", "lsass.exe", "svchost.exe",
"explorer.exe", "python.exe", "ykxmz.exe"):
check("保护 %s" % name, '"%s"' % name in features,
"少了它一按就可能蓝屏/自断后路")
check("保护自己的 PID", "_protected_pids" in features)
check("追父进程链", "ParentProcessId" in features)
# ---------- 4. 真的跑一次 _media_list(用临时目录)----------
print()
print("四、_media_list 实跑")
try:
import os
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
from lanremote import server as srv_mod
# 造一个假的 store(不需要 Config —— 那个类不存在,之前是我写错了)
tmp = Path(tempfile.mkdtemp(prefix="media_"))
music = tmp / "Music"
music.mkdir()
(music / "a.mp3").write_bytes(b"x" * 100)
(music / "b.mp4").write_bytes(b"y" * 200)
(music / "c.txt").write_text("not media", encoding="utf-8")
class FakeStore:
def received_dir(self):
return tmp
# 直接借 Server 的方法:构造太麻烦,用 types 绑定
import types
obj = types.SimpleNamespace()
obj.store = FakeStore()
obj.AUDIO_EXT = srv_mod.RemoteServer.AUDIO_EXT
obj.VIDEO_EXT = srv_mod.RemoteServer.VIDEO_EXT
obj.MEDIA_LIST_LIMIT = srv_mod.RemoteServer.MEDIA_LIST_LIMIT
obj.MEDIA_SUBDIRS = ("Music", "Videos")
obj._media_roots = types.MethodType(srv_mod.RemoteServer._media_roots, obj)
# Path.home() 是真实用户目录,这里把 _media_roots 换成只返回 music。
# ⚠ 必须返回**装文件的那一层**,不是它的父目录 ——
# _media_list 只扫根目录下的文件,不递归子目录。
# (第一版写成了 tmp,扫到的是 Music 这个文件夹本身,全被跳过。)
obj._media_roots = lambda: [music.resolve()]
result = srv_mod.RemoteServer._media_list(obj)
names = [f["name"] for f in result["files"]]
check("列出了 a.mp3", "a.mp3" in names)
check("列出了 b.mp4", "b.mp4" in names)
check("**排除了 c.txt**", "c.txt" not in names,
"非媒体扩展名不该出现")
kinds = {f["name"]: f["kind"] for f in result["files"]}
check("a.mp3 标成 audio", kinds.get("a.mp3") == "audio")
check("b.mp4 标成 video", kinds.get("b.mp4") == "video")
except Exception as exc: # noqa: BLE001
check("_media_list 实跑", False, "%s: %s" % (type(exc).__name__, exc))
print()
print("=" * 62)
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
print("=" * 62)
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())