import { afterAll, beforeAll, describe, expect, setSystemTime, test } from "bun:test"; import { fileIssue } from "../src/issues/store.ts"; import { Browser, type Instance, instance } from "./browser.ts"; import { makeRepos, writeRelease } from "./fixture.ts"; let clock = Date.parse("2026-09-26T12:00:00Z"); const tick = (seconds: number) => { clock += seconds * 1000; setSystemTime(new Date(clock)); }; describe("repos and visibility", () => { let inst: Instance; let browser: Browser; let bucketDir: string; let save: string; const publicPaths = () => [ "/app", "/app/files/README.md", "/app/raw/README.md", `/app/saves/${save}`, `/app/archive/${save}.tar.gz`, "/app/archive/HEAD.tar.gz", "/app/archives", "/app/releases", "/app/releases/latest", "/app/releases/latest.json", "/app/releases/history.json", "/app/releases/latest/version.txt", "/app/releases/latest/linux-x86_64", "/app/releases/1.0.0/checksums.txt", "/app/releases/1.0.0/linux-x86_64", "/app/install.sh", "/api/app/releases/latest", "/api/app/releases/history", "/app/update-info", "/app/meta", "/app/issues", "/app/issues/new", "/app/issues/1", ]; const statuses = async () => { const out: string[] = []; for (const path of publicPaths()) out.push(`${path} ${(await inst.app.request(path)).status}`); return out; }; const text = async (path: string) => (await inst.app.request(path)).text(); const publish = (repo: string, fields: Record) => browser.post(`/admin/repos/${repo}`, { sort_order: "0", ...fields }); beforeAll(async () => { setSystemTime(new Date(clock)); const repos = await makeRepos(["app", "other"]); bucketDir = repos.bucketDir; save = repos.saves.app as string; await writeRelease(bucketDir, "app", { version: "1.0.0", save, files: [{ platform: "linux-x86_64", name: "app", bytes: new Uint8Array([1]) }], }); inst = await instance(bucketDir); inst.kepr.run("INSERT INTO settings (key, value) VALUES ('handle', 'june')"); fileIssue(inst.kepr, { repo: "app", title: "It broke", body: "b", type: "bug", version: "", reporterName: "", contactEmail: "", ipHash: "x", filedAt: 1, }); browser = new Browser(inst.app); await browser.signIn(); }); afterAll(() => setSystemTime()); test("every repo the sync found is listed, private, with its last sync", async () => { const page = await (await browser.get("/admin/repos")).text(); expect(page).toContain('id="repo-app"'); expect(page).toContain('id="repo-other"'); expect(page.match(/private<\/span>/g)?.length).toBe(2); expect(page).toContain("last sync 2026-09-26 12:00 UTC"); expect((await statuses()).every((line) => line.endsWith(" 404"))).toBe(true); }); test("bad settings are refused and change nothing", async () => { for (const fields of []>[ { homepage: "javascript:alert(1)" }, { sort_order: "first" }, { install1: "one\ntwo" }, { category: "x".repeat(501) }, ]) { expect((await publish("app", { public: "1", ...fields })).status).toBe(400); } expect((await inst.app.request("/app")).status).toBe(404); expect((await browser.post("/admin/repos/nowhere", { public: "1" })).status).toBe(404); }); test("publishing opens every public surface, with the settings shown", async () => { await publish("other", { public: "1" }); const response = await publish("app", { public: "1", category: "tools", homepage: "https://app.example", install1: "nix profile add app", }); expect(response.status).toBe(303); const lines = await statuses(); expect(lines.filter((line) => line.endsWith(" 404"))).toEqual([]); expect(await text("/app")).toContain("https://app.example"); expect(await text("/app")).toContain("nix profile add app"); expect(await text("/")).toContain('href="/app"'); expect(await text("/browse")).toContain('href="/app"'); expect(await text("/@june")).toContain('href="/app"'); expect(await text("/sitemap.xml")).toContain("/app"); }); test("flipping a repo private 404s every surface and drops it from lists and counts", async () => { const stat = async () => [...(await text("/@june")).matchAll(/profile-stat-number">([^<]*) m[1]); // Both download routes above counted; the issue is the repo's one. expect(await stat()).toEqual(["2", "2", "1", "2"]); await publish("app", { category: "tools" }); expect((await statuses()).filter((line) => !line.endsWith(" 404"))).toEqual([]); for (const path of ["/", "/browse", "/@june"]) expect(await text(path)).not.toContain("/app"); expect(await text("/sitemap.xml")).not.toContain("/app<"); expect(await text("/")).not.toContain("tools"); expect(await stat()).toEqual(["1", "1", "0", "0"]); // Settings survive the flip. expect(await (await browser.get("/admin/repos")).text()).toContain('value="tools"'); }); test("sync now reads the bucket at once and records the time", async () => { await writeRelease(bucketDir, "other", { version: "2.0.0", save: save, files: [{ platform: "linux-x86_64", name: "other", bytes: new Uint8Array([2]) }], }); tick(60); expect((await inst.app.request("/other/releases/latest/version.txt")).status).toBe(404); const response = await browser.post("/admin/sync", {}); expect(response.status).toBe(303); expect(await text("/other/releases/latest/version.txt")).toBe("2.0.0"); expect(await (await browser.get("/admin/repos")).text()).toContain( "last sync 2026-09-26 12:01 UTC", ); }); });