Files
modelstudioai__cli/packages/commands/tests/e2e/knowledge/knowledge-kb-delete.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

221 lines
7.5 KiB
TypeScript

// The live group chains the full lifecycle: upload → create → update → upload --index-id
// (import orchestration) → delete → list to verify removal.
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vite-plus/test";
import { isKbAdminE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
import { deleteKbWithRetry } from "./journeys/journey-helpers.ts";
import { KNOWLEDGE_KB_DELETE_ROUTES } from "../topic-routes.ts";
describe("e2e: knowledge kb delete", () => {
test("--help 展示 flags", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"delete",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--index-id/i);
expect(stderr).toMatch(/--yes/i);
});
test("缺 --index-id 报 USAGE (2)", async () => {
const { exitCode } = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"delete",
"--workspace-id",
"ws_test",
]);
expect(exitCode).toBe(2);
});
test("--dry-run 断言 body index_id (snake_case)", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"delete",
"--index-id",
"idx_test",
"--workspace-id",
"ws_test",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ endpoint?: string; request?: Record<string, unknown> }>(stdout);
expect(data.endpoint).toMatch(/api\/v1\/indices\/rag\/index\/delete/);
expect(data.request?.index_id).toBe("idx_test");
});
test("非 TTY 无 --yes 报 USAGE (2)", async () => {
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"delete",
"--index-id",
"idx_test",
"--api-key",
"sk-fake",
"--workspace-id",
"ws_test",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/--yes/);
});
});
describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge kb 写链路 (live, 自清理)", () => {
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
test("upload → create → update → upload --index-id → delete → list 验证消失", async () => {
// Mid-chain failures must not leak the base / data-center files — track created
// resources and best-effort clean them in finally (only when the chain aborted)
const fixtureFileIds: string[] = [];
let indexId = "";
let chainCompleted = false;
try {
// 1) Upload a file
const fixtureDir = mkdtempSync(join(tmpdir(), "kb-chain-e2e-"));
const filePath = join(fixtureDir, `chain-${Date.now()}.md`);
writeFileSync(filePath, "# kb chain e2e fixture\n");
const uploadRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"doc",
"upload",
"--file",
filePath,
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(uploadRun.exitCode, uploadRun.stderr).toBe(0);
const fileId = uploadRun.stdout.trim();
expect(fileId).toMatch(/^file_/);
fixtureFileIds.push(fileId);
// 2) Create the knowledge base (--wait for the import; server gotcha: a freshly
// created base cannot be deleted immediately — Index.IndexStatusError)
const kbName = `e2e-del-${Date.now() % 100000000}`;
const createRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"create",
"--name",
kbName,
"--doc-id",
fileId,
"--workspace-id",
workspaceId,
"--wait",
"--quiet",
]);
expect(createRun.exitCode, createRun.stderr).toBe(0);
indexId = createRun.stdout.trim().split("\n")[0]!;
expect(indexId).toBeTruthy();
// 2.5) Live update coverage: name / description / rerank threshold in one call
const updateRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"update",
"--index-id",
indexId,
"--name",
`e2e-upd-${Date.now() % 100000000}`,
"--description",
"e2e chain updated description",
"--rerank-min-score",
"0.3",
"--workspace-id",
workspaceId,
]);
expect(updateRun.exitCode, updateRun.stderr).toBe(0);
expect(updateRun.stdout).toMatch(/updated/);
// 2.7) Live coverage of the upload → import orchestration (doc upload --index-id --wait):
// the journeys always upload bare files and import via kb create, so this is the
// only place the createImportJob step runs live
const importFilePath = join(fixtureDir, `chain-import-${Date.now()}.md`);
writeFileSync(importFilePath, "# kb chain e2e import fixture\n");
const importUploadRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"doc",
"upload",
"--file",
importFilePath,
"--index-id",
indexId,
"--wait",
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(importUploadRun.exitCode, importUploadRun.stderr).toBe(0);
const importedFileId = importUploadRun.stdout.trim();
expect(importedFileId).toMatch(/^file_/);
fixtureFileIds.push(importedFileId);
// 3) Delete the base (--yes non-interactive; deleteKbWithRetry retries on
// IndexStatusError: readiness can lag briefly even after the import completes)
const deleteRun = await deleteKbWithRetry(
(args) => runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, args),
indexId,
workspaceId,
);
expect(deleteRun.exitCode, deleteRun.stderr).toBe(0);
expect(deleteRun.stdout).toMatch(/deleted/);
// 3.5) Clean up the data-center files (kb delete removes them from the base but
// the file artifacts persist in the data center)
for (const cleanupFileId of fixtureFileIds) {
const fileDeleteRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"file",
"delete",
"--file-id",
cleanupFileId,
"--yes",
"--workspace-id",
workspaceId,
]);
expect(fileDeleteRun.exitCode, fileDeleteRun.stderr).toBe(0);
}
// 4) list verifies the base is gone
const listRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"list",
"--workspace-id",
workspaceId,
"--quiet",
]);
expect(listRun.exitCode, listRun.stderr).toBe(0);
const remainingIds = listRun.stdout.trim().split("\n");
expect(remainingIds).not.toContain(indexId);
chainCompleted = true;
} finally {
if (!chainCompleted) {
// Best-effort cleanup on abort — do not assert, the original failure matters more
if (indexId) {
await deleteKbWithRetry(
(args) => runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, args),
indexId,
workspaceId,
);
}
for (const cleanupFileId of fixtureFileIds) {
await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
"knowledge",
"file",
"delete",
"--file-id",
cleanupFileId,
"--yes",
"--workspace-id",
workspaceId,
]);
}
}
}
// Two --wait import phases in the chain — timeout sized for slow server-side parsing
}, 600_000);
});