Files
host ebc5624893 console: React 生产级重写(左列表 + 中对话 + 右详情)
旧单页 console.html 换成 Vite + React + TypeScript 单文件构建产物
(309525 字节,sha 90345ec8…,重建字节一致),源码在
cloud/console-react/,改页面只改 src 再 npm run build:console,
产物不手改。布局:左侧列表(模块导航 + 任务列表)、中间标准对话
(实时链路时间线 + 继续会话聊天框,共用 Chat 组件)、右侧详情
(元数据、操作、评审、审批、协调子任务、子代理、线程)。
API 与后端语义未改,key 只存浏览器 localStorage。
6 个 console 标记单测随实现改名更新(意图不变:壳确实调了
这些接口、turn 信息仍在详情里、旧 goal 输入仍不存在);
P1 标记单测未动且通过。
2026-10-29 06:32:38 +08:00

118 lines
4.9 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.
"""命令台 /console 单测:只验页面壳与路由,不跑浏览器 JS。
页面本身不走鉴权(只是个壳),数据一律凭 X-API-Key 调接口,
所以 /console 无 key 也应 200 出 HTML;真正的鉴权由各 API
端点负责(已有单测覆盖)。
"""
import http.client
import os
import sys
import tempfile
import threading
import unittest
from pathlib import Path
try:
from cloud.auth import AuthStore
from cloud.server import create_server
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.server import create_server
class ConsoleTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self.server = create_server(self.db_path, "127.0.0.1", 0)
self.port = self.server.server_address[1]
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _get(self, path, key=None):
conn = http.client.HTTPConnection(
"127.0.0.1", self.port, timeout=10)
headers = {}
if key is not None:
headers["X-API-Key"] = key
try:
conn.request("GET", path, headers=headers)
resp = conn.getresponse()
return resp.status, dict(resp.getheaders()), resp.read()
finally:
conn.close()
def test_console_serves_html_without_key(self):
status, headers, body = self._get("/console")
self.assertEqual(status, 200)
self.assertIn("text/html", headers.get("Content-Type", ""))
text = body.decode("utf-8")
self.assertIn("Codex 命令台", text)
# 壳必须引用它实际调的接口,防 JS 与服务端路由漂移。
# React 版(2026-10-29 起):旧单页的 agentBox / threadBox /
# THREAD_MUTATION_STATUSES / skillList / appList / mcpList
# id 与 /thread/<动作> 字面路径已不存在,改为 React 的
# taskAgents / threadText / threadAction + 动作名 /
# nav-skills;断言意图不变(壳确实调了这些接口),
# 只换成构建产物里实际存在的标记。
for marker in ("/health", "/projects", "/tasks", "/workers",
"X-API-Key", "taskAgents", "/agents",
"peerMessage", "/messages",
"threadText", "threadGateNote", "threadList",
"threadAction", "/thread", "/threads",
"rename", "archive", "unarchive", "compact",
"nav-skills", "mcpOauthBox",
"/skills", "/apps", "/mcp-servers",
"/mcp-servers/reload", "/oauth-login"):
self.assertIn(marker, text)
def test_console_trailing_slash_same_page(self):
status, _headers, body = self._get("/console/")
self.assertEqual(status, 200)
self.assertIn("Codex 命令台", body.decode("utf-8"))
def test_console_subpath_not_routed(self):
# 未知子路径先过鉴权(无 key 时 401),有 key 时才落到 404;
# 绝不能把 /console/extra 也当成壳 serve 出去。
status, _headers, _body = self._get("/console/extra")
self.assertEqual(status, 401)
status, _headers, _body = self._get("/console/extra", self.owner_key)
self.assertEqual(status, 404)
def test_console_does_not_leak_data_without_key(self):
# 壳里只有静态文本,没有任何任务/项目数据(数据须凭 key 调 API)。
_status, _headers, body = self._get("/console")
text = body.decode("utf-8")
self.assertNotIn(self.owner_id, text)
def test_console_init_loads_relay_models(self):
# 回访用户 key 已在 localStorage 里:初始化序列必须含
# loadRelayModels 调用,否则模型下拉永远只有默认项
# (2026-10-27 实测:此前只在点“保存”时调一次)。
# React 版(2026-10-29 起):旧单页的 setKeyState/loadHealth
# 序列已不存在,初始化在 useConsoleStore 的 useEffect 里,
# 有 key 时调 loadRelayModels;断言意图不变,只换成
# React 构建产物里实际存在的标记。
_status, _headers, body = self._get("/console")
text = body.decode("utf-8")
self.assertIn("loadRelayModels", text)
self.assertIn("/relay/models", text)
if __name__ == "__main__":
unittest.main()