storage.test.ts

3.9 KB · raw

1// Covers §24's Storage section: sizes by repo and kind, kept tarballs with a delete each, and2// orphan cleanup that touches only Kepr's own files.34import { beforeAll, describe, expect, test } from "bun:test";5import { mkdir, writeFile } from "node:fs/promises";6import { dirname, join } from "node:path";7import { measureStorage } from "../src/admin/storage.ts";8import { openBucket } from "../src/bucket/reader.ts";9import { Browser, type Instance, instance } from "./browser.ts";10import { makeRepos, writeRelease } from "./fixture.ts";1112describe("storage view (§24)", () => {13	let inst: Instance;14	let operator: Browser;15	let bucketDir: string;16	let save: string;17	const exists = (key: string) => Bun.file(join(bucketDir, key)).exists();18	const put = async (key: string) => {19		await mkdir(dirname(join(bucketDir, key)), { recursive: true });20		await writeFile(join(bucketDir, key), "x");21	};22	let generation = "";23	const kept = () => `.kepr/tarballs/app/${generation}/${save}.tar.gz`;24	const orphans = [25		".kepr/tarballs/app/0-000000000000/aaaaaaaaaaaa.tar.gz",26		".kepr/tarballs/gone/0-000000000000/aaaaaaaaaaaa.tar.gz",27		".kepr/hidden/gone",28	];29	const unlisted = "app/releases/9.9.9/linux-x86_64/app";3031	beforeAll(async () => {32		const repos = await makeRepos(["app"]);33		bucketDir = repos.bucketDir;34		save = repos.saves.app as string;35		await writeRelease(bucketDir, "app", {36			version: "1.0.0",37			save,38			files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array(2048) }],39		});40		inst = await instance(bucketDir);41		operator = new Browser(inst.app);42		await operator.signIn();43		expect(await (await operator.get("/admin/storage")).text()).toContain("Not measured yet");44		await operator.post("/admin/repos/app", { public: "1" });45		expect((await inst.app.request(`/app/archive/${save}.tar.gz`)).status).toBe(200);46		const listed = new Bun.Glob(`.kepr/tarballs/app/*/${save}.tar.gz`).scanSync({47			cwd: bucketDir,48			dot: true,49		});50		generation = (Array.from(listed)[0] as string).split("/")[3] as string;51		for (const key of [...orphans, unlisted]) await put(key);52		await measure();53	});54	const measure = () =>55		measureStorage({56			bucket: openBucket({ kind: "folder", path: bucketDir }),57			kepr: inst.kepr,58			mirrorDir: join(inst.dataDir, "mirror"),59		});6061	test("shows each repo's history, builds and kept tarballs, and the orphans", async () => {62		const page = await (await operator.get("/admin/storage")).text();63		expect(page).toMatch(/<td>app<\/td>\s*<td>[\d.]+ K?B<\/td>\s*<td>2\.\d KB<\/td>/);64		expect(page).toContain(`${save}.tar.gz`);65		expect(page).toContain("3 orphans");66		expect(page).toContain("Measured just now");67	});6869	test("shows the last measurement, not the bucket as it is now", async () => {70		const late = ".kepr/tarballs/app/0-000000000000/bbbbbbbbbbbb.tar.gz";71		await put(late);72		expect(await (await operator.get("/admin/storage")).text()).toContain("3 orphans");73		await measure();74		expect(await (await operator.get("/admin/storage")).text()).toContain("4 orphans");75		orphans.push(late);76	});7778	test("deleting orphans removes only Kepr's stale files", async () => {79		expect((await operator.post("/admin/storage", { action: "delete_orphans" })).status).toBe(303);80		for (const key of orphans) expect(await exists(key)).toBe(false);81		expect(await exists(kept())).toBe(true);82		expect(await exists(unlisted)).toBe(true);83		expect(await (await operator.get("/admin/storage")).text()).toContain("no orphans");84	});8586	test("one kept tarball can be deleted; any other key is a 404", async () => {87		const other = { action: "delete_tarball", key: "app/releases/1.0.0/linux-x86_64/app" };88		expect((await operator.post("/admin/storage", other)).status).toBe(404);89		expect(await exists("app/releases/1.0.0/linux-x86_64/app")).toBe(true);90		const response = await operator.post("/admin/storage", {91			action: "delete_tarball",92			key: kept(),93		});94		expect(response.status).toBe(303);95		expect(await exists(kept())).toBe(false);96	});97});