test: cover llm workflow through api

This commit is contained in:
Codex
2026-06-21 23:43:02 +08:00
parent 4db9980e81
commit f90ddab454
2 changed files with 54 additions and 3 deletions
+5 -2
View File
@@ -36,8 +36,11 @@ OPENAI_API_KEY=
OPENAI_MODEL=gpt-4.1-mini
```
When no API key is configured, deterministic local fallbacks keep the workflow
usable for tests and local review.
When a DeepSeek or OpenAI-compatible key is configured, the workflow uses the
provider for fact extraction, article optimization, QA enrichment, and targeted
rewrite. Every LLM response is validated with Zod before use. When no API key is
configured, or when the provider response is invalid, deterministic local
fallbacks keep the workflow usable for tests and local review.
All API requests require the configured access key:
+49 -1
View File
@@ -2,7 +2,21 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const llmMocks = vi.hoisted(() => ({
generateValidatedJson: vi.fn(),
}));
vi.mock("../../../lib/llm/client", async () => {
const actual = await vi.importActual<typeof import("../../../lib/llm/client")>(
"../../../lib/llm/client",
);
return {
...actual,
generateValidatedJson: llmMocks.generateValidatedJson,
};
});
import { POST as confirmFactCard } from "../jobs/[jobId]/confirm-fact-card/route";
import { GET as downloadExport } from "../jobs/[jobId]/exports/[fileName]/route";
@@ -52,6 +66,7 @@ describe("job API routes", () => {
process.env.APP_DATA_DIR = originalDataDir;
process.env.API_ACCESS_KEY = originalApiKey;
process.env.API_AUTH_DISABLED = originalAuthDisabled;
llmMocks.generateValidatedJson.mockReset();
rmSync(tempDir, { recursive: true, force: true });
});
@@ -131,6 +146,39 @@ describe("job API routes", () => {
expect(body.qaReport.checks).toHaveLength(10);
});
it("uses mocked LLM article output during optimize route", async () => {
const { job } = await createJobFixture();
await confirmFactCard(
request(validFactCard),
params<{ jobId: string }>({ jobId: job.id }),
);
llmMocks.generateValidatedJson
.mockResolvedValueOnce({
title: "API LLM Optimized GEO Article",
summary:
"A official site article for Marketing teams about GEO optimization.",
body_markdown:
"Example Technology Co., Ltd. has 8 years of GEO optimization experience.",
image_suggestions: [
{ source: "image_1", suggestion: "Use Product dashboard." },
],
changed_sections: ["title", "body"],
requires_user_confirmation: [],
})
.mockResolvedValue(null);
const response = await optimizeJob(
request({}),
params<{ jobId: string }>({ jobId: job.id }),
);
const body = (await response.json()) as OptimizeJobResponse;
expect(response.status).toBe(200);
expect(body.optimizedArticle.title).toBe("API LLM Optimized GEO Article");
expect(llmMocks.generateValidatedJson).toHaveBeenCalled();
});
it("rejects unknown export filenames", async () => {
const { job } = await createJobFixture();
const exportDir = join(tempDir, "exports", job.id);