import type { Database } from "bun:sqlite";
import { beforeAll, describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, unlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Hono } from "hono";
import { openBucket } from "../src/bucket/reader.ts";
import { syncAll } from "../src/bucket/sync.ts";
import { openMirror } from "../src/mirror/store.ts";
import { createApp } from "../src/server/app.ts";
import { openKeprDb } from "../src/server/db.ts";
import { makeRepos } from "./fixture.ts";
let app: Hono;
let kepr: Database;
let dataDir: string;
let alphaSaves: { id: string; message: string; is_checkpoint: boolean }[];
let vaultSave: string;
const body = async (path: string) => (await app.request(path)).text();
// alpha (public): a save, a checkpoint holding a draft no real save keeps, then a second save.
// vault (private): one save whose message, category, language and ID must never show.
beforeAll(async () => {
const repos = await makeRepos(["alpha", "vault"]);
const root = join(repos.bucketDir, "..");
await writeFile(join(root, "work/alpha/draft.txt"), "a draft only a checkpoint holds\n");
repos.koh("alpha", "checkpoint", "checkpoint message never shown");
await unlink(join(root, "work/alpha/draft.txt"));
await writeFile(join(root, "work/alpha/README.md"), "# alpha\n\nThe alpha project, grown.\n");
await writeFile(join(root, "work/alpha/main.ts"), "export const a = 1;\n");
repos.koh("alpha", "save", "grow alpha");
// HEAD is a checkpoint adding a new language: none of it may reach a public stat.
await writeFile(join(root, "work/alpha/tool.py"), "print(1)\n");
repos.koh("alpha", "checkpoint", "checkpoint at head adds python");
repos.koh("alpha", "offer");
await writeFile(join(root, "work/vault/main.zig"), "const x = 1;\n");
repos.koh("vault", "save", "vault message");
alphaSaves = JSON.parse(repos.koh("alpha", "view", "--json")).saves;
vaultSave = JSON.parse(repos.koh("vault", "view", "--json")).head_id;
dataDir = await mkdtemp(join(tmpdir(), "kepr-front-"));
kepr = openKeprDb(dataDir);
const bucket = openBucket({ kind: "folder", path: repos.bucketDir });
await syncAll({
bucket,
kepr,
mirrorDir: join(dataDir, "mirror"),
});
kepr.run("UPDATE repos SET public = 1, category = 'tools' WHERE name = 'alpha'");
kepr.run("UPDATE repos SET category = 'secretcat' WHERE name = 'vault'");
for (const [key, value] of [
["handle", "ada"],
["tagline", "A library of small tools."],
["bio", "Writes small tools."],
["location", "Lisbon"],
["link", "https://ada.example"],
["building_since", "may 2026"],
]) {
kepr.query("INSERT INTO settings (key, value) VALUES (?, ?)").run(key, value);
}
kepr.run(
`INSERT INTO issues (repo, number, title, body, type, filed_at, hidden) VALUES
('alpha', 1, 'a', 'b', 'bug', 1, 0), ('alpha', 2, 'c', 'd', 'bug', 1, 1),
('vault', 1, 'e', 'f', 'bug', 1, 0)`,
);
kepr.run(
`INSERT INTO downloads (repo, version, platform, count) VALUES
('alpha', '1.0.0', 'linux-x86_64', 7), ('alpha', '0.9.0', 'linux-x86_64', 100),
('vault', '1.0.0', 'linux-x86_64', 1000)`,
);
kepr.run("INSERT INTO release_settings (repo, version, hidden) VALUES ('alpha', '0.9.0', 1)");
app = createApp(
{
domain: "kepr.example",
dataDir,
adminPasswordHash: "$argon2id$x",
bucket: { kind: "folder", path: repos.bucketDir },
},
kepr,
);
});
const checkpoints = () => alphaSaves.filter((save) => save.is_checkpoint).map((save) => save.id);
const realSaves = () => alphaSaves.filter((save) => !save.is_checkpoint);
describe("front page", () => {
test("counts distinct objects of public repos' real saves only", async () => {
// alpha's real saves hold .kohignore, two READMEs and main.ts; its mirror also holds the
// draft and tool.py only checkpoints kept, and the private repo's objects are elsewhere.
const mirror = openMirror(join(dataDir, "mirror"), "alpha");
expect(mirror.query("SELECT COUNT(*) AS n FROM objects").get()).toEqual({ n: 6 });
expect(await body("/")).toContain("The library knows 4 things.");
});
test("the ticker, cloud and cards show public saves, never checkpoints or private repos", async () => {
const html = await body("/");
expect(html).toContain('
');
expect(html).toContain("grow alpha");
expect(html).toContain("release alpha");
expect(html).toContain('class="tc-tag tc-5">tools<');
expect(html).toContain('class="tc-tag tc-5">TypeScript<');
expect(html).toContain('2 saves');
expect(html).toContain("The alpha project, grown.");
expect(html).toContain("A library of small tools.");
expect(html).toContain('href="/docs">run your own library →');
for (const secret of [
"checkpoint message",
"vault",
"secretcat",
"Zig",
...checkpoints(),
vaultSave,
]) {
expect(html).not.toContain(secret);
}
});
test("a checkpoint at head adds nothing to the public stats", async () => {
const mirror = openMirror(join(dataDir, "mirror"), "alpha");
const state = (key: string) => mirror.query("SELECT value FROM state WHERE key = ?").get(key);
expect(checkpoints()).toContain((state("head") as { value: string }).value);
// Lines and languages are counted at the newest real save, so tool.py is in neither.
expect(state("stats_save")).toEqual({ value: realSaves()[0]?.id });
for (const path of ["/", "/@ada", "/alpha"]) {
const html = await body(path);
expect(html).toContain("TypeScript");
expect(html).not.toContain("Python");
}
expect(await body("/browse")).not.toContain("Python");
});
test("the nav has browse, docs, support and log in, as Kepr 2's", async () => {
const html = await body("/");
expect(html).toContain(
'browsedocssupportlog in',
);
});
test("browse lists public repos as Kepr 2's cards: name, saves, description, category", async () => {
const html = await body("/browse");
expect(html).toContain('');
expect(html).toContain(`${realSaves().length} saves`);
expect(html).toContain('
');
expect(html).toContain("building since may 2026");
expect(html).toContain("◈ operator");
expect(html).toContain("Writes small tools.");
expect(html).toContain("Lisbon");
expect(html).toContain('href="https://ada.example"');
expect(html).toContain('TypeScript');
expect(html).toContain('');
});
test("an uploaded signature is served from data/assets and replaces the handle", async () => {
await mkdir(join(dataDir, "assets"), { recursive: true });
await writeFile(join(dataDir, "assets/sig.svg"), "");
kepr.run("INSERT INTO settings (key, value) VALUES ('signature', 'sig.svg')");
try {
expect(await body("/@ada")).toContain(
'',
);
const asset = await app.request("/static/assets/sig.svg");
expect(asset.status).toBe(200);
expect(asset.headers.get("content-type")).toContain("image/svg+xml");
await mkdir(join(dataDir, "assets/logos"));
await writeFile(join(dataDir, "assets/logos/mark.svg"), "");
expect((await app.request("/static/assets/logos/mark.svg")).status).toBe(200);
// `..` spelled so the URL parser leaves it for the route, and a backslash; kepr.db sits
// right above assets/, so any of these reaching the disk would serve it.
for (const path of [
"/static/assets/../kepr.db",
"/static/assets/..%2Fkepr.db",
"/static/assets/%2e%2e%2fkepr.db",
"/static/assets/..%5Ckepr.db",
"/static/assets%2F..%2Fkepr.db",
"/static/%00",
"/static/assets/%00",
"/static/css/%01kepr.css",
]) {
expect((await app.request(path)).status).toBe(404);
}
} finally {
kepr.run("DELETE FROM settings WHERE key = 'signature'");
}
});
test("with no handle set there is no profile", async () => {
kepr.run("UPDATE settings SET value = '' WHERE key = 'handle'");
try {
expect((await app.request("/@ada")).status).toBe(404);
} finally {
kepr.run("UPDATE settings SET value = 'ada' WHERE key = 'handle'");
}
});
});
describe("docs and support", () => {
test("render the repo's Markdown, links left as written", async () => {
const docs = await body("/docs");
expect(docs).toContain("
Your work, in public
");
expect(docs).toContain("/setup");
const support = await body("/support");
expect(support).toContain('self-hosting docs');
expect(support).toContain(' {
const css = await Bun.file(join(import.meta.dir, "../public/css/kepr.css")).text();
const blocks = [...css.matchAll(/@media \(prefers-reduced-motion: reduce\) \{([\s\S]*?)\n\}/g)]
.map((m) => m[1] as string)
.join("\n");
for (const selector of [".ticker-row", ".ticker-track", ".hex-col", ".tri-outer"]) {
expect(blocks).toContain(selector);
}
expect(blocks).toContain("animation: none;");
});
test("a file two public repos share is one thing the library knows", async () => {
const repos = await makeRepos(["one", "two"]);
for (const repo of ["one", "two"]) {
await writeFile(join(repos.bucketDir, "..", "work", repo, "LICENSE"), "the same licence\n");
repos.koh(repo, "save", "add the licence");
repos.koh(repo, "offer");
}
const dir = await mkdtemp(join(tmpdir(), "kepr-front-"));
const db = openKeprDb(dir);
const bucket = openBucket({ kind: "folder", path: repos.bucketDir });
await syncAll({ bucket, kepr: db, mirrorDir: join(dir, "mirror") });
db.run("UPDATE repos SET public = 1");
const shared = createApp(
{
domain: "kepr.example",
dataDir: dir,
adminPasswordHash: "$argon2id$x",
bucket: { kind: "folder", path: repos.bucketDir },
},
db,
);
// Two READMEs that differ; koh's default .kohignore and the licence, each shared by both.
expect(await (await shared.request("/")).text()).toContain("The library knows 4 things.");
});