Skip to content

Commit 12e7a22

Browse files
committed
test(knowledge): 增加文档相关命令的独立读回验证
- 在 knowledge doc delete 命令中添加异步删除的轮询验证,确保文档从服务器彻底移除 - 为 knowledge doc tag 添加标签设置后,独立调用 file get 验证标签正确应用 - 在知识库更新操作后,通过 info 命令独立验证更新是否成功保存 - 在文件删除和类别删除后,通过独立列表命令验证资源确实被清除 - 对知识块更新及排除标记修改,添加通过列表接口的内容验证步骤 - 对知识服务代理删除操作后,增加独立查询接口确保代理已彻底删除 - 补充 doc delete 备注,明确 doc_id 与 fileId 的区别及异步删除机制说明 - 增加 e2e 路由映射中缺失的 knowledge info 和 knowledge file get 命令支持
1 parent 219d8be commit 12e7a22

7 files changed

Lines changed: 231 additions & 4 deletions

File tree

packages/commands/src/commands/knowledge/doc-delete.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export default defineCommand({
4242
flags: DOC_DELETE_FLAGS,
4343
notes: [
4444
"Removes documents from the knowledge base index only; the source files remain in the data center.",
45+
"Use the doc_id from `knowledge doc list --quiet`, not the fileId from `knowledge doc upload`. For documents created via `knowledge create --doc-id`, the doc_id equals the fileId; for documents imported via `knowledge doc upload --index-id`, the doc_id may include a workspace suffix.",
46+
"Deletion is asynchronous: the server returns Success immediately, but the document may still appear in `knowledge doc list` for up to ~30s until the change propagates.",
4547
"The output lists the ids actually deleted as reported by the server.",
4648
],
4749
exampleArgs: [

packages/commands/tests/e2e/knowledge/knowledge-chunk-category-file.e2e.test.ts

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,20 @@ describe.skipIf(!isKbAdminE2EReady())(
10721072
workspaceId,
10731073
]);
10741074
expect(deleteRun.exitCode, deleteRun.stderr).toBe(0);
1075+
1076+
// Verify the file is actually gone from the data center via read-back
1077+
const postFileListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1078+
"knowledge",
1079+
"file",
1080+
"list",
1081+
"--category-id",
1082+
categoryId,
1083+
"--workspace-id",
1084+
workspaceId,
1085+
"--quiet",
1086+
]);
1087+
expect(postFileListRun.exitCode, postFileListRun.stderr).toBe(0);
1088+
expect(postFileListRun.stdout.trim()).not.toContain(fileId);
10751089
} finally {
10761090
// Clean up the self-created category (doubles as live coverage of category delete)
10771091
const categoryDeleteRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
@@ -1085,6 +1099,20 @@ describe.skipIf(!isKbAdminE2EReady())(
10851099
workspaceId,
10861100
]);
10871101
expect(categoryDeleteRun.exitCode, categoryDeleteRun.stderr).toBe(0);
1102+
1103+
// Verify the category is actually gone from the server via read-back
1104+
const postCategoryListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1105+
"knowledge",
1106+
"category",
1107+
"list",
1108+
"--name",
1109+
categoryName,
1110+
"--workspace-id",
1111+
workspaceId,
1112+
"--quiet",
1113+
]);
1114+
expect(postCategoryListRun.exitCode, postCategoryListRun.stderr).toBe(0);
1115+
expect(postCategoryListRun.stdout.trim()).not.toContain(categoryId);
10881116
}
10891117
// kb create --wait adds a full import phase — generous timeout
10901118
}, 600_000);
@@ -1176,6 +1204,31 @@ describe.skipIf(!isKbAdminE2EReady())(
11761204
]);
11771205
expect(updateRun.exitCode, updateRun.stderr).toBe(0);
11781206

1207+
// Verify the content update landed on the server via independent read-back
1208+
const updateVerifyRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1209+
"knowledge",
1210+
"chunk",
1211+
"list",
1212+
"--index-id",
1213+
indexId,
1214+
"--workspace-id",
1215+
workspaceId,
1216+
"--output",
1217+
"json",
1218+
]);
1219+
expect(updateVerifyRun.exitCode, updateVerifyRun.stderr).toBe(0);
1220+
const updateVerifyData = parseStdoutJson<{
1221+
data?: {
1222+
nodes?: Array<{ metadata?: { _id?: string; content?: string }; text?: string }>;
1223+
};
1224+
}>(updateVerifyRun.stdout);
1225+
const updatedChunk = updateVerifyData.data?.nodes?.find(
1226+
(node) => node.metadata?._id === targetChunkId,
1227+
);
1228+
expect(updatedChunk?.metadata?.content ?? updatedChunk?.text).toBe(
1229+
"e2e updated chunk content",
1230+
);
1231+
11791232
// update toggling --exclude only (no content — exercises the fetchChunkContent read-back path live)
11801233
const excludeRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
11811234
"knowledge",
@@ -1193,6 +1246,29 @@ describe.skipIf(!isKbAdminE2EReady())(
11931246
]);
11941247
expect(excludeRun.exitCode, excludeRun.stderr).toBe(0);
11951248

1249+
// Verify the exclude flag landed on the server via independent read-back
1250+
const excludeVerifyRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1251+
"knowledge",
1252+
"chunk",
1253+
"list",
1254+
"--index-id",
1255+
indexId,
1256+
"--workspace-id",
1257+
workspaceId,
1258+
"--output",
1259+
"json",
1260+
]);
1261+
expect(excludeVerifyRun.exitCode, excludeVerifyRun.stderr).toBe(0);
1262+
const excludeVerifyData = parseStdoutJson<{
1263+
data?: {
1264+
nodes?: Array<{ metadata?: { _id?: string; is_displayed_chunk_content?: boolean } }>;
1265+
};
1266+
}>(excludeVerifyRun.stdout);
1267+
const excludedChunk = excludeVerifyData.data?.nodes?.find(
1268+
(node) => node.metadata?._id === targetChunkId,
1269+
);
1270+
expect(excludedChunk?.metadata?.is_displayed_chunk_content).toBe(false);
1271+
11961272
// kb stats on this base doubles as live coverage of the monitor endpoint
11971273
// (the only indices endpoint not touched by the chain)
11981274
const statsRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
@@ -1298,22 +1374,89 @@ describe.skipIf(!isKbAdminE2EReady())(
12981374
),
12991375
);
13001376

1377+
// Verify the chunks are actually gone from the server via independent read-back
1378+
const postDeleteListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1379+
"knowledge",
1380+
"chunk",
1381+
"list",
1382+
"--index-id",
1383+
indexId,
1384+
"--workspace-id",
1385+
workspaceId,
1386+
"--quiet",
1387+
]);
1388+
expect(postDeleteListRun.exitCode, postDeleteListRun.stderr).toBe(0);
1389+
expect(postDeleteListRun.stdout.trim()).toBe("");
1390+
13011391
// doc delete verifies document-level delete semantics (only unlinks from this
1302-
// base, unlike file delete)
1392+
// base, unlike file delete). The --doc-id must be the full doc_id from doc
1393+
// list (e.g. "file_xxx_<workspaceId>"), NOT the bare fileId ("file_xxx") —
1394+
// passing the bare fileId returns Success but does not actually remove the
1395+
// document.
1396+
const preDocListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1397+
"knowledge",
1398+
"doc",
1399+
"list",
1400+
"--index-id",
1401+
indexId,
1402+
"--workspace-id",
1403+
workspaceId,
1404+
"--output",
1405+
"json",
1406+
]);
1407+
expect(preDocListRun.exitCode, preDocListRun.stderr).toBe(0);
1408+
const preDocListData = parseStdoutJson<{
1409+
data?: { rows?: Array<{ doc_id?: string; doc_name?: string }> };
1410+
}>(preDocListRun.stdout);
1411+
const docRow = preDocListData.data?.rows?.find((row) => row.doc_id?.startsWith(fileId));
1412+
expect(
1413+
docRow,
1414+
`expected doc list to contain a doc_id starting with "${fileId}"`,
1415+
).toBeTruthy();
1416+
const fullDocId = docRow!.doc_id!;
1417+
13031418
const docDeleteRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
13041419
"knowledge",
13051420
"doc",
13061421
"delete",
13071422
"--index-id",
13081423
indexId,
13091424
"--doc-id",
1310-
fileId,
1425+
fullDocId,
13111426
"--yes",
13121427
"--workspace-id",
13131428
workspaceId,
13141429
]);
13151430
expect(docDeleteRun.exitCode, docDeleteRun.stderr).toBe(0);
1316-
expect(docDeleteRun.stdout).toMatch(new RegExp(fileId));
1431+
expect(docDeleteRun.stdout).toMatch(new RegExp(fullDocId));
1432+
1433+
// Verify the document is actually gone from the server via independent read-back.
1434+
// doc delete is asynchronous — the server returns Success immediately but the
1435+
// document disappears from doc list after a propagation delay (same pattern as
1436+
// IndexStatusError on kb delete). Poll until the doc_id is gone or timeout.
1437+
let docGone = false;
1438+
for (let retry = 0; retry < 6; retry++) {
1439+
await new Promise((resolve) => setTimeout(resolve, 10_000));
1440+
const postDocListRun = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
1441+
"knowledge",
1442+
"doc",
1443+
"list",
1444+
"--index-id",
1445+
indexId,
1446+
"--workspace-id",
1447+
workspaceId,
1448+
"--quiet",
1449+
]);
1450+
expect(postDocListRun.exitCode, postDocListRun.stderr).toBe(0);
1451+
if (!postDocListRun.stdout.trim().includes(fullDocId)) {
1452+
docGone = true;
1453+
break;
1454+
}
1455+
}
1456+
expect(
1457+
docGone,
1458+
`doc delete returned Success but the document is still in doc list after 60s`,
1459+
).toBe(true);
13171460
} finally {
13181461
// Clean up the throwaway base + data-center file (same IndexStatusError retry as the kb delete chain)
13191462
await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [

packages/commands/tests/e2e/knowledge/knowledge-doc-tag.e2e.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge doc tag (live)", () => {
218218
const data = parseStdoutJson<{ code: string }>(tagRun.stdout);
219219
expect(data.code).toBe("Success");
220220

221+
// Verify the tag actually landed on the server via an independent read-back
222+
const tagGetRun = await runCommandE2e(KNOWLEDGE_DOC_TAG_ROUTES, [
223+
"knowledge",
224+
"file",
225+
"get",
226+
"--file-id",
227+
fileId,
228+
"--workspace-id",
229+
workspaceId,
230+
"--output",
231+
"json",
232+
]);
233+
expect(tagGetRun.exitCode, tagGetRun.stderr).toBe(0);
234+
const tagGetDetail = parseStdoutJson<{ data: { tags?: string[] } }>(tagGetRun.stdout);
235+
expect(tagGetDetail.data?.tags ?? []).toContain("e2e-tag");
236+
221237
// overwrite mode replaces the tag set live (append above covered the default)
222238
const overwriteRun = await runCommandE2e(KNOWLEDGE_DOC_TAG_ROUTES, [
223239
"knowledge",
@@ -238,6 +254,26 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge doc tag (live)", () => {
238254
const overwriteData = parseStdoutJson<{ code: string }>(overwriteRun.stdout);
239255
expect(overwriteData.code).toBe("Success");
240256

257+
// Verify overwrite replaced the tag set — old tag gone, new tag present
258+
const overwriteGetRun = await runCommandE2e(KNOWLEDGE_DOC_TAG_ROUTES, [
259+
"knowledge",
260+
"file",
261+
"get",
262+
"--file-id",
263+
fileId,
264+
"--workspace-id",
265+
workspaceId,
266+
"--output",
267+
"json",
268+
]);
269+
expect(overwriteGetRun.exitCode, overwriteGetRun.stderr).toBe(0);
270+
const overwriteGetDetail = parseStdoutJson<{ data: { tags?: string[] } }>(
271+
overwriteGetRun.stdout,
272+
);
273+
const overwriteTags = overwriteGetDetail.data?.tags ?? [];
274+
expect(overwriteTags).toContain("e2e-tag-final");
275+
expect(overwriteTags).not.toContain("e2e-tag");
276+
241277
// Clean up the uploaded data-center file
242278
const fileDeleteRun = await runCommandE2e(KNOWLEDGE_DOC_TAG_ROUTES, [
243279
"knowledge",

packages/commands/tests/e2e/knowledge/knowledge-kb-delete.e2e.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,14 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge kb 写链路 (live, 自清
113113
expect(indexId).toBeTruthy();
114114

115115
// 2.5) Live update coverage: name / description / rerank threshold in one call
116+
const updatedName = `e2e-upd-${Date.now() % 100000000}`;
116117
const updateRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
117118
"knowledge",
118119
"update",
119120
"--index-id",
120121
indexId,
121122
"--name",
122-
`e2e-upd-${Date.now() % 100000000}`,
123+
updatedName,
123124
"--description",
124125
"e2e chain updated description",
125126
"--rerank-min-score",
@@ -130,6 +131,28 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge kb 写链路 (live, 自清
130131
expect(updateRun.exitCode, updateRun.stderr).toBe(0);
131132
expect(updateRun.stdout).toMatch(/updated/);
132133

134+
// 2.6) Verify the update actually landed on the server — not just that the
135+
// command returned Success, but that a fresh read-back shows the new values
136+
const infoRun = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
137+
"knowledge",
138+
"info",
139+
"--index-id",
140+
indexId,
141+
"--workspace-id",
142+
workspaceId,
143+
"--output",
144+
"json",
145+
]);
146+
expect(infoRun.exitCode, infoRun.stderr).toBe(0);
147+
const infoData = parseStdoutJson<{
148+
name?: string;
149+
description?: string;
150+
rerankMinScore?: number;
151+
}>(infoRun.stdout);
152+
expect(infoData.name).toBe(updatedName);
153+
expect(infoData.description).toBe("e2e chain updated description");
154+
expect(infoData.rerankMinScore).toBe(0.3);
155+
133156
// 2.7) Live coverage of the upload → import orchestration (doc upload --index-id --wait):
134157
// the journeys always upload bare files and import via kb create, so this is the
135158
// only place the createImportJob step runs live. Using --output json (not --quiet)

packages/commands/tests/e2e/knowledge/knowledge-service.e2e.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,5 +647,24 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge service 生命周期 (live
647647
]);
648648
expect(deleteRun.exitCode, deleteRun.stderr).toBe(0);
649649
}
650+
651+
// 6.5) Verify both agents are gone from the server — not just that delete
652+
// returned Success, but that a fresh list filtered by agent_id finds nothing
653+
for (const idToVerify of [copiedAgentId, agentId]) {
654+
const postDeleteListRun = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
655+
"knowledge",
656+
"service",
657+
"list",
658+
"--scene",
659+
"chat",
660+
"--agent-id",
661+
idToVerify,
662+
"--workspace-id",
663+
workspaceId,
664+
"--quiet",
665+
]);
666+
expect(postDeleteListRun.exitCode, postDeleteListRun.stderr).toBe(0);
667+
expect(postDeleteListRun.stdout.trim()).toBe("");
668+
}
650669
}, 300_000);
651670
});

packages/commands/tests/e2e/topic-routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ export const KNOWLEDGE_KB_DELETE_ROUTES: E2eRouteExports = {
220220
"knowledge delete": "knowledgeKbDelete",
221221
"knowledge create": "knowledgeKbCreate", // live self-cleaning chain
222222
"knowledge update": "knowledgeKbUpdate", // live update step in the chain
223+
"knowledge info": "knowledgeKbInfo", // live: verify update landed on the server
223224
"knowledge list": "knowledgeKbList",
224225
"knowledge doc upload": "knowledgeDocUpload",
225226
"knowledge doc list": "knowledgeDocList", // live: verify imported file is visible in the KB
@@ -233,6 +234,7 @@ export const KNOWLEDGE_DOC_DELETE_ROUTES: E2eRouteExports = {
233234
export const KNOWLEDGE_DOC_TAG_ROUTES: E2eRouteExports = {
234235
"knowledge doc tag": "knowledgeDocTag",
235236
"knowledge doc upload": "knowledgeDocUpload", // live uploads first to grab a real fileId
237+
"knowledge file get": "knowledgeFileGet", // live: verify tags landed on the server
236238
"knowledge file delete": "knowledgeFileDelete", // live cleanup of data-center files
237239
};
238240

skills/bailian-cli/reference/knowledge.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ bl knowledge delete --index-id idx-xxx --yes
502502
#### Notes
503503

504504
- Removes documents from the knowledge base index only; the source files remain in the data center.
505+
- Use the doc_id from `knowledge doc list --quiet`, not the fileId from `knowledge doc upload`. For documents created via `knowledge create --doc-id`, the doc_id equals the fileId; for documents imported via `knowledge doc upload --index-id`, the doc_id may include a workspace suffix.
506+
- Deletion is asynchronous: the server returns Success immediately, but the document may still appear in `knowledge doc list` for up to ~30s until the change propagates.
505507
- The output lists the ids actually deleted as reported by the server.
506508

507509
#### Examples

0 commit comments

Comments
 (0)