import { describe, expect, it } from "bun:test";
import { readFileSync } from "fs";
const SRC = readFileSync("src/routes/api-v2.ts", "utf8");
describe("api-v2 repo privacy gate", () => {
it("is mounted on the whole /repos/:owner/:repo subtree", () => {
expect(SRC).toContain('apiv2.use("/repos/:owner/:repo", repoPrivacyGate)');
expect(SRC).toContain('apiv2.use("/repos/:owner/:repo/*", repoPrivacyGate)');
});
it("denies with 404, never 403 — a private repo must not confirm it exists", () => {
const gate = SRC.slice(
SRC.indexOf("async function repoPrivacyGate"),
SRC.indexOf("// ─── Helper")
);
expect(gate).toContain('access === "none"');
expect(gate).toContain('c.json({ error: "Not found" }, 404)');
expect(gate).not.toMatch(/,\s*403\s*\)/);
});
it("uses resolveRepoAccess, not an ownerId comparison", () => {
const gate = SRC.slice(
SRC.indexOf("async function repoPrivacyGate"),
SRC.indexOf("// ─── Helper")
);
expect(gate).toContain("resolveRepoAccess");
expect(gate).not.toMatch(/user\.id\s*!==\s*\w*[Oo]wner/);
});
it("runs after apiAuth so the viewer identity is populated", () => {
expect(SRC.indexOf('apiv2.use("*", apiAuth)')).toBeLessThan(
SRC.indexOf('apiv2.use("/repos/:owner/:repo", repoPrivacyGate)')
);
});
it("lets unknown repos fall through to each handler's own 404", () => {
const gate = SRC.slice(
SRC.indexOf("async function repoPrivacyGate"),
SRC.indexOf("// ─── Helper")
);
expect(gate).toContain("if (!resolved) return next()");
});
});
describe("repoExists is not a privacy check", () => {
it("never consults the repositories table", () => {
const repo = readFileSync("src/git/repository.ts", "utf8");
const fn = repo.slice(
repo.indexOf("export async function repoExists"),
repo.indexOf("export async function repoExists") + 400
);
expect(fn).not.toContain("isPrivate");
expect(fn).not.toContain("repositories");
});
});
describe("GET /api/users/:username/repos does not leak private repos", () => {
const SRC_API = readFileSync("src/routes/api.ts", "utf8");
const handler = SRC_API.slice(
SRC_API.indexOf('api.get("/users/:username/repos"'),
SRC_API.indexOf('api.get("/repos/:owner/:name"')
);
it("filters each row through resolveRepoAccess", () => {
expect(handler).toContain("resolveRepoAccess");
expect(handler).toContain('access === "none"');
});
it("strips diskPath from the response", () => {
expect(handler).toMatch(/diskPath: _diskPath, \.\.\.rest/);
});
it("does not return the raw rows", () => {
expect(handler).not.toContain("return c.json(repos)");
});
});
|