import { beforeAll, describe, expect, test } from "bun:test"; import { mkdtemp } 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 dataDir = await mkdtemp(join(tmpdir(), "kepr-pages-")); mirrorDir = join(dataDir, "mirror"); const kepr = openKeprDb(dataDir); for (const bucketDir of [fx.bucketDir, secretRepo.bucketDir]) { await syncAll({ bucket: openBucket({ kind: "folder", path: bucketDir }), kepr, mirrorDir }); } kepr.run( "UPDATE repos SET public = 1, install1 = 'nix profile add x', homepage = 'https://demo.example' WHERE name = 'demo'", ); 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('
');
expect(page).toContain('the site');
expect(page).not.toContain('href="javascript:');
expect(page).toContain("<script>alert");
expect(page).not.toContain("