Files
UHD_Media_Tool/apps/server/test/browser/browser-provider-config.test.ts
T
rootandClaude Opus 5.5 b61335eaf6 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]>
2026-10-02 16:54:04 +08:00

93 lines
5.0 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { ServerContext } from "../../src/modules/types.ts";
import { BrowserService, BrowserServiceError, ENV_PROVIDER_NAME } from "../../src/modules/browser/service.ts";
import { ACCESS_KEY, type BrowserHarness, createBrowserHarness, type Loose, pgAvailable } from "./harness.ts";
// BR-1:浏览器服务登记 —— 环境变量(BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY / BROWSER_ENABLED → camofox-env)
// 与设置页端点(POST / PATCH /browser/providers、POST /browser/providers/{id}/probe)。
const up = await pgAvailable();
describe.skipIf(!up)("浏览器服务登记", () => {
let h: BrowserHarness;
const withEnv = (env: Record<string, unknown>) =>
new BrowserService({ ...h.ctx, config: { ...(h.ctx.config as object), ...env } } as ServerContext, {});
beforeAll(async () => {
h = await createBrowserHarness("provcfg");
}, 120_000);
afterAll(async () => {
await h?.close();
});
it("BROWSER_PROVIDER_URL:登记 camofox-env,凭据只在进程内;探测带 Bearer;地址/凭据不能在设置页改", async () => {
const svc = withEnv({ BROWSER_PROVIDER_URL: h.mock.url, BROWSER_ACCESS_KEY: ACCESS_KEY, BROWSER_ENABLED: true });
const list = await svc.listProviders(true);
const env = list.find((p) => p.name === ENV_PROVIDER_NAME)!;
expect(env).toMatchObject({ managedBy: "env", hasAccessKey: true, secretRef: null, enabled: true });
expect(env.health?.status).toBe("ok");
const row = await h.db
.selectFrom("browser_provider")
.selectAll()
.where("name", "=", ENV_PROVIDER_NAME)
.executeTakeFirstOrThrow();
expect(JSON.stringify(row)).not.toContain(ACCESS_KEY);
expect(h.mock.calls.filter((c) => c.path === "/openapi.json").at(-1)?.headers.authorization).toBe(
`Bearer ${ACCESS_KEY}`,
);
await expect(svc.updateProvider(env.id, { baseUrl: "http://x:1" })).rejects.toBeInstanceOf(BrowserServiceError);
// 启用可以改
expect((await svc.updateProvider(env.id, { enabled: false })).enabled).toBe(false);
});
it("BROWSER_ENABLED=false / 去掉地址:camofox-env 停用,其它服务不受影响", async () => {
await withEnv({ BROWSER_PROVIDER_URL: h.mock.url, BROWSER_ENABLED: false }).syncEnvProvider();
const off = await h.db
.selectFrom("browser_provider")
.selectAll()
.where("name", "=", ENV_PROVIDER_NAME)
.executeTakeFirstOrThrow();
expect(off.enabled).toBe(false);
await withEnv({ BROWSER_PROVIDER_URL: h.mock.url, BROWSER_ENABLED: true }).syncEnvProvider();
await withEnv({}).syncEnvProvider();
const rows = await h.db.selectFrom("browser_provider").select(["name", "enabled"]).orderBy("name").execute();
expect(rows).toEqual([
{ name: "camofox", enabled: true },
{ name: ENV_PROVIDER_NAME, enabled: false },
]);
});
it("设置页:登记 / 改凭据 / 测试连通;凭据明文不出 API,名称 camofox-env 保留", async () => {
const bad = await h.req("/browser/providers", { body: { name: ENV_PROVIDER_NAME, baseUrl: h.mock.url } });
expect(bad.status).toBe(400);
const c = await h.req("/browser/providers", {
body: { name: "my-camofox", baseUrl: h.mock.url, accessKey: "wrong-key", enabled: true },
});
expect(c.status).toBe(200);
const id = c.body.data.id as string;
expect(c.body.data).toMatchObject({ managedBy: null, hasAccessKey: true, enabled: true });
expect(JSON.stringify(c.body)).not.toContain("wrong-key");
const dup = await h.req("/browser/providers", { body: { name: "my-camofox", baseUrl: h.mock.url } });
expect(dup.status).toBe(409);
// 错误凭据:/health 免认证是好的,但 /openapi.json 401 → 标离线并注明 auth
const p1 = await h.req(`/browser/providers/${id}/probe`, { body: {} });
expect(p1.body.data).toMatchObject({
healthStatus: "offline",
health: { status: "down", detail: { error: "auth" } },
});
const u = await h.req(`/browser/providers/${id}`, { method: "PATCH", body: { accessKey: ACCESS_KEY } });
expect(u.status).toBe(200);
const p2 = await h.req(`/browser/providers/${id}/probe`, { body: {} });
expect(p2.status).toBe(200);
expect((p2.body.data as Loose).capabilities.upstreamVersion).toBe("1.17.0-mock");
// 空串 = 保持原凭据(FIX-8 只写凭证统一语义)
const kept = await h.req(`/browser/providers/${id}`, { method: "PATCH", body: { accessKey: "", enabled: true } });
expect(kept.body.data).toMatchObject({ hasAccessKey: true, secretRef: u.body.data.secretRef });
const cleared = await h.req(`/browser/providers/${id}`, { method: "PATCH", body: { accessKey: null } });
expect(cleared.body.data).toMatchObject({ hasAccessKey: false, secretRef: null });
expect((await h.req(`/browser/providers/${id}`, { method: "DELETE" })).status).toBe(200);
const env = (await h.req("/browser/providers")).body.data.items.find((p: Loose) => p.name === ENV_PROVIDER_NAME);
expect((await h.req(`/browser/providers/${env.id}`, { method: "DELETE" })).status).toBe(409);
});
});