test: cover llm workflow through api
This commit is contained in:
@@ -36,8 +36,11 @@ OPENAI_API_KEY=
|
|||||||
OPENAI_MODEL=gpt-4.1-mini
|
OPENAI_MODEL=gpt-4.1-mini
|
||||||
```
|
```
|
||||||
|
|
||||||
When no API key is configured, deterministic local fallbacks keep the workflow
|
When a DeepSeek or OpenAI-compatible key is configured, the workflow uses the
|
||||||
usable for tests and local review.
|
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:
|
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 { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
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 { POST as confirmFactCard } from "../jobs/[jobId]/confirm-fact-card/route";
|
||||||
import { GET as downloadExport } from "../jobs/[jobId]/exports/[fileName]/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.APP_DATA_DIR = originalDataDir;
|
||||||
process.env.API_ACCESS_KEY = originalApiKey;
|
process.env.API_ACCESS_KEY = originalApiKey;
|
||||||
process.env.API_AUTH_DISABLED = originalAuthDisabled;
|
process.env.API_AUTH_DISABLED = originalAuthDisabled;
|
||||||
|
llmMocks.generateValidatedJson.mockReset();
|
||||||
rmSync(tempDir, { recursive: true, force: true });
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,6 +146,39 @@ describe("job API routes", () => {
|
|||||||
expect(body.qaReport.checks).toHaveLength(10);
|
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 () => {
|
it("rejects unknown export filenames", async () => {
|
||||||
const { job } = await createJobFixture();
|
const { job } = await createJobFixture();
|
||||||
const exportDir = join(tempDir, "exports", job.id);
|
const exportDir = join(tempDir, "exports", job.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user