- 新增 apps/server/test/support/{pg,engine}.ts:测试库地址、可用性探测、schema 咨询锁、
建 schema + 加锁迁移 + 删除、可选去重的假引擎;6 份 harness 改用它(acquisition-organize 与
file-manager 原先迁移不加锁,并行下有冲突隐患,一并修正)
- sse-pg 起子进程,并行下子进程启动超时,归入 serial
- 修正 b61335e 挪动 / 改名测试后遗留的 import 排序与格式(当时 lint 检查经管道被吞掉退出码)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
93 lines
5.0 KiB
TypeScript
93 lines
5.0 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import { BrowserService, BrowserServiceError, ENV_PROVIDER_NAME } from "../../src/modules/browser/service.ts";
|
|
import type { ServerContext } from "../../src/modules/types.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);
|
|
});
|
|
});
|