AkurAI Build
Menu

popagent

public

Latest change 87f51a535b92b11ccaf217f96b05a14c907d8eac - Store project documentation in PostgreSQL by Ólafur Búi Ólafsson

import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { DocumentationFiles, DocumentationConflictError } from "./documentation-files";
import { documentationIndex } from "./documentation-index";
import { storage } from "./storage";

const workspaceId = `docs-${crypto.randomUUID()}`;
const repositoryPath = `docs-${crypto.randomUUID()}`;
const docs = new DocumentationFiles();

beforeAll(async () => {
  await documentationIndex.init();
  await storage.db.none(`
    INSERT INTO popagent_agent_workspaces (id, name, repository_path)
    VALUES ($1, 'Documentation test', $2)
  `, [workspaceId, repositoryPath]);
});

afterAll(async () => {
  await storage.db.none("DELETE FROM popagent_agent_workspaces WHERE id = $1", [workspaceId]);
});

describe("DocumentationFiles", () => {
  test("creates project folders and lists contained Markdown pages", async () => {
    await docs.createFolder(workspaceId, "Architecture/Decisions");
    await docs.save(workspaceId, { path: "Architecture/overview.md", content: "# System overview\n" });
    await docs.save(workspaceId, { path: "Architecture/Decisions/storage.md", content: "# Storage\n" });

    const tree = await docs.list(workspaceId);
    expect(tree.folders.map(({ path }) => path)).toEqual(["Architecture", "Architecture/Decisions"]);
    expect(tree.pages).toEqual([
      expect.objectContaining({ path: "Architecture/Decisions/storage.md", title: "Storage" }),
      expect.objectContaining({ path: "Architecture/overview.md", title: "System overview" }),
    ]);
  });

  test("creates, updates, moves, and deletes Markdown using content revisions", async () => {
    const created = await docs.save(workspaceId, { path: "Operations/deploy.md", content: "# Deploy\n" });
    await expect(docs.save(workspaceId, { path: created.path, content: "stale", revision: "bad" }))
      .rejects.toBeInstanceOf(DocumentationConflictError);
    const updated = await docs.save(workspaceId, { path: created.path, content: "# Release\n", revision: created.revision });
    expect(updated.revision).not.toBe(created.revision);
    const moved = await docs.move(workspaceId, updated.path, "Runbooks/release.md", updated.revision);
    expect(moved.path).toBe("Runbooks/release.md");
    await expect(docs.remove(workspaceId, moved.path, created.revision))
      .rejects.toBeInstanceOf(DocumentationConflictError);
    await docs.remove(workspaceId, moved.path, moved.revision);
    await expect(docs.read(workspaceId, moved.path)).rejects.toThrow("Documentation page not found");
  });

  test("deletes a folder and all descendant pages", async () => {
    await docs.save(workspaceId, { path: "History/2026/launch.md", content: "# Launch\n" });
    await docs.save(workspaceId, { path: "History/archive.md", content: "# Archive\n" });
    expect(await docs.removeFolder(workspaceId, "History")).toEqual([
      "History/2026/launch.md",
      "History/archive.md",
    ]);
    expect((await docs.list(workspaceId)).folders.some(({ path }) => path.startsWith("History"))).toBe(false);
  });

  test("rejects traversal, hidden paths, and non-Markdown files", async () => {
    await expect(docs.save(workspaceId, { path: "../secret.md", content: "no" })).rejects.toThrow("Invalid documentation path");
    await expect(docs.save(workspaceId, { path: ".private/page.md", content: "no" })).rejects.toThrow("Invalid documentation path");
    await expect(docs.save(workspaceId, { path: "notes.txt", content: "no" })).rejects.toThrow("Invalid documentation path");
  });
});