hidden.test.ts

3.7 KB · raw

1// Covers per-repo hidden paths (June, 2026-09-30): gone from every page, the stats and new2// tarballs, kept hidden through later syncs, handed to Koh under `.kepr/hidden/`, and back on unhide.34import { beforeAll, describe, expect, test } from "bun:test";5import { mkdir, writeFile } from "node:fs/promises";6import { join } from "node:path";7import { openBucket } from "../src/bucket/reader.ts";8import { syncAll } from "../src/bucket/sync.ts";9import { Browser, type Instance, instance } from "./browser.ts";10import { makeRepos } from "./fixture.ts";1112describe("hidden paths", () => {13	let inst: Instance;14	let browser: Browser;15	let repos: Awaited<ReturnType<typeof makeRepos>>;16	let work: string;17	const status = async (path: string) => (await inst.app.request(path)).status;18	const text = async (path: string) => (await inst.app.request(path)).text();19	const hide = (hidden_paths: string) =>20		browser.post("/admin/repos/app", { public: "1", hidden_paths });21	const sync = () =>22		syncAll({23			bucket: openBucket({ kind: "folder", path: repos.bucketDir }),24			kepr: inst.kepr,25			mirrorDir: join(inst.dataDir, "mirror"),26		});2728	beforeAll(async () => {29		repos = await makeRepos(["app"]);30		work = join(repos.bucketDir, "..", "work", "app");31		await mkdir(join(work, "private"));32		await writeFile(join(work, "private", "notes.md"), "# private\n");33		await writeFile(join(work, "tool.py"), "print(1)\n");34		await writeFile(join(work, "main.ts"), "export const a = 1;\n");35		repos.koh("app", "save", "add sources");36		repos.koh("app", "offer");37		inst = await instance(repos.bucketDir);38		browser = new Browser(inst.app);39		await browser.signIn();40		await hide("");41	});4243	test("refuses unsafe paths and changes nothing", async () => {44		for (const path of ["../x", "/etc", "a//b", "x".repeat(501)]) {45			expect((await hide(path)).status).toBe(400);46		}47		expect(await status("/app/files/tool.py")).toBe(200);48	});4950	test("a hidden file or folder is gone everywhere and handed to Koh", async () => {51		const saved = await hide("tool.py\r\nprivate/\n");52		expect(saved.status).toBe(303);53		for (const path of ["/app/files/tool.py", "/app/raw/tool.py", "/app/raw/private/notes.md"]) {54			expect(`${path} ${await status(path)}`).toBe(`${path} 404`);55		}56		expect(await status("/app/files/main.ts")).toBe(200);57		const page = await text("/app");58		expect(page).not.toContain("tool.py");59		expect(page).not.toContain("notes.md");60		expect(page).not.toContain("Python");61		const head = (await inst.app.request("/app/archive/HEAD.tar.gz")).headers.get("location");62		const tarball = new Uint8Array(await (await inst.app.request(head ?? "")).arrayBuffer());63		const listed = Bun.spawnSync(["tar", "-tzf", "-"], { stdin: tarball }).stdout.toString();64		expect(listed).toContain("main.ts");65		expect(listed).not.toContain("tool.py");66		expect(await Bun.file(join(repos.bucketDir, ".kepr/hidden/app")).text()).toBe(67			"tool.py\nprivate\n",68		);69		expect(await (await browser.get("/admin/repos?repo=app")).text()).toContain(70			">tool.py\nprivate</textarea>",71		);72	});7374	test("stays hidden in saves that arrive later", async () => {75		await writeFile(join(work, "tool.py"), "print(2)\n");76		repos.koh("app", "save", "change the tool");77		repos.koh("app", "offer");78		await sync();79		expect(await status("/app/files/tool.py")).toBe(404);80		expect(await text("/app")).toContain("change the tool");81	});8283	test("unhiding brings the files back", async () => {84		expect((await hide("")).status).toBe(303);85		expect(await status("/app/files/tool.py")).toBe(200);86		expect(await text("/app/raw/tool.py")).toBe("print(2)\n");87		expect(await status("/app/raw/private/notes.md")).toBe(200);88		expect(await Bun.file(join(repos.bucketDir, ".kepr/hidden/app")).text()).toBe("");89	});90});