mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
d24104f7dc
包名 @ali/bailian-kb-dsh → bailian-kb-dsh(公开 npm):package.json name + cordis.patch.yml insert.name + tsdown PLUGIN_ID 三处同步(漏一处即 dsh 运行时崩)。 产物 lib/ → dist/(本仓 .gitignore 忽略 dist 不忽略 lib),连带 main/types/ exports/files/tsdown outDir 同步;.gitignore 补 *.tsbuildinfo。 依赖接 catalog(yaml/typescript/@types/node/vite-plus);测试导入 vitest → vite-plus/test(全仓统一约定,消掉唯一的 vitest 依赖漂移)。 tsconfig 拆三件套:tsconfig.json 纯类型检查覆盖 src+tests(供 oxlint 自动发现, 含 jsx/DOM),tsconfig.build.json 产出 node 半,tsconfig.web.json 隔离检查 web 半。 补齐 tests 从未被类型检查暴露的一处 partial 输入类型错误。 根 vite.config.ts 新增两条 override:web 半 no-restricted-imports 把 tsdown 构建期 的 bundle purity gate 提前到 lint 期;全包放开 _ 前缀的 no-unused-vars。 文档:新增 docs/agents/dsh-plugin.md,AGENTS.md 项目地图/版本锁步例外/分层边界/ 场景索引同步,packages.mjs 注释说明故意不进发布白名单。 格式化(单引号无分号 → 双引号加分号)由 pre-commit 的 vp check --fix 自动完成, 无法单独成 commit,一并纳入。 全仓 vp check 0 error;插件 13 文件 85 测试全绿;build + typecheck 通过。
125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
import { describe, expect, it, vi } from "vite-plus/test";
|
|
import type { ServiceListResponse } from "../src/api-types.js";
|
|
import type { KbClient } from "../src/client.js";
|
|
import { listServices } from "../src/services.js";
|
|
|
|
/** A page of `count` rows, all deployed unless overridden. */
|
|
function page(
|
|
count: number,
|
|
total: number,
|
|
overrides: Record<string, unknown> = {},
|
|
): ServiceListResponse {
|
|
return {
|
|
code: "Success",
|
|
data: {
|
|
total_count: total,
|
|
rows: Array.from({ length: count }, (_row, index) => ({
|
|
agent_id: `aid-${index}`,
|
|
agent_name: `service-${index}`,
|
|
agent_status: "deployed",
|
|
modify_time: "2026-08-20T10:00:00",
|
|
...overrides,
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
|
|
/** A client whose postJson is driven by a queue of per-call responses or errors. */
|
|
function clientReturning(...outcomes: (ServiceListResponse | Error)[]): {
|
|
client: KbClient;
|
|
postJson: ReturnType<typeof vi.fn>;
|
|
} {
|
|
const postJson = vi.fn(async () => {
|
|
const next = outcomes.shift();
|
|
if (next === undefined) throw new Error("unexpected extra request");
|
|
if (next instanceof Error) throw next;
|
|
return next;
|
|
});
|
|
return { client: { postJson } as unknown as KbClient, postJson };
|
|
}
|
|
|
|
describe("listServices", () => {
|
|
it("queries both scenes for deployed services only and tags each entry with its scene", async () => {
|
|
const { client, postJson } = clientReturning(page(1, 1), page(1, 1));
|
|
const result = await listServices(client);
|
|
|
|
expect(postJson.mock.calls[0]?.[1]).toMatchObject({
|
|
agent_scene: "search",
|
|
// Verified server-side: this filter is honored and excludes drafts.
|
|
agent_status: "deployed",
|
|
page_number: 1,
|
|
page_size: 100,
|
|
});
|
|
expect(postJson.mock.calls[1]?.[1]).toMatchObject({ agent_scene: "chat" });
|
|
expect(result.entries.map((e) => e.scene)).toEqual(["search", "chat"]);
|
|
expect(result.total).toBe(2);
|
|
expect(result.truncated).toBe(false);
|
|
expect(result.failedScenes).toEqual([]);
|
|
});
|
|
|
|
it("stops at a short page without asking for another", async () => {
|
|
// 3 rows on a 100-row page is the last page; a second request would be waste.
|
|
const { client, postJson } = clientReturning(page(3, 3), page(0, 0));
|
|
const result = await listServices(client);
|
|
expect(postJson).toHaveBeenCalledTimes(2); // one per scene, not one per page
|
|
expect(result.entries).toHaveLength(3);
|
|
});
|
|
|
|
it("caps at two pages per scene and reports the shortfall as truncated", async () => {
|
|
// A workspace claiming 500 rows: fetch 200, flag the rest as unfetched.
|
|
const { client, postJson } = clientReturning(page(100, 500), page(100, 500), page(0, 0));
|
|
const result = await listServices(client);
|
|
// 2 pages for search + 1 short page for chat: the cap holds.
|
|
expect(postJson).toHaveBeenCalledTimes(3);
|
|
expect(result.entries).toHaveLength(200);
|
|
expect(result.truncated).toBe(true);
|
|
});
|
|
|
|
it("keeps one scene when the other fails instead of losing the whole list", async () => {
|
|
const { client } = clientReturning(page(2, 2), new Error("chat scene exploded"));
|
|
const result = await listServices(client);
|
|
expect(result.entries).toHaveLength(2);
|
|
expect(result.entries.every((e) => e.scene === "search")).toBe(true);
|
|
expect(result.failedScenes).toEqual(["chat"]);
|
|
});
|
|
|
|
it("reports both scenes as failed without throwing", async () => {
|
|
const { client } = clientReturning(new Error("down"), new Error("down"));
|
|
const result = await listServices(client);
|
|
expect(result.entries).toEqual([]);
|
|
expect(result.failedScenes).toEqual(["search", "chat"]);
|
|
});
|
|
|
|
it("drops rows without an agent_id and never reads pipeline_list", async () => {
|
|
// pipeline_list is unreliable in production (missing names, sometimes empty),
|
|
// so entries must not carry any knowledge-base label derived from it.
|
|
const { client } = clientReturning(
|
|
{
|
|
code: "Success",
|
|
data: {
|
|
total_count: 2,
|
|
rows: [
|
|
{ agent_name: "no id", agent_status: "deployed" },
|
|
{
|
|
agent_id: "aid-1",
|
|
agent_name: "ok",
|
|
agent_status: "deployed",
|
|
pipeline_list: [{ pipeline_id: "p1" }],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
page(0, 0),
|
|
);
|
|
const result = await listServices(client);
|
|
expect(result.entries).toHaveLength(1);
|
|
expect(result.entries[0]).toEqual({
|
|
agent_id: "aid-1",
|
|
agent_name: "ok",
|
|
scene: "search",
|
|
status: "deployed",
|
|
});
|
|
expect(JSON.stringify(result.entries)).not.toContain("p1");
|
|
});
|
|
});
|