import { describe, it, expect } from "bun:test";
import { Hono } from "hono";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import orgRoutes from "../routes/orgs";
const app = new Hono();
app.route("/", orgRoutes);
describe("anonymous access to a team page", () => {
it("redirects to login instead of rendering the roster", async () => {
const res = await app.request("/orgs/acme/teams/platform");
expect(res.status).toBe(302);
const location = res.headers.get("location") || "";
expect(location).toStartWith("/login?redirect=");
});
it("round-trips the requested team as the post-login target", async () => {
const res = await app.request("/orgs/acme/teams/platform");
const location = res.headers.get("location") || "";
const target = decodeURIComponent(location.split("redirect=")[1] || "");
expect(target).toBe("/orgs/acme/teams/platform");
});
it("encodes the redirect target so it stays a single relative path", async () => {
const res = await app.request(
"/orgs/acme/teams/" + encodeURIComponent("https://evil.example")
);
const location = res.headers.get("location") || "";
expect(location).toStartWith("/login?redirect=");
expect(location).not.toContain("//evil.example");
});
it("does not leak roster or repository markup in the response body", async () => {
const body = await (await app.request("/orgs/acme/teams/platform")).text();
expect(body).not.toContain("Members (");
expect(body).not.toContain("access</span>");
});
});
describe("the team route enforces org membership", () => {
const handler = (() => {
const text = readFileSync(
join(import.meta.dir, "..", "routes", "orgs.tsx"),
"utf8"
);
const src = text
.split("\n")
.filter((l) => !l.trim().startsWith("//"))
.join("\n");
const start = src.indexOf(`orgRoutes.get("/orgs/:org/teams/:team"`);
expect(start).toBeGreaterThan(-1);
const end = src.indexOf("orgRoutes.", start + 40);
const body = end > start ? src.slice(start, end) : src.slice(start);
expect(body.length).toBeGreaterThan(0);
return body;
})();
it("looks the viewer up in orgMembers", () => {
expect(handler).toContain("from(orgMembers)");
expect(handler).toContain("eq(orgMembers.userId, user.id)");
});
it("bounces anonymous callers before touching the database", () => {
const anonAt = handler.indexOf("if (!user)");
const dbAt = handler.indexOf("db.select()");
expect(anonAt).toBeGreaterThan(-1);
expect(dbAt).toBeGreaterThan(-1);
expect(anonAt).toBeLessThan(dbAt);
});
it("resolves membership before reading the team or its repos", () => {
const memberAt = handler.indexOf("eq(orgMembers.userId, user.id)");
const teamAt = handler.indexOf("from(teams)");
const reposAt = handler.indexOf("from(teamRepos)");
expect(teamAt).toBeGreaterThan(memberAt);
expect(reposAt).toBeGreaterThan(memberAt);
});
it("answers a non-member with 404, not 403", () => {
const memberAt = handler.indexOf("eq(orgMembers.userId, user.id)");
const after = handler.slice(memberAt);
expect(after).toContain("c.notFound()");
expect(after.indexOf("c.notFound()")).toBeLessThan(
after.indexOf("from(teams)")
);
});
});
|