Files
modelstudioai__cli/packages/commands/tests/e2e/knowledge/knowledge-search.e2e.test.ts
T
zeyu.fz 43abf0aca5 feat(knowledge): 新增知识库管理及用户旅程端到端测试支持
- 增加test:journey脚本,覆盖知识库跨命令全链路用户旅程测试
- 在文档中新增Journey E2E章节,详细说明用户旅程测试定位及断言机制
- 完善commands模块,新增知识库相关命令包括知识库列表、信息、创建、更新、删除
- 新增知识库文档相关命令,如文档列表、状态、上传、删除、打标签及OSS导入
- 添加知识服务管理命令,支持列表、创建、更新、部署、删除及复制
- 支持知识块增删查改命令,完善知识点的灵活操作能力
- 实现数据中心分类管理命令,支持分类增删查操作
- 优化knowledge chat命令,增加workspace-id统一解析及agent-version版本控制
- 重构与知识库相关命令的导出与注册,完善CLI整体能力覆盖
- 新增命令详尽的帮助文档,包含参数说明、使用示例及错误边界
- 实现批量删除知识块的自动分批处理逻辑,易于操作大规模数据
- 添加必要的输入校验与安全提示,确保操作安全且符合规范
2026-08-05 12:02:32 +08:00

296 lines
8.3 KiB
TypeScript

import { describe, expect, test } from "vite-plus/test";
import { isSearchE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
import { KNOWLEDGE_SEARCH_ROUTES } from "../topic-routes.ts";
interface DryRunBody {
endpoint?: string;
request?: {
query?: string;
agent_id?: string;
agent_version?: string;
images?: string[];
query_history?: Array<{ role: string; content: string }>;
};
}
describe("e2e: knowledge search", () => {
test("knowledge search --help 正常退出", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--query/i);
expect(stderr).toMatch(/--agent-id/i);
expect(stderr).toMatch(/--workspace-id/i);
expect(stderr).toMatch(/--image/i);
expect(stderr).toMatch(/--query-history/i);
});
test("缺少 --query 时报用法错误并退出 (2)", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--agent-id",
"aid_test",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/--query|Usage:/i);
});
test("缺少 --agent-id 时报用法错误并退出 (2)", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"test",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/--agent-id|Usage:/i);
});
test("缺少 --workspace-id 时非零退出并提示", async () => {
const { stderr, exitCode } = await runCommandE2e(
KNOWLEDGE_SEARCH_ROUTES,
// Fake key + isolated config dir: keep the local config's workspace_id/api_key from leaking in
[
"knowledge",
"search",
"--query",
"test",
"--agent-id",
"aid_test",
"--api-key",
"sk-fake",
"--output",
"json",
],
{ BAILIAN_WORKSPACE_ID: "", BAILIAN_CONFIG_DIR: "/tmp" },
);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/workspace.*required/i);
});
test("--dry-run 输出 endpoint 和 request body", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--dry-run",
"--query",
"什么是RAG",
"--agent-id",
"aid_test",
"--workspace-id",
"ws_test",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.endpoint).toMatch(/ws_test\.cn-beijing\.maas\.aliyuncs\.com/);
expect(data.endpoint).toMatch(/api\/v1\/indices\/knowledge\/search/);
expect(data.request?.query).toBe("什么是RAG");
expect(data.request?.agent_id).toBe("aid_test");
// Without --agent-version the field is not sent (default behavior unchanged: latest published version)
expect(data.request).not.toHaveProperty("agent_version");
});
test("--dry-run + --agent-version 落在 body 顶层", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--dry-run",
"--query",
"什么是RAG",
"--agent-id",
"aid_test",
"--agent-version",
"beta",
"--workspace-id",
"ws_test",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.agent_version).toBe("beta");
});
test("--dry-run + --image 输出 images", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--dry-run",
"--query",
"test",
"--agent-id",
"aid_test",
"--workspace-id",
"ws_test",
"--image",
"https://example.com/a.jpg",
"--image",
"https://example.com/b.jpg",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.images).toEqual([
"https://example.com/a.jpg",
"https://example.com/b.jpg",
]);
});
test("--dry-run + --query-history 输出用户对话历史", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--dry-run",
"--query",
"它怎么工作",
"--agent-id",
"aid_test",
"--workspace-id",
"ws_test",
"--query-history",
'[{"role":"user","content":"什么是RAG"},{"role":"assistant","content":"RAG是检索增强生成"}]',
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.query_history).toEqual([
{ role: "user", content: "什么是RAG" },
{ role: "assistant", content: "RAG是检索增强生成" },
]);
});
test("--dry-run + --query-history 无效 JSON 非零退出", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--dry-run",
"--query",
"test",
"--agent-id",
"aid_test",
"--workspace-id",
"ws_test",
"--query-history",
"not-valid-json",
"--output",
"json",
]);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/query-history.*valid JSON/i);
});
});
interface SearchResponse {
code: string;
status_code: number;
request_id: string;
data: {
total: number;
cost_time: number;
nodes: Array<{
score: number;
text: string;
metadata: Record<string, unknown>;
}>;
};
}
describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
const agentId = process.env.BAILIAN_E2E_SEARCH_AGENT_ID!;
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
test("search returns results in JSON mode", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"什么是大模型",
"--agent-id",
agentId,
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<SearchResponse>(stdout);
expect(data.code).toBe("Success");
expect(data.request_id).toBeTruthy();
expect(data.data.total).toBeGreaterThan(0);
expect(data.data.nodes.length).toBeGreaterThan(0);
const firstNode = data.data.nodes[0]!;
expect(typeof firstNode.score).toBe("number");
expect(firstNode.score).toBeGreaterThanOrEqual(0);
expect(typeof firstNode.text).toBe("string");
expect(firstNode.text.length).toBeGreaterThan(0);
});
test("search returns results in text mode", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"RAG",
"--agent-id",
agentId,
"--workspace-id",
workspaceId,
"--output",
"text",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toMatch(/\[1\].*score/);
});
test("search with --query-history returns results", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"它怎么工作",
"--agent-id",
agentId,
"--workspace-id",
workspaceId,
"--query-history",
'[{"role":"user","content":"什么是大模型"},{"role":"assistant","content":"大模型是大规模语言模型"}]',
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<SearchResponse>(stdout);
expect(data.code).toBe("Success");
expect(data.data.nodes.length).toBeGreaterThan(0);
});
test("search with invalid agent_id fails gracefully", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
"knowledge",
"search",
"--query",
"test",
"--agent-id",
"aid-invalid-not-exist",
"--workspace-id",
workspaceId,
"--output",
"json",
]);
expect(exitCode).not.toBe(0);
expect(stderr).toBeTruthy();
});
});