Files
writing-agent/tests/preset-lab-api.test.ts
T
chenmoranzhi f3303038fe 新增预设工作台:编辑、版本树、结构检查、用例试跑放在同一页。
改预设要能一边写一边看装出来的 messages,还要多模型对照,所以把编辑与试跑拆成两个视图塞进一个页面;版本存完整快照、可分叉,用例全局共用一份、运行记录按预设存。顺带让读预设文件先剥 BOM,静态资源改成不缓存,免得改了脚本还要强刷。
2026-10-26 01:09:40 +08:00

147 lines
5.5 KiB
TypeScript

import { mkdtempSync, rmSync } from "node:fs";
import { Readable } from "node:stream";
import os from "node:os";
import path from "node:path";
import type { IncomingMessage, ServerResponse } from "node:http";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { savePreset } from "../src/preset/store.js";
import { handlePresetLabApi } from "../src/server/preset-lab-handlers.js";
import type { PresetPackage } from "../src/types/preset.js";
function preset(id = "lab"): PresetPackage {
return {
id,
name: "工作台预设",
source: "native",
prompts: [
{ id: "sys", name: "规则", enabled: true, role: "system", content: "\u4f60\u662f{{char}}", marker: false, sourceIdentifier: "sys" },
{ id: "chatHistory", name: "历史", enabled: true, role: "system", content: "", marker: true, sourceIdentifier: "chatHistory" },
],
promptOrder: [
{ promptId: "sys", enabled: true, orderIndex: 0 },
{ promptId: "chatHistory", enabled: true, orderIndex: 1 },
],
generation: {},
unsupported: [],
importedAt: new Date().toISOString(),
};
}
type Captured = { status: number; body: any };
async function call(
pathname: string,
init: { method?: string; body?: unknown; query?: string } = {},
): Promise<Captured> {
const raw = init.body === undefined ? "" : JSON.stringify(init.body);
const req = Readable.from(raw ? [Buffer.from(raw, "utf8")] : []) as unknown as IncomingMessage;
(req as any).method = init.method ?? "GET";
let status = 0;
let payload = "";
const res = {
writeHead(code: number) {
status = code;
},
end(chunk: string) {
payload = chunk ?? "";
},
} as unknown as ServerResponse;
const handled = await handlePresetLabApi(req, res, pathname, new URLSearchParams(init.query ?? ""));
expect(handled).toBe(true);
return { status, body: payload ? JSON.parse(payload) : null };
}
let dir = "";
beforeEach(() => {
dir = mkdtempSync(path.join(os.tmpdir(), "preset-lab-api-"));
process.env.WRITING_AGENT_DATA_DIR = dir;
savePreset(preset());
});
afterEach(() => {
delete process.env.WRITING_AGENT_DATA_DIR;
rmSync(dir, { recursive: true, force: true });
});
describe("预设工作台 API", () => {
it("非本模块路径不接管", async () => {
const req = Readable.from([]) as unknown as IncomingMessage;
(req as any).method = "GET";
const res = { writeHead() {}, end() {} } as unknown as ServerResponse;
expect(await handlePresetLabApi(req, res, "/api/settings/x", new URLSearchParams())).toBe(false);
});
it("列出预设", async () => {
const { status, body } = await call("/api/preset-lab/presets");
expect(status).toBe(200);
expect(body.presets.some((p: any) => p.id === "lab")).toBe(true);
});
it("读取单个预设与结构检查", async () => {
const one = await call("/api/preset-lab/preset", { query: "presetId=lab" });
expect(one.body.preset.id).toBe("lab");
const checked = await call("/api/preset-lab/check", { method: "POST", body: { preset: one.body.preset } });
expect(checked.body.issues.filter((i: any) => i.level === "error")).toHaveLength(0);
});
it("提交版本后能读回版本树", async () => {
const committed = await call("/api/preset-lab/versions/commit", {
method: "POST",
body: { preset: preset(), note: "第一次" },
});
expect(committed.body.nodes).toHaveLength(1);
const tree = await call("/api/preset-lab/versions", { query: "presetId=lab" });
expect(tree.body.currentId).toBe(committed.body.nodes[0].id);
});
it("保存至当前版本只覆盖节点,不新增", async () => {
const first = await call("/api/preset-lab/versions/commit", {
method: "POST",
body: { preset: preset(), note: "建根" },
});
const nodeId = first.body.currentId;
const edited = { ...preset(), name: "改过的名字" };
const amended = await call("/api/preset-lab/versions/amend", {
method: "POST",
body: { preset: edited, note: "就地改" },
});
expect(amended.body.nodes).toHaveLength(1);
expect(amended.body.currentId).toBe(nodeId);
const node = amended.body.nodes[0];
expect(node.id).toBe(nodeId);
expect(node.parentId).toBe(null);
expect(node.preset.name).toBe("改过的名字");
expect(node.note).toBe("就地改");
expect(typeof node.updatedAt).toBe("string");
});
it("预览按用例组装消息,{{char}} 被替换", async () => {
const { status, body } = await call("/api/preset-lab/preview", {
method: "POST",
body: {
preset: preset(),
testCase: { id: "c", name: "c", charName: "林", charDescription: "", worldBook: "", userInput: "开门", history: [] },
},
});
expect(status).toBe(200);
const text = body.messages.map((m: any) => m.content).join("\n");
expect(text).toContain("你是林");
expect(text).toContain("开门");
});
it("测试组保存与列出", async () => {
const saved = await call("/api/preset-lab/groups/save", {
method: "POST",
body: { presetId: "lab", name: "组一", cases: [{ name: "a", userInput: "hi" }] },
});
expect(saved.body.cases[0].id).toBeTruthy();
const groups = await call("/api/preset-lab/groups", { query: "presetId=lab" });
expect(groups.body.groups).toHaveLength(1);
});
it("缺少 preset 时返回 400 而不是崩溃", async () => {
const { status, body } = await call("/api/preset-lab/check", { method: "POST", body: {} });
expect(status).toBe(400);
expect(body.error).toContain("preset");
});
});