12345import { beforeAll, describe, expect, test } from "bun:test";6import { writeFile } from "node:fs/promises";7import { join } from "node:path";8import { Browser, type Instance, instance } from "./browser.ts";9import { makeRepos } from "./fixture.ts";1011describe("renaming a repo", () => {12 let inst: Instance;13 let browser: Browser;14 let repos: Awaited<ReturnType<typeof makeRepos>>;15 const rename = (name: string) => browser.post("/admin/repos/app/rename", { name });16 const keys = (prefix: string) => [...new Bun.Glob(`${prefix}/**`).scanSync(repos.bucketDir)];17 const bucketText = (key: string) => Bun.file(join(repos.bucketDir, key)).text();18 19 const settled = async (path: string) => {20 for (let i = 0; i < 100 && (await inst.app.request(path)).status !== 200; i++) {21 await Bun.sleep(50);22 }23 };2425 beforeAll(async () => {26 repos = await makeRepos(["app", "other"]);27 inst = await instance(repos.bucketDir);28 browser = new Browser(inst.app);29 await browser.signIn();30 await browser.post("/admin/repos/app", { public: "1", category: "tools", hidden_paths: "" });31 inst.kepr.run(32 "INSERT INTO downloads (repo, version, platform, count) VALUES ('app', '1.0.0', 'linux-x64', 7)",33 );34 });3536 test("refuses a name that is malformed, uppercase, taken or Kepr's own", async () => {37 for (const name of ["", "a/b", "..", "Tool", "other", "admin", "app"]) {38 expect(`${name} ${(await rename(name)).status}`).toBe(`${name} 400`);39 }40 expect(keys("app").length).toBeGreaterThan(0);41 });4243 test("moves the history, Kepr's settings and counts, and leaves a marker for Koh", async () => {44 const head = await bucketText("app/head");45 expect((await rename("tool")).status).toBe(303);46 await settled("/tool");47 expect(await bucketText("tool/head")).toBe(head);48 expect(keys("app")).toEqual([]);49 expect(await bucketText(".kepr/renamed/app")).toBe("tool\n");50 const row = inst.kepr.query("SELECT public, category FROM repos WHERE name = 'tool'").get();51 expect(row).toEqual({ public: 1, category: "tools" });52 expect(inst.kepr.query("SELECT COUNT(*) AS n FROM repos WHERE name = 'app'").get()).toEqual({53 n: 0,54 });55 const counted = inst.kepr.query("SELECT count FROM downloads WHERE repo = 'tool'").get();56 expect(counted).toEqual({ count: 7 });57 });5859 test("old page URLs redirect to the new name; Koh's routes do not", async () => {60 for (const [from, to] of [61 ["/app", "/tool"],62 ["/app/files/README.md?at=abc123", "/tool/files/README.md?at=abc123"],63 ]) {64 const response = await inst.app.request(from as string);65 expect(`${from} ${response.status} ${response.headers.get("location")}`).toBe(66 `${from} 301 ${to}`,67 );68 }69 expect((await inst.app.request("/app/koh/head")).status).toBe(404);70 });7172 test("Koh follows the marker on its next offer", async () => {73 await writeFile(join(repos.bucketDir, "..", "work", "app", "later.txt"), "after\n");74 repos.koh("app", "save", "after the rename");75 const local = JSON.parse(repos.koh("app", "view", "--json")).head_id as string;76 expect(await bucketText("tool/head")).toBe(local);77 expect(keys("app")).toEqual([]);78 });79});