test: 测试按行为命名、server 测试统一到 test/、PG 测试分组并行
- server 的 src/modules/*/__tests__ 挪到 test/<模块>/;e2e 去掉 eN- 前缀, w5a2 / v2 / search2 / *-dh1 / *-md1 / *-md2、web 的 agui3 / search2、domain 的 id2 / id3 改为按行为命名 - vitest 分三组:unit、pg(真实 PG + 假引擎,文件并行,最多 6 个)、serial(真实引擎 / worker / e2e,串行); pg 组用例超时放宽到 60s(迁移类用例并行时排队等 schema 锁)。server 测试 615s → 约 350s - CLAUDE.md 补测试约定;待办补 harness 去重与 continuous-series 偶发超时(疑似 Slot 评估竞争) - ADR-004 补充:维持 Deno,记录实际只用到 --allow-run 白名单 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b343dddbf6
commit
b61335eaf6
75 files changed
+159
-115
No files matched your search
@@ -0,0 +1,147 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { AuthEnv, Principal, SessionVar } from "../../src/http/auth.ts";
|
||||
import { ApiException, errorBody } from "../../src/http/errors.ts";
|
||||
import {
|
||||
basicAuth,
|
||||
type DesktopDeps,
|
||||
type DesktopRole,
|
||||
deriveDesktopPasswords,
|
||||
desktopUpstream,
|
||||
registerDesktopRoutes,
|
||||
sameOrigin,
|
||||
} from "../../src/modules/desktop/desktop.ts";
|
||||
|
||||
// 桌面模式反代:口令派生与容器 init 脚本一致、角色 → 口令注入、只接受控制台会话、响应头与路径约束。
|
||||
|
||||
const SID = "7d3c1a52-9b0e-4b7a-8a51-0c2f7f7f0a11";
|
||||
const KEY = "p0key";
|
||||
|
||||
function harness(
|
||||
opts: { principal?: Partial<Principal>; role?: DesktopRole; check?: DesktopDeps["checkSession"] } = {},
|
||||
) {
|
||||
const calls: { url: string; headers: Headers; method: string }[] = [];
|
||||
const app = new OpenAPIHono<AuthEnv>();
|
||||
app.onError((err, c) => {
|
||||
if (err instanceof ApiException) return c.json(errorBody(err.code, err.message), err.status as 400);
|
||||
throw err;
|
||||
});
|
||||
app.use("*", async (c, next) => {
|
||||
const p: Principal = { kind: "admin_session", id: "u1", name: "admin", scopes: ["admin"], ...opts.principal };
|
||||
c.set("principal", p);
|
||||
c.set("session", p.kind === "admin_session" ? ({ id: "s1" } as SessionVar) : undefined);
|
||||
await next();
|
||||
});
|
||||
registerDesktopRoutes(app, {
|
||||
upstream: "http://camofox:3000",
|
||||
accessKey: KEY,
|
||||
checkSession:
|
||||
opts.check ??
|
||||
(async (_id, holder) => {
|
||||
expect(holder).toBe("admin_session:admin");
|
||||
return { ok: true, role: opts.role ?? "viewer", leaseExpiresAt: null };
|
||||
}),
|
||||
consoleSessionValid: async () => true,
|
||||
probeTtlMs: 0,
|
||||
fetch: (async (input: string | URL | Request, init?: RequestInit) => {
|
||||
const url = String(input);
|
||||
calls.push({ url, headers: new Headers(init?.headers), method: init?.method ?? "GET" });
|
||||
if (url.endsWith("/api/secret-401"))
|
||||
return new Response("no", { status: 401, headers: { "www-authenticate": "Basic" } });
|
||||
return new Response("ok", {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/html", "www-authenticate": "Basic", "x-frame-options": "DENY" },
|
||||
});
|
||||
}) as typeof fetch,
|
||||
});
|
||||
const root = new OpenAPIHono<AuthEnv>();
|
||||
root.onError((err, c) => {
|
||||
if (err instanceof ApiException) return c.json(errorBody(err.code, err.message), err.status as 400);
|
||||
throw err;
|
||||
});
|
||||
root.route("/api/v1", app);
|
||||
return { app: root, calls };
|
||||
}
|
||||
|
||||
describe("桌面口令与上游地址", () => {
|
||||
it("口令与容器 init-umt-desktop 的 openssl HMAC 派生一致", () => {
|
||||
// printf '%s' umt-desktop/control | openssl dgst -sha256 -hmac p0key -r | cut -c1-48
|
||||
const p = deriveDesktopPasswords(KEY);
|
||||
expect(p.controller).toBe("0b29251f95456b1e4612e3f4c45c2e353fd24e5ecbdf4959");
|
||||
expect(p.viewer).toHaveLength(48);
|
||||
expect(p.viewer).not.toBe(p.controller);
|
||||
});
|
||||
|
||||
it("缺省由浏览器服务地址推出(端口换 3000),可显式覆盖", () => {
|
||||
expect(desktopUpstream({ providerUrl: "http://camofox:9377" })).toBe("http://camofox:3000");
|
||||
expect(desktopUpstream({ providerUrl: "http://camofox:9377", desktopUrl: "http://desk:3000/" })).toBe(
|
||||
"http://desk:3000",
|
||||
);
|
||||
expect(desktopUpstream({})).toBeNull();
|
||||
});
|
||||
|
||||
it("WebSocket 只接受同源页面", () => {
|
||||
const req = (origin: string | null, host = "media.example.com") =>
|
||||
new Request("https://media.example.com/x", { headers: { host, ...(origin ? { origin } : {}) } });
|
||||
expect(sameOrigin(req("https://media.example.com"))).toBe(true);
|
||||
expect(sameOrigin(req("https://evil.example"))).toBe(false);
|
||||
expect(sameOrigin(req(null))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("桌面反代", () => {
|
||||
it("外部 API Key 不能用桌面模式", async () => {
|
||||
const { app } = harness({ principal: { kind: "api_key" } });
|
||||
const r = await app.request(`/api/v1/browser/desktop/${SID}/`);
|
||||
expect(r.status).toBe(403);
|
||||
});
|
||||
|
||||
it("状态:按租约给角色,并返回 iframe 地址", async () => {
|
||||
const { app } = harness({ role: "controller" });
|
||||
const r = await app.request(`/api/v1/browser/desktop/${SID}/_status`);
|
||||
const body = (await r.json()) as { data: { available: boolean; role: string; path: string } };
|
||||
expect(body.data).toMatchObject({ available: true, role: "controller", path: `/api/v1/browser/desktop/${SID}/` });
|
||||
});
|
||||
|
||||
it("静态资源:不带口令、不转发 cookie;响应只允许本站嵌入,不外露认证质询", async () => {
|
||||
const { app, calls } = harness();
|
||||
const r = await app.request(`/api/v1/browser/desktop/${SID}/assets/index.js?v=1`, {
|
||||
headers: { cookie: "umt_session=secret" },
|
||||
});
|
||||
expect(r.status).toBe(200);
|
||||
expect(calls.at(-1)?.url).toBe("http://camofox:3000/assets/index.js?v=1");
|
||||
expect(calls.at(-1)?.headers.get("authorization")).toBeNull();
|
||||
expect(calls.at(-1)?.headers.get("cookie")).toBeNull();
|
||||
expect(r.headers.get("content-security-policy")).toBe("frame-ancestors 'self'");
|
||||
expect(r.headers.get("x-frame-options")).toBe("SAMEORIGIN");
|
||||
expect(r.headers.get("www-authenticate")).toBeNull();
|
||||
});
|
||||
|
||||
it("Selkies 接口:只读 / 控制口令按角色注入", async () => {
|
||||
const pw = deriveDesktopPasswords(KEY);
|
||||
for (const role of ["viewer", "controller"] as const) {
|
||||
const { app, calls } = harness({ role });
|
||||
await app.request(`/api/v1/browser/desktop/${SID}/api/websockets`);
|
||||
expect(calls.at(-1)?.headers.get("authorization")).toBe(basicAuth(pw[role]));
|
||||
}
|
||||
});
|
||||
|
||||
it("上游 401 不透传质询,报口令不匹配", async () => {
|
||||
const { app } = harness();
|
||||
const r = await app.request(`/api/v1/browser/desktop/${SID}/api/secret-401`);
|
||||
expect(r.status).toBe(502);
|
||||
expect(r.headers.get("www-authenticate")).toBeNull();
|
||||
});
|
||||
|
||||
it("会话不属于自带浏览器服务 → 409;非法会话 id → 404;WebSocket 跨源 → 403", async () => {
|
||||
const { app } = harness({
|
||||
check: async () => ({ ok: false, code: "unsupported", message: "只有部署自带的浏览器服务支持桌面模式" }),
|
||||
});
|
||||
expect((await app.request(`/api/v1/browser/desktop/${SID}/api/websockets`)).status).toBe(409);
|
||||
expect((await app.request("/api/v1/browser/desktop/not-a-uuid/")).status).toBe(404);
|
||||
const ws = await harness().app.request(`/api/v1/browser/desktop/${SID}/api/websockets`, {
|
||||
headers: { upgrade: "websocket", origin: "https://evil.example", host: "media.example.com" },
|
||||
});
|
||||
expect(ws.status).toBe(403);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user