1234import { 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});