feat: implement tests for SupabaseProvider and GitProvider, and refactor StorageProvider tests

This commit is contained in:
doum1004
2026-04-11 00:50:41 -04:00
parent a346a2f2ed
commit 0d621759cf
5 changed files with 264 additions and 108 deletions

View File

@@ -6,7 +6,7 @@ export class SupabaseProvider implements StorageProvider {
private client: any;
private wikiId: string;
private constructor(client: any, wikiId: string) {
constructor(client: any, wikiId: string) {
this.client = client;
this.wikiId = wikiId;
}

View File

@@ -0,0 +1,69 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { mkdtemp, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { WikiManager } from "../src/lib/wiki.ts";
import type { StorageProvider } from "../src/types.ts";
let testDir: string;
let provider: StorageProvider;
beforeEach(async () => {
testDir = await mkdtemp(join(tmpdir(), "llmwiki-fs-"));
provider = new WikiManager(testDir);
});
afterEach(async () => {
await rm(testDir, { recursive: true, force: true });
});
describe("FilesystemProvider", () => {
it("writePage + readPage round-trips content", async () => {
await provider.writePage("test.md", "hello world");
const content = await provider.readPage("test.md");
expect(content).toBe("hello world");
});
it("readPage returns null for missing page", async () => {
const content = await provider.readPage("nonexistent.md");
expect(content).toBeNull();
});
it("pageExists returns false for missing page", async () => {
expect(await provider.pageExists("nope.md")).toBe(false);
});
it("pageExists returns true after write", async () => {
await provider.writePage("exists.md", "content");
expect(await provider.pageExists("exists.md")).toBe(true);
});
it("appendPage returns false for missing page", async () => {
const ok = await provider.appendPage("missing.md", "more");
expect(ok).toBe(false);
});
it("appendPage appends to existing page", async () => {
await provider.writePage("page.md", "first\n");
const ok = await provider.appendPage("page.md", "second");
expect(ok).toBe(true);
const content = await provider.readPage("page.md");
expect(content).toBe("first\nsecond");
});
it("listPages returns written markdown files", async () => {
await provider.writePage("a.md", "a");
await provider.writePage("sub/b.md", "b");
const pages = await provider.listPages();
expect(pages).toContain("a.md");
expect(pages).toContain("sub/b.md");
});
it("listPages with dir scopes to subdirectory", async () => {
await provider.writePage("root.md", "r");
await provider.writePage("sub/child.md", "c");
const pages = await provider.listPages("sub");
expect(pages).toContain("sub/child.md");
expect(pages).not.toContain("root.md");
});
});

66
test/git-provider.test.ts Normal file
View File

@@ -0,0 +1,66 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { mkdtemp, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import { execFile } from "child_process";
import { promisify } from "util";
import { GitProvider } from "../src/lib/git-provider.ts";
import * as git from "../src/lib/git.ts";
import type { StorageProvider } from "../src/types.ts";
const exec = promisify(execFile);
let gitDir: string;
let gitProvider: StorageProvider;
beforeEach(async () => {
gitDir = await mkdtemp(join(tmpdir(), "llmwiki-git-"));
await git.init(gitDir);
// Configure git user for CI environments
await exec("git", ["config", "user.name", "Test"], { cwd: gitDir });
await exec("git", ["config", "user.email", "test@test.com"], { cwd: gitDir });
gitProvider = new GitProvider(gitDir);
});
afterEach(async () => {
await rm(gitDir, { recursive: true, force: true });
});
describe("GitProvider", () => {
it("writePage stores content and auto-commits", async () => {
await gitProvider.writePage("test.md", "hello");
const content = await gitProvider.readPage("test.md");
expect(content).toBe("hello");
const log = await git.log(gitDir, 1);
expect(log.ok).toBe(true);
expect(log.output).toContain("update test.md");
});
it("appendPage auto-commits on success", async () => {
await gitProvider.writePage("page.md", "first\n");
await gitProvider.appendPage("page.md", "second");
const log = await git.log(gitDir, 2);
expect(log.ok).toBe(true);
expect(log.output).toContain("append to page.md");
});
it("appendPage does not commit on missing page", async () => {
const ok = await gitProvider.appendPage("missing.md", "nope");
expect(ok).toBe(false);
const log = await git.log(gitDir, 1);
expect(log.output).not.toContain("append to missing.md");
});
it("readPage returns null for missing page", async () => {
const content = await gitProvider.readPage("nope.md");
expect(content).toBeNull();
});
it("listPages works like filesystem", async () => {
await gitProvider.writePage("a.md", "a");
await gitProvider.writePage("sub/b.md", "b");
const pages = await gitProvider.listPages();
expect(pages).toContain("a.md");
expect(pages).toContain("sub/b.md");
});
});

View File

@@ -4,7 +4,6 @@ import { join } from "path";
import { tmpdir } from "os";
import { createProvider } from "../src/lib/storage.ts";
import { GitProvider } from "../src/lib/git-provider.ts";
import * as git from "../src/lib/git.ts";
import type { StorageProvider, WikiConfig } from "../src/types.ts";
function makeConfig(backend: WikiConfig["backend"] = "filesystem"): WikiConfig {
@@ -18,11 +17,9 @@ function makeConfig(backend: WikiConfig["backend"] = "filesystem"): WikiConfig {
}
let testDir: string;
let provider: StorageProvider;
beforeEach(async () => {
testDir = await mkdtemp(join(tmpdir(), "llmwiki-storage-"));
provider = await createProvider(makeConfig("filesystem"), testDir);
});
afterEach(async () => {
@@ -30,7 +27,8 @@ afterEach(async () => {
});
describe("createProvider", () => {
it("creates a filesystem provider", () => {
it("creates a filesystem provider", async () => {
const provider = await createProvider(makeConfig("filesystem"), testDir);
expect(provider).toBeDefined();
expect(provider.readPage).toBeInstanceOf(Function);
expect(provider.writePage).toBeInstanceOf(Function);
@@ -56,106 +54,3 @@ describe("createProvider", () => {
).rejects.toThrow('Unknown storage backend: "unknown"');
});
});
describe("StorageProvider contract (filesystem)", () => {
it("writePage + readPage round-trips content", async () => {
await provider.writePage("test.md", "hello world");
const content = await provider.readPage("test.md");
expect(content).toBe("hello world");
});
it("readPage returns null for missing page", async () => {
const content = await provider.readPage("nonexistent.md");
expect(content).toBeNull();
});
it("pageExists returns false for missing page", async () => {
expect(await provider.pageExists("nope.md")).toBe(false);
});
it("pageExists returns true after write", async () => {
await provider.writePage("exists.md", "content");
expect(await provider.pageExists("exists.md")).toBe(true);
});
it("appendPage returns false for missing page", async () => {
const ok = await provider.appendPage("missing.md", "more");
expect(ok).toBe(false);
});
it("appendPage appends to existing page", async () => {
await provider.writePage("page.md", "first\n");
const ok = await provider.appendPage("page.md", "second");
expect(ok).toBe(true);
const content = await provider.readPage("page.md");
expect(content).toBe("first\nsecond");
});
it("listPages returns written markdown files", async () => {
await provider.writePage("a.md", "a");
await provider.writePage("sub/b.md", "b");
const pages = await provider.listPages();
expect(pages).toContain("a.md");
expect(pages).toContain("sub/b.md");
});
it("listPages with dir scopes to subdirectory", async () => {
await provider.writePage("root.md", "r");
await provider.writePage("sub/child.md", "c");
const pages = await provider.listPages("sub");
expect(pages).toContain("sub/child.md");
expect(pages).not.toContain("root.md");
});
});
describe("GitProvider", () => {
let gitDir: string;
let gitProvider: StorageProvider;
beforeEach(async () => {
gitDir = await mkdtemp(join(tmpdir(), "llmwiki-git-"));
await git.init(gitDir);
gitProvider = await createProvider(makeConfig("git"), gitDir);
});
afterEach(async () => {
await rm(gitDir, { recursive: true, force: true });
});
it("writePage stores content and auto-commits", async () => {
await gitProvider.writePage("test.md", "hello");
const content = await gitProvider.readPage("test.md");
expect(content).toBe("hello");
const log = await git.log(gitDir, 1);
expect(log.ok).toBe(true);
expect(log.output).toContain("update test.md");
});
it("appendPage auto-commits on success", async () => {
await gitProvider.writePage("page.md", "first\n");
await gitProvider.appendPage("page.md", "second");
const log = await git.log(gitDir, 2);
expect(log.ok).toBe(true);
expect(log.output).toContain("append to page.md");
});
it("appendPage does not commit on missing page", async () => {
const ok = await gitProvider.appendPage("missing.md", "nope");
expect(ok).toBe(false);
const log = await git.log(gitDir, 1);
expect(log.output).not.toContain("append to missing.md");
});
it("readPage returns null for missing page", async () => {
const content = await gitProvider.readPage("nope.md");
expect(content).toBeNull();
});
it("listPages works like filesystem", async () => {
await gitProvider.writePage("a.md", "a");
await gitProvider.writePage("sub/b.md", "b");
const pages = await gitProvider.listPages();
expect(pages).toContain("a.md");
expect(pages).toContain("sub/b.md");
});
});

View File

@@ -0,0 +1,126 @@
import { describe, it, expect, beforeEach } from "bun:test";
import { SupabaseProvider } from "../src/lib/supabase-provider.ts";
// In-memory store simulating Supabase table
let store: Map<string, { wiki_id: string; path: string; content: string }>;
function mockClient() {
function makeEqChain(wikiId: string, isCount: boolean) {
return {
eq(col2: string, val2: string) {
const key = `${wikiId}:${val2}`;
if (isCount) {
const exists = store.has(key);
return { count: exists ? 1 : 0, error: null };
}
return {
maybeSingle() {
const row = store.get(key);
return { data: row ? { content: row.content } : null, error: null };
},
};
},
like(_col: string, _pattern: string) {
return {
order() {
return { data: [], error: null };
},
};
},
order(_col: string) {
const results: { path: string }[] = [];
for (const row of store.values()) {
if (row.wiki_id === wikiId) {
results.push({ path: row.path });
}
}
return { data: results, error: null };
},
};
}
return {
from(_table: string) {
return {
select(fields: string, opts?: { count?: string; head?: boolean }) {
const isCount = opts?.count === "exact";
return {
eq(col: string, val: string) {
return makeEqChain(val, isCount);
},
};
},
upsert(row: any, _opts?: any) {
const key = `${row.wiki_id}:${row.path}`;
store.set(key, { wiki_id: row.wiki_id, path: row.path, content: row.content });
return { error: null };
},
};
},
};
}
let provider: SupabaseProvider;
beforeEach(() => {
store = new Map();
provider = new SupabaseProvider(mockClient(), "test-wiki");
});
describe("SupabaseProvider", () => {
it("writePage + readPage round-trips content", async () => {
await provider.writePage("wiki/test.md", "hello world");
const content = await provider.readPage("wiki/test.md");
expect(content).toBe("hello world");
});
it("readPage returns null for missing page", async () => {
const content = await provider.readPage("nonexistent.md");
expect(content).toBeNull();
});
it("writePage overwrites existing content", async () => {
await provider.writePage("wiki/page.md", "v1");
await provider.writePage("wiki/page.md", "v2");
const content = await provider.readPage("wiki/page.md");
expect(content).toBe("v2");
});
it("appendPage appends to existing page", async () => {
await provider.writePage("wiki/page.md", "first\n");
const ok = await provider.appendPage("wiki/page.md", "second");
expect(ok).toBe(true);
const content = await provider.readPage("wiki/page.md");
expect(content).toBe("first\nsecond");
});
it("appendPage returns false for missing page", async () => {
const ok = await provider.appendPage("missing.md", "nope");
expect(ok).toBe(false);
});
it("pageExists returns false for missing page", async () => {
expect(await provider.pageExists("nope.md")).toBe(false);
});
it("pageExists returns true after write", async () => {
await provider.writePage("wiki/exists.md", "content");
expect(await provider.pageExists("wiki/exists.md")).toBe(true);
});
it("listPages returns stored pages", async () => {
await provider.writePage("wiki/a.md", "a");
await provider.writePage("wiki/b.md", "b");
const pages = await provider.listPages();
expect(pages).toContain("wiki/a.md");
expect(pages).toContain("wiki/b.md");
});
it("listPages only returns .md files", async () => {
await provider.writePage("wiki/page.md", "content");
await provider.writePage("wiki/image.png", "binary");
const pages = await provider.listPages();
expect(pages).toContain("wiki/page.md");
expect(pages).not.toContain("wiki/image.png");
});
});