test: cover llm workflow through api
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user