1234import { beforeAll, describe, expect, test } from "bun:test";5import { mkdir, writeFile } from "node:fs/promises";6import { dirname, join } from "node:path";7import { Browser, type Instance, instance } from "./browser.ts";8import { makeRepos, writeRelease } from "./fixture.ts";910describe("storage view (§24)", () => {11 let inst: Instance;12 let operator: Browser;13 let bucketDir: string;14 let save: string;15 const exists = (key: string) => Bun.file(join(bucketDir, key)).exists();16 const put = async (key: string) => {17 await mkdir(dirname(join(bucketDir, key)), { recursive: true });18 await writeFile(join(bucketDir, key), "x");19 };20 let generation = "";21 const kept = () => `.kepr/tarballs/app/${generation}/${save}.tar.gz`;22 const orphans = [23 ".kepr/tarballs/app/0-000000000000/aaaaaaaaaaaa.tar.gz",24 ".kepr/tarballs/gone/0-000000000000/aaaaaaaaaaaa.tar.gz",25 ".kepr/hidden/gone",26 ];27 const unlisted = "app/releases/9.9.9/linux-x86_64/app";2829 beforeAll(async () => {30 const repos = await makeRepos(["app"]);31 bucketDir = repos.bucketDir;32 save = repos.saves.app as string;33 await writeRelease(bucketDir, "app", {34 version: "1.0.0",35 save,36 files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array(2048) }],37 });38 inst = await instance(bucketDir);39 operator = new Browser(inst.app);40 await operator.signIn();41 await operator.post("/admin/repos/app", { public: "1" });42 expect((await inst.app.request(`/app/archive/${save}.tar.gz`)).status).toBe(200);43 const listed = new Bun.Glob(`.kepr/tarballs/app/*/${save}.tar.gz`).scanSync({44 cwd: bucketDir,45 dot: true,46 });47 generation = (Array.from(listed)[0] as string).split("/")[3] as string;48 for (const key of [...orphans, unlisted]) await put(key);49 });5051 test("shows each repo's history, builds and kept tarballs, and the orphans", async () => {52 const page = await (await operator.get("/admin/storage")).text();53 expect(page).toMatch(/<td>app<\/td>\s*<td>[\d.]+ K?B<\/td>\s*<td>2\.\d KB<\/td>/);54 expect(page).toContain(`${save}.tar.gz`);55 expect(page).toContain("3 orphans");56 });5758 test("deleting orphans removes only Kepr's stale files", async () => {59 expect((await operator.post("/admin/storage", { action: "delete_orphans" })).status).toBe(303);60 for (const key of orphans) expect(await exists(key)).toBe(false);61 expect(await exists(kept())).toBe(true);62 expect(await exists(unlisted)).toBe(true);63 expect(await (await operator.get("/admin/storage")).text()).toContain("no orphans");64 });6566 test("one kept tarball can be deleted; any other key is a 404", async () => {67 const other = { action: "delete_tarball", key: "app/releases/1.0.0/linux-x86_64/app" };68 expect((await operator.post("/admin/storage", other)).status).toBe(404);69 expect(await exists("app/releases/1.0.0/linux-x86_64/app")).toBe(true);70 const response = await operator.post("/admin/storage", {71 action: "delete_tarball",72 key: kept(),73 });74 expect(response.status).toBe(303);75 expect(await exists(kept())).toBe(false);76 });77});