import { Database } from "bun:sqlite"; import { expect, test } from "bun:test"; import { createApp } from "../src/server/app.ts"; import type { Config } from "../src/server/config.ts"; import { RESERVED_NAMES } from "../src/server/reserved.ts"; const config: Config = { domain: "kepr.example", dataDir: "data", adminPasswordHash: "$argon2id$x", bucket: { kind: "s3", endpoint: "https://s3.fr-par.scw.cloud", name: "koh", region: "fr-par", accessKeyId: "read", secretAccessKey: "only", }, }; const registered = () => createApp(config, new Database(":memory:")) .routes.filter((route) => !(route.method === "ALL" && route.path === "/*")) .map((route) => `${route.method} ${route.path}`); test("the app registers exactly the routes in docs/routes.txt, both ways (§26)", async () => { const listed = (await Bun.file("docs/routes.txt").text()) .split("\n") .filter((line) => line.trim() !== "" && !line.startsWith("#")); const routes = new Set(registered()); expect(registered().filter((route) => !listed.includes(route))).toEqual([]); expect(listed.filter((route) => !routes.has(route))).toEqual([]); }); test("every literal first segment of a route is a reserved repo name", () => { const firsts = registered() .map((route) => route.split(" ")[1]?.split("/")[1] ?? "") .filter((segment) => segment !== "" && !segment.startsWith(":")); expect([...new Set(firsts)].sort()).toEqual([...RESERVED_NAMES].sort()); }); test("responses carry the §25 content security policy", async () => { const response = await createApp(config, new Database(":memory:")).request("/"); const csp = response.headers.get("content-security-policy") ?? ""; expect(csp).toContain("script-src 'self'"); expect(csp).not.toContain("unsafe-inline"); expect(csp).toContain("img-src 'self' https://s3.fr-par.scw.cloud"); expect(csp).toContain("frame-ancestors 'none'"); expect(response.headers.get("x-content-type-options")).toBe("nosniff"); }); test("request bodies over 64 KB are refused", async () => { const app = createApp(config, new Database(":memory:")); app.post("/echo", async (c) => c.text(String((await c.req.text()).length))); const post = (bytes: number) => app.request("/echo", { method: "POST", body: "x".repeat(bytes) }); expect((await post(64 * 1024)).status).toBe(200); expect((await post(64 * 1024 + 1)).status).toBe(413); });