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", "log", "--json")).saves;
vaultSave = JSON.parse(repos.koh("vault", "log", "--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"),
tarballDir: join(dataDir, "tarballs"),
});
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 ["/", "/browse", "/@ada", "/alpha"]) {
const html = await body(path);
expect(html).toContain("TypeScript");
expect(html).not.toContain("Python");
}
});
test("the nav has browse, docs and support, and no login link", async () => {
const html = await body("/");
expect(html).toContain(
'
browse docs support ',
);
expect(html).not.toContain("sign-in");
});
test("browse lists public repos as cards", async () => {
const html = await body("/browse");
expect(html).toContain('
');
expect(html).not.toContain("vault");
// One tick per real save.
expect(html.match(/ {
test("a handle that isn't the configured one is a 404", async () => {
expect((await app.request("/@ada")).status).toBe(200);
expect((await app.request("/@someone")).status).toBe(404);
expect((await app.request("/@")).status).toBe(404);
});
test("stats leave out private repos, checkpoints, hidden issues and hidden releases", async () => {
const numbers = [...(await body("/@ada")).matchAll(/profile-stat-number">([^<]*) m[1],
);
// repos, saves, issues, downloads
expect(numbers).toEqual(["1", String(realSaves().length), "1", "7"]);
});
test("recent activity and the hex backdrop use real public saves only", async () => {
const html = await body("/@ada");
for (const save of realSaves()) {
expect(html).toContain(`${save.id} `);
expect(html).toContain(`href="/alpha/saves/${save.id}">${save.message} `);
}
for (const id of checkpoints()) expect(html).not.toContain(id);
expect(html).not.toContain(vaultSave);
expect(html).not.toContain("vault");
expect(html).not.toContain("Zig");
});
test("shows the settings, the handle in the signature font, and the language bar", async () => {
const html = await body("/@ada");
expect(html).toContain('
ada ');
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("
Run your own library ");
expect(docs).toContain("
admin_password_hash");
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;");
});