import { beforeAll, describe, expect, test } from "bun:test"; import { mkdtemp, 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 { fileAt, objectBytes } from "../src/mirror/queries.ts"; import { openMirror } from "../src/mirror/store.ts"; import { createApp } from "../src/server/app.ts"; import { openKeprDb } from "../src/server/db.ts"; import { type Fixture, makeKohBucket } from "./fixture.ts"; let fx: Fixture; let app: Hono; let mirrorDir: string; const get = (path: string) => app.request(path); const body = async (path: string) => (await get(path)).text(); beforeAll(async () => { fx = await makeKohBucket(); const secretRepo = await makeKohBucket("hidden"); const large = await makeKohBucket("large"); // Over 1 MB each: text with a two-byte "é" split by the 1 MB mark, and invalid UTF-8 with no NUL. await writeFile( join(large.repoDir, "big.txt"), `${"a".repeat(1024 * 1024 - 1)}é${"b".repeat(1000)}\n`, ); await writeFile(join(large.repoDir, "big.bin"), new Uint8Array(1024 * 1024 + 500).fill(0xff)); Bun.spawnSync(["koh3", "save", "add large files"], { cwd: large.repoDir, env: { PATH: process.env.PATH, HOME: join(large.repoDir, "../../home"), NO_COLOR: "1" }, }); const dataDir = await mkdtemp(join(tmpdir(), "kepr-pages-")); mirrorDir = join(dataDir, "mirror"); const kepr = openKeprDb(dataDir); for (const bucketDir of [fx.bucketDir, secretRepo.bucketDir, large.bucketDir]) { const bucket = openBucket({ kind: "folder", path: bucketDir }); await syncAll({ bucket, kepr, mirrorDir, tarballDir: join(dataDir, "tarballs") }); } kepr.run( "UPDATE repos SET public = 1, install1 = 'nix profile add x', homepage = 'https://demo.example' WHERE name = 'demo'", ); kepr.run("UPDATE repos SET public = 1 WHERE name = 'large'"); app = createApp( { domain: "kepr.example", dataDir, adminPasswordHash: "$argon2id$x", bucket: { kind: "folder", path: fx.bucketDir }, }, kepr, ); }); const [head, , second, first] = [0, 1, 2, 3].map((i) => () => fx.saveIds[i] as string); describe("the visibility gate", () => { test("a private repo and an unknown one are the same 404, everywhere", async () => { for (const suffix of ["", "/files/src/main.ts", "/raw/src/main.ts"]) { const hidden = await get(`/hidden${suffix}`); const missing = await get(`/nowhere${suffix}`); expect(hidden.status).toBe(404); expect(missing.status).toBe(404); expect((await hidden.text()).replaceAll("/hidden", "/nowhere")).toBe(await missing.text()); } }); }); describe("the repo page", () => { test("renders header, timeline, history and README at HEAD with a short cache", async () => { const response = await get("/demo"); expect(response.status).toBe(200); expect(response.headers.get("cache-control")).toBe("public, max-age=60"); const page = await response.text(); expect(page).toContain('

demo

'); // The README opens with an HTML comment and a raw

; neither is the intro. expect(/

(.*?)<\/p>/.exec(page)?.[1]).toBe( "A small repo for Kepr's tests.", ); expect(page).toContain( '', ); expect(page).toContain('href="https://demo.example"'); expect(page).toContain('TypeScript'); expect(page).toContain("3 saves"); expect(page).toContain('data-copy="nix profile add x"'); expect(page.match(/class="dot d\d"/g)?.length).toBe(3); expect(page).toContain("density = activity · position = time"); const history = [...page.matchAll(/class="history-id">([0-9a-f]+) m[1]); expect(history).toEqual([head(), second(), first()]); }); test("checkpoints never appear", async () => { expect(await body("/demo")).not.toContain(fx.saveIds[1] as string); }); test("the README points relative links and images at raw files and drops unsafe links", async () => { const page = await body("/demo"); expect(page).toContain('the guide'); expect(page).toContain('logo'); expect(page).toContain('the site'); expect(page).not.toContain('href="javascript:'); expect(page).toContain("<script>alert"); expect(page).not.toContain("