Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"date-fns": "^2.30.0",
"decode-uri-component": "^0.2.1",
"dexie": "^3.2.5",
"diff": "^8.0.3",
"dom-to-image": "^2.6.0",
"dompurify": "^3.0.6",
"echarts": "^5.5.1",
Expand Down
9 changes: 9 additions & 0 deletions client/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

246 changes: 246 additions & 0 deletions client/src/api/pages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/**
* Tests for the unified pages API client.
*/
import { describe, expect, it } from "vitest";

import { useServerMock } from "@/api/client/__mocks__";

import type { HistoryPageDetails, HistoryPageSummary } from "./pages";
import {
createHistoryPage,
deleteHistoryPage,
fetchHistoryPage,
fetchHistoryPages,
savePage,
updateHistoryPage,
} from "./pages";

const { server, http } = useServerMock();

const TEST_HISTORY_ID = "abc123historyid";
const TEST_PAGE_ID = "def456pageid";
const TEST_REVISION_ID = "rev789revisionid";

const TEST_PAGE_SUMMARY: HistoryPageSummary = {
id: TEST_PAGE_ID,
history_id: TEST_HISTORY_ID,
title: "My Analysis Notes",
slug: null,
source_invocation_id: null,
published: false,
importable: false,
deleted: false,
latest_revision_id: TEST_REVISION_ID,
revision_ids: [TEST_REVISION_ID],
create_time: "2025-06-15T10:30:00Z",
update_time: "2025-06-15T12:45:00Z",
username: "test",
email_hash: "",
author_deleted: false,
model_class: "Page",
tags: [],
};

const TEST_PAGE_DETAILS: HistoryPageDetails = {
...TEST_PAGE_SUMMARY,
content: "# Analysis\n\nSome markdown content here.",
content_editor: "# Analysis\n\nSome markdown content here.",
content_format: "markdown",
edit_source: "user",
annotation: null,
};

describe("pages API", () => {
describe("fetchHistoryPages", () => {
it("returns list of pages for a history", async () => {
server.use(
http.get("/api/pages", ({ response }: any) => {

Check warning on line 57 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
return response(200).json([TEST_PAGE_SUMMARY]);
}) as any,

Check warning on line 59 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
);

const result = await fetchHistoryPages(TEST_HISTORY_ID);

expect(result).toEqual([TEST_PAGE_SUMMARY]);
});

it("returns empty list when history has no pages", async () => {
server.use(
http.get("/api/pages", ({ response }: any) => {

Check warning on line 69 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
return response(200).json([]);
}) as any,

Check warning on line 71 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
);

const result = await fetchHistoryPages(TEST_HISTORY_ID);

expect(result).toEqual([]);
});

it("throws on server error", async () => {
server.use(
http.get("/api/pages", ({ response }: any) => {

Check warning on line 81 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
return response("4XX").json({ err_msg: "History not found", err_code: 404 }, { status: 404 });
}) as any,

Check warning on line 83 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
);

await expect(fetchHistoryPages(TEST_HISTORY_ID)).rejects.toThrow();
});
});

describe("fetchHistoryPage", () => {
it("returns page details by id", async () => {
server.use(
http.get("/api/pages/{id}", ({ response }: any) => {

Check warning on line 93 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
return response(200).json(TEST_PAGE_DETAILS);
}) as any,

Check warning on line 95 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
);

const result = await fetchHistoryPage(TEST_PAGE_ID);

expect(result).toEqual(TEST_PAGE_DETAILS);
});

it("throws on page not found", async () => {
server.use(
http.get("/api/pages/{id}", ({ response }: any) => {

Check warning on line 105 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
return response("4XX").json({ err_msg: "Page not found", err_code: 404 }, { status: 404 });
}) as any,

Check warning on line 107 in client/src/api/pages.test.ts

View workflow job for this annotation

GitHub Actions / client-unit-test

Unexpected any. Specify a different type
);

await expect(fetchHistoryPage("nonexistent")).rejects.toThrow();
});
});

describe("createHistoryPage", () => {
const CREATE_PAYLOAD = {
content: "# New Page\n\nInitial content.",
content_format: "markdown",
title: "New Page",
history_id: TEST_HISTORY_ID,
};

it("returns created page details", async () => {
server.use(
http.post("/api/pages", ({ response }: any) => {
return response(200).json({
...TEST_PAGE_DETAILS,
title: "New Page",
content: "# New Page\n\nInitial content.",
});
}) as any,
);

const result = await createHistoryPage(CREATE_PAYLOAD);

expect(result.title).toBe("New Page");
expect(result.content).toBe("# New Page\n\nInitial content.");
expect(result.history_id).toBe(TEST_HISTORY_ID);
expect(result.id).toBe(TEST_PAGE_ID);
});

it("throws on creation error", async () => {
server.use(
http.post("/api/pages", ({ response }: any) => {
return response("4XX").json({ err_msg: "Cannot create page", err_code: 400 }, { status: 400 });
}) as any,
);

await expect(createHistoryPage(CREATE_PAYLOAD)).rejects.toThrow();
});
});

describe("updateHistoryPage", () => {
const UPDATE_PAYLOAD = {
content: "# Updated Content\n\nRevised analysis.",
content_format: "markdown",
title: "Updated Title",
};

it("returns updated page details", async () => {
server.use(
http.put("/api/pages/{id}", ({ response }: any) => {
return response(200).json({
...TEST_PAGE_DETAILS,
title: "Updated Title",
content: "# Updated Content\n\nRevised analysis.",
update_time: "2025-06-16T09:00:00Z",
});
}) as any,
);

const result = await updateHistoryPage(TEST_PAGE_ID, UPDATE_PAYLOAD);

expect(result.title).toBe("Updated Title");
expect(result.content).toBe("# Updated Content\n\nRevised analysis.");
expect(result.update_time).toBe("2025-06-16T09:00:00Z");
});

it("throws on update error", async () => {
server.use(
http.put("/api/pages/{id}", ({ response }: any) => {
return response("4XX").json({ err_msg: "Page is deleted", err_code: 400 }, { status: 400 });
}) as any,
);

await expect(updateHistoryPage(TEST_PAGE_ID, UPDATE_PAYLOAD)).rejects.toThrow();
});
});

describe("savePage", () => {
it("saves content via PUT with default edit_source", async () => {
server.use(
http.put("/api/pages/{id}", ({ response }: any) => {
return response(200).json({
...TEST_PAGE_DETAILS,
content: "# Saved Content",
edit_source: "user",
});
}) as any,
);

const result = await savePage(TEST_PAGE_ID, "# Saved Content");

expect(result.content).toBe("# Saved Content");
expect(result.edit_source).toBe("user");
});

it("saves content with custom edit_source", async () => {
server.use(
http.put("/api/pages/{id}", ({ response }: any) => {
return response(200).json({
...TEST_PAGE_DETAILS,
content: "# Agent Content",
edit_source: "agent",
});
}) as any,
);

const result = await savePage(TEST_PAGE_ID, "# Agent Content", "agent");

expect(result.content).toBe("# Agent Content");
expect(result.edit_source).toBe("agent");
});
});

describe("deleteHistoryPage", () => {
it("resolves without error on success", async () => {
server.use(
http.delete("/api/pages/{id}", ({ response }: any) => {
return response(204).empty();
}) as any,
);

await expect(deleteHistoryPage(TEST_PAGE_ID)).resolves.toBeUndefined();
});

it("throws on deletion error", async () => {
server.use(
http.delete("/api/pages/{id}", ({ response }: any) => {
return response("4XX").json({ err_msg: "Page not found", err_code: 404 }, { status: 404 });
}) as any,
);

await expect(deleteHistoryPage(TEST_PAGE_ID)).rejects.toThrow();
});
});
});
Loading
Loading