Files
united-workforce/packages/workflow-util-role/__tests__/llm-extract.test.ts
T
xingyue 94fa964b84 fix(workflow): add typecheck script and fix remaining type errors
- Add typecheck script (bunx tsc --build) to package.json
- Update check script to run typecheck before biome
- Fix mock fetch casts in test files (bun-types preconnect)
- Fix RequestInfo → Request | string | URL in llm-extract test
- Fix ThreadContext generic cast in solve-issue-template test
- Fix git-exec.ts missing return and module resolution
- Remove @types/node from workflow-role-committer
- Add exports field to workflow-util-agent/package.json
2026-05-06 18:10:25 +08:00

147 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import * as z from "zod/v4";
import { llmExtract } from "../src/llm-extract.js";
describe("llmExtract", () => {
const originalFetch = globalThis.fetch;
test("parses tool call arguments and validates with the zod schema", async () => {
const schema = z
.object({
name: z.string(),
description: z.string(),
})
.describe("Extract sense metadata from plan");
let capturedUrl: string | null = null;
let capturedInit: RequestInit | null = null;
globalThis.fetch = ((input: Request | string | URL, init?: RequestInit) => {
capturedUrl = typeof input === "string" ? input : input.toString();
capturedInit = init ?? null;
return Promise.resolve(
new Response(
JSON.stringify({
choices: [
{
message: {
tool_calls: [
{
function: {
name: "extract",
arguments: JSON.stringify({
name: "cpu-usage",
description: "CPU load",
}),
},
},
],
},
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
}) as unknown as typeof fetch;
const result = await llmExtract({
text: "some plan",
schema,
provider: {
baseUrl: "https://example.com/v1",
apiKey: "k",
model: "m",
},
dryRun: false,
dryRunMeta: { name: "", description: "" },
});
globalThis.fetch = originalFetch;
expect(result.ok).toBe(true);
if (!result.ok) {
return;
}
expect(result.value).toEqual({ name: "cpu-usage", description: "CPU load" });
expect(capturedUrl!).toBe("https://example.com/v1/chat/completions");
expect(capturedInit!.method).toBe("POST");
expect(capturedInit!.headers).toMatchObject({
Authorization: "Bearer k",
"Content-Type": "application/json",
});
const body = JSON.parse(capturedInit!.body as string) as {
model: string;
tool_choice: { function: { name: string } };
};
expect(body.model).toBe("m");
expect(body.tool_choice.function.name).toBeDefined();
});
test("returns schema_validation_failed when arguments do not match the schema", async () => {
const schema = z.object({ n: z.number() });
globalThis.fetch = (() =>
Promise.resolve(
new Response(
JSON.stringify({
choices: [
{
message: {
tool_calls: [
{ function: { name: "extract", arguments: JSON.stringify({ n: "oops" }) } },
],
},
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
)) as unknown as typeof fetch;
const result = await llmExtract({
text: "x",
schema,
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
dryRun: false,
dryRunMeta: { n: 0 },
});
globalThis.fetch = originalFetch;
expect(result.ok).toBe(false);
if (result.ok) {
return;
}
expect(result.error.kind).toBe("schema_validation_failed");
});
test("dryRun skips fetch and returns dryRunMeta", async () => {
let calls = 0;
globalThis.fetch = (() => {
calls += 1;
return Promise.resolve(new Response("{}", { status: 200 }));
}) as unknown as typeof fetch;
const schema = z.object({ n: z.number() });
const result = await llmExtract({
text: "ignored",
schema,
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
dryRun: true,
dryRunMeta: { n: 42 },
});
globalThis.fetch = originalFetch;
expect(calls).toBe(0);
expect(result.ok).toBe(true);
if (!result.ok) {
return;
}
expect(result.value).toEqual({ n: 42 });
});
});