12345import { beforeAll, describe, expect, test } from "bun:test";6import { mkdir, writeFile } from "node:fs/promises";7import { join } from "node:path";8import { openBucket } from "../src/bucket/reader.ts";9import { syncAll } from "../src/bucket/sync.ts";10import { Browser, type Instance, instance } from "./browser.ts";11import { makeRepos } from "./fixture.ts";1213describe("names ignored in every repo", () => {14 let inst: Instance;15 let browser: Browser;16 let repos: Awaited<ReturnType<typeof makeRepos>>;17 const status = async (path: string) => `${path} ${(await inst.app.request(path)).status}`;18 const ignore = (ignored: string) => browser.post("/admin/site", { section: "site", ignored });19 const sync = () =>20 syncAll({21 bucket: openBucket({ kind: "folder", path: repos.bucketDir }),22 kepr: inst.kepr,23 mirrorDir: join(inst.dataDir, "mirror"),24 });25 const gone = ["/app/raw/AGENTS.md", "/app/raw/.agent/notes.md", "/lib/raw/src/AGENTS.md"];26 const kept = ["/app/raw/AGENTS.md.bak", "/app/raw/.agents/x.md", "/lib/raw/src/main.ts"];2728 beforeAll(async () => {29 repos = await makeRepos(["app", "lib"]);30 const files: [string, string][] = [31 ["app/AGENTS.md", "rules\n"],32 ["app/AGENTS.md.bak", "old rules\n"],33 ["app/.agent/notes.md", "notes\n"],34 ["app/.agents/x.md", "not the same name\n"],35 ["lib/src/AGENTS.md", "nested rules\n"],36 ["lib/src/main.ts", "export const a = 1;\n"],37 ];38 for (const [path, text] of files) {39 const full = join(repos.bucketDir, "..", "work", path);40 await mkdir(join(full, ".."), { recursive: true });41 await writeFile(full, text);42 }43 for (const repo of ["app", "lib"]) {44 repos.koh(repo, "save", "add agent files");45 repos.koh(repo, "offer");46 }47 inst = await instance(repos.bucketDir);48 browser = new Browser(inst.app);49 await browser.signIn();50 for (const repo of ["app", "lib"]) {51 await browser.post(`/admin/repos/${repo}`, { public: "1", hidden_paths: "" });52 }53 });5455 test("global hide is linked from repos and precedes the long site editors", async () => {56 const reposPage = await (await browser.get("/admin/repos")).text();57 expect(reposPage).toContain('href="/admin/site#global-hide"');58 const site = await (await browser.get("/admin/site")).text();59 expect(site).toContain('id="global-hide"');60 expect(site.indexOf('name="ignored"')).toBeLessThan(site.indexOf('name="support_page"'));61 expect(await (await browser.get("/admin/site?section=profile")).text()).not.toContain(62 'name="ignored"',63 );64 });6566 test("refuses anything but a plain name", async () => {67 for (const name of ["src/AGENTS.md", "..", "a\u0000b", "x".repeat(256)]) {68 expect((await ignore(name)).status).toBe(400);69 }70 expect(await status(gone[0] as string)).toBe(`${gone[0]} 200`);71 });7273 test("an ignored name is gone from every repo at any depth, and only that name", async () => {74 expect((await ignore(".agent\r\nAGENTS.md\n")).status).toBe(303);75 for (const path of gone) expect(await status(path)).toBe(`${path} 404`);76 for (const path of kept) expect(await status(path)).toBe(`${path} 200`);77 expect(await (await browser.get("/admin/site")).text()).toContain(78 ">.agent\nAGENTS.md</textarea>",79 );80 });8182 test("stays ignored in saves that arrive later", async () => {83 await writeFile(join(repos.bucketDir, "..", "work", "app", "AGENTS.md"), "new rules\n");84 repos.koh("app", "save", "change the rules");85 repos.koh("app", "offer");86 await sync();87 expect(await status("/app/raw/AGENTS.md")).toBe("/app/raw/AGENTS.md 404");88 expect(await (await inst.app.request("/app")).text()).toContain("change the rules");89 });9091 test("taking a name off the list brings its files back", async () => {92 expect((await ignore(".agent")).status).toBe(303);93 expect(await status("/lib/raw/src/AGENTS.md")).toBe("/lib/raw/src/AGENTS.md 200");94 expect(await (await inst.app.request("/app/raw/AGENTS.md")).text()).toBe("new rules\n");95 expect(await status("/app/raw/.agent/notes.md")).toBe("/app/raw/.agent/notes.md 404");96 });97});