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 26 const settled = async (path: string, want: number) => {27 for (let i = 0; i < 100 && (await inst.app.request(path)).status !== want; i++) {28 await Bun.sleep(50);29 }30 };31 const gone = ["/app/raw/AGENTS.md", "/app/raw/.agent/notes.md", "/lib/raw/src/AGENTS.md"];32 const kept = ["/app/raw/AGENTS.md.bak", "/app/raw/.agents/x.md", "/lib/raw/src/main.ts"];3334 beforeAll(async () => {35 repos = await makeRepos(["app", "lib"]);36 const files: [string, string][] = [37 ["app/AGENTS.md", "rules\n"],38 ["app/AGENTS.md.bak", "old rules\n"],39 ["app/.agent/notes.md", "notes\n"],40 ["app/.agents/x.md", "not the same name\n"],41 ["lib/src/AGENTS.md", "nested rules\n"],42 ["lib/src/main.ts", "export const a = 1;\n"],43 ];44 for (const [path, text] of files) {45 const full = join(repos.bucketDir, "..", "work", path);46 await mkdir(join(full, ".."), { recursive: true });47 await writeFile(full, text);48 }49 for (const repo of ["app", "lib"]) {50 repos.koh(repo, "save", "add agent files");51 repos.koh(repo, "offer");52 }53 inst = await instance(repos.bucketDir);54 browser = new Browser(inst.app);55 await browser.signIn();56 for (const repo of ["app", "lib"]) {57 await browser.post(`/admin/repos/${repo}`, { public: "1", hidden_paths: "" });58 }59 });6061 test("refuses anything but a plain name", async () => {62 for (const name of ["src/AGENTS.md", "..", "a\u0000b", "x".repeat(256)]) {63 expect((await ignore(name)).status).toBe(400);64 }65 expect(await status(gone[0] as string)).toBe(`${gone[0]} 200`);66 });6768 test("an ignored name is gone from every repo at any depth, and only that name", async () => {69 expect((await ignore(".agent\r\nAGENTS.md\n")).status).toBe(303);70 await settled(gone[2] as string, 404);71 for (const path of gone) expect(await status(path)).toBe(`${path} 404`);72 for (const path of kept) expect(await status(path)).toBe(`${path} 200`);73 expect(await (await browser.get("/admin/site")).text()).toContain(74 ">.agent\nAGENTS.md</textarea>",75 );76 });7778 test("stays ignored in saves that arrive later", async () => {79 await writeFile(join(repos.bucketDir, "..", "work", "app", "AGENTS.md"), "new rules\n");80 repos.koh("app", "save", "change the rules");81 repos.koh("app", "offer");82 await sync();83 expect(await status("/app/raw/AGENTS.md")).toBe("/app/raw/AGENTS.md 404");84 expect(await (await inst.app.request("/app")).text()).toContain("change the rules");85 });8687 test("taking a name off the list brings its files back", async () => {88 expect((await ignore(".agent")).status).toBe(303);89 await settled("/lib/raw/src/AGENTS.md", 200);90 expect(await status("/lib/raw/src/AGENTS.md")).toBe("/lib/raw/src/AGENTS.md 200");91 expect(await (await inst.app.request("/app/raw/AGENTS.md")).text()).toBe("new rules\n");92 expect(await status("/app/raw/.agent/notes.md")).toBe("/app/raw/.agent/notes.md 404");93 });94});