rename.test.ts

3.4 KB · raw

1// Covers renaming a repo from the console (June, 2026-10-01): the history moves to the new name in2// the bucket with a marker left behind, Kepr's own rows and counts move with it, old page URLs3// redirect, and Koh follows the marker on its next offer.45import { 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	// The rename runs after its response; wait for the new name to answer.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});