87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
|
||
import { fetchDraft, fetchOutline } from "./server";
|
||
|
||
// 纯加载/降级逻辑(mock fetch,无网络/DOM):大纲解包/降级、草稿 404→null。
|
||
function mockFetch(status: number, body: unknown): void {
|
||
vi.stubGlobal(
|
||
"fetch",
|
||
vi.fn(async () =>
|
||
new Response(JSON.stringify(body), {
|
||
status,
|
||
headers: { "content-type": "application/json" },
|
||
}),
|
||
),
|
||
);
|
||
}
|
||
|
||
afterEach(() => {
|
||
vi.unstubAllGlobals();
|
||
});
|
||
|
||
describe("fetchOutline", () => {
|
||
it("returns the persisted chapters on 200", async () => {
|
||
mockFetch(200, { chapters: [{ no: 1, volume: 1, beats: ["开场"] }] });
|
||
|
||
const chapters = await fetchOutline("p1");
|
||
|
||
expect(chapters).toHaveLength(1);
|
||
expect(chapters[0]?.no).toBe(1);
|
||
});
|
||
|
||
it("returns empty array when the project has no outline", async () => {
|
||
mockFetch(200, { chapters: [] });
|
||
|
||
expect(await fetchOutline("p1")).toEqual([]);
|
||
});
|
||
|
||
it("falls back to empty array on any error (does not throw)", async () => {
|
||
mockFetch(404, { error: { code: "NOT_FOUND", message: "no project" } });
|
||
|
||
expect(await fetchOutline("missing")).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe("fetchDraft", () => {
|
||
it("returns the saved draft view on 200", async () => {
|
||
mockFetch(200, {
|
||
project_id: "p1",
|
||
chapter_no: 1,
|
||
volume: 1,
|
||
status: "draft",
|
||
version: 2,
|
||
content: "第一章正文",
|
||
length: 5,
|
||
});
|
||
|
||
const draft = await fetchDraft("p1", 1);
|
||
|
||
expect(draft?.content).toBe("第一章正文");
|
||
expect(draft?.version).toBe(2);
|
||
});
|
||
|
||
it("returns null when no draft exists (404 → empty editor)", async () => {
|
||
mockFetch(404, { error: { code: "NOT_FOUND", message: "no draft" } });
|
||
|
||
expect(await fetchDraft("p1", 9)).toBeNull();
|
||
});
|
||
});
|
||
|
||
// parseJsonBody 边界校验:防御被代理/网关污染的非对象或非 JSON 响应。
|
||
describe("parseJsonBody structural validation", () => {
|
||
it("throws on a 200 body that is not a JSON object (primitive)", async () => {
|
||
mockFetch(200, "surprise-string");
|
||
|
||
await expect(fetchDraft("p1", 1)).rejects.toThrow("响应体形状异常");
|
||
});
|
||
|
||
it("throws when the 200 body is not valid JSON", async () => {
|
||
vi.stubGlobal(
|
||
"fetch",
|
||
vi.fn(async () => new Response("<html>not json</html>", { status: 200 })),
|
||
);
|
||
|
||
await expect(fetchDraft("p1", 1)).rejects.toThrow("响应非 JSON");
|
||
});
|
||
});
|