fix(knowledge): 验证并限制查询时间范围为过去时间

- 修改时间参数说明,明确要求起止时间必须为过去时间
- 添加起始时间未来时报错机制,避免无意义查询
- 截断结束时间未来时间至当前时间,保障监控接口正确响应
- 添加测试覆盖,验证时间范围边界行为及错误处理
- 补充对应端到端测试路由映射,完善测试用例组织结构
This commit is contained in:
zeyu.fz
2026-08-11 17:39:40 +08:00
parent 81959145d7
commit 92a978af3c
4 changed files with 130 additions and 11 deletions
@@ -21,12 +21,13 @@ const KB_STATS_FLAGS = {
start: {
type: "string",
valueHint: "<time>",
description: "Range start: Unix seconds or ISO date (default: 24 hours ago)",
description:
"Range start: Unix seconds or ISO date, must be in the past (default: 24 hours ago)",
},
end: {
type: "string",
valueHint: "<time>",
description: "Range end: Unix seconds or ISO date (default: now)",
description: "Range end: Unix seconds or ISO date, must be in the past (default: now)",
},
...WORKSPACE_FLAG,
} satisfies FlagsDef;
@@ -56,6 +57,7 @@ export default defineCommand({
notes: [
"Defaults to the last 24 hours when --start/--end are omitted.",
"Timestamps are normalized to epoch seconds as required by the server.",
"Future timestamps are rejected for --start and clamped to now for --end, since the monitor API only returns past data.",
],
exampleArgs: [
"--index-id idx-xxx --workspace-id ws-xxx",
@@ -70,7 +72,21 @@ export default defineCommand({
const startTimestamp = flags.start
? toEpochSecondsString(flags.start)
: String(nowSeconds - 24 * 3600);
const endTimestamp = flags.end ? toEpochSecondsString(flags.end) : String(nowSeconds);
let endTimestamp = flags.end ? toEpochSecondsString(flags.end) : String(nowSeconds);
// The monitor API rejects future timestamps with a misleading
// "missing or invalid" error — validate here with a clear message.
if (Number(startTimestamp) > nowSeconds) {
throw new BailianError(
`Start time is in the future; the monitor API only accepts past or current timestamps.`,
ExitCode.USAGE,
"Use a start date/time at or before now, or omit --start to default to 24 hours ago.",
);
}
const clampedEnd = Number(endTimestamp) > nowSeconds;
if (clampedEnd) {
endTimestamp = String(nowSeconds);
}
const body = { indexId: flags.indexId, startTimestamp, endTimestamp };
const endpoint = ragEndpoint(workspaceId, RAG_PATHS.indexMonitor);
@@ -90,6 +106,9 @@ export default defineCommand({
emitResult(response, format === "text" ? "json" : format);
return;
}
if (clampedEnd) {
emitBare("note: end time was in the future, clamped to now.");
}
// Shape verified against the live API: the monitor fields are objects, not arrays
const storage = response.data?.storageMonitorData;
const qps = response.data?.qpsMonitorData;
@@ -0,0 +1,95 @@
import { describe, expect, test } from "vite-plus/test";
import { parseStdoutJson, runCommandE2e } from "../helpers.ts";
import { KNOWLEDGE_KB_STATS_ROUTES } from "../topic-routes.ts";
interface DryRunBody {
endpoint?: string;
request?: {
indexId?: string;
startTimestamp?: string;
endTimestamp?: string;
};
}
describe("e2e: knowledge stats dry-run", () => {
test("--dry-run 正常日期范围输出正确时间戳", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(
KNOWLEDGE_KB_STATS_ROUTES,
[
"knowledge",
"stats",
"--dry-run",
"--index-id",
"idx_test",
"--start",
"2026-07-30",
"--end",
"2026-08-10",
"--workspace-id",
"ws_test",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.endpoint).toMatch(/api\/v1\/indices\/rag\/index\/monitor/);
expect(data.request?.indexId).toBe("idx_test");
expect(data.request?.startTimestamp).toBe("1785369600"); // 2026-07-30 00:00:00 UTC
expect(data.request?.endTimestamp).toBe("1786320000"); // 2026-08-10 00:00:00 UTC (11 days after start)
});
test("--end 未来时间被截断为当前时间", async () => {
const beforeRun = Math.floor(Date.now() / 1000);
const { stdout, stderr, exitCode } = await runCommandE2e(
KNOWLEDGE_KB_STATS_ROUTES,
[
"knowledge",
"stats",
"--dry-run",
"--index-id",
"idx_test",
"--start",
"2026-07-30",
"--end",
"2026-12-31",
"--workspace-id",
"ws_test",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
const afterRun = Math.floor(Date.now() / 1000);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<DryRunBody>(stdout);
expect(data.request?.startTimestamp).toBe("1785369600"); // 2026-07-30 unchanged
// endTimestamp should be clamped to now, within the run window
const endTs = Number(data.request?.endTimestamp);
expect(endTs).toBeGreaterThanOrEqual(beforeRun);
expect(endTs).toBeLessThanOrEqual(afterRun);
});
test("--start 未来时间报用法错误 (exit 2)", async () => {
const { stderr, exitCode } = await runCommandE2e(
KNOWLEDGE_KB_STATS_ROUTES,
[
"knowledge",
"stats",
"--dry-run",
"--index-id",
"idx_test",
"--start",
"2026-12-31",
"--workspace-id",
"ws_test",
"--output",
"json",
],
{ DASHSCOPE_API_KEY: "sk-fake-for-dryrun" },
);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/future/i);
});
});
@@ -244,6 +244,10 @@ export const KNOWLEDGE_DOC_TAG_ROUTES: E2eRouteExports = {
"knowledge file delete": "knowledgeFileDelete", // live cleanup of data-center files
};
export const KNOWLEDGE_KB_STATS_ROUTES: E2eRouteExports = {
"knowledge stats": "knowledgeKbStats",
};
export const KNOWLEDGE_SERVICE_ROUTES: E2eRouteExports = {
"knowledge service list": "knowledgeServiceList",
"knowledge service get": "knowledgeServiceGet",
+9 -8
View File
@@ -1207,19 +1207,20 @@ bl knowledge service update --agent-id aid-xxx --agent-version 1 --version-desc
#### Flags
| Flag | Type | Required | Description |
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
| `--index-id <id>` | string | yes | Knowledge base ID |
| `--start <time>` | string | no | Range start: Unix seconds or ISO date (default: 24 hours ago) |
| `--end <time>` | string | no | Range end: Unix seconds or ISO date (default: now) |
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| Flag | Type | Required | Description |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| `--index-id <id>` | string | yes | Knowledge base ID |
| `--start <time>` | string | no | Range start: Unix seconds or ISO date, must be in the past (default: 24 hours ago) |
| `--end <time>` | string | no | Range end: Unix seconds or ISO date, must be in the past (default: now) |
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
#### Notes
- Defaults to the last 24 hours when --start/--end are omitted.
- Timestamps are normalized to epoch seconds as required by the server.
- Future timestamps are rejected for --start and clamped to now for --end, since the monitor API only returns past data.
#### Examples