CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
discussions.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 1e162a8 | 1 | /** |
| 2 | * Block E2 — Discussions smoke tests. | |
| 3 | * | |
| 4 | * Route integration paths against a real repo would require a seeded test DB; | |
| 5 | * we stick to category validation + public route behaviour (anon access, | |
| 6 | * auth redirects) which exercise middleware + mounting. | |
| 7 | */ | |
| 8 | ||
| 9 | import { describe, it, expect } from "bun:test"; | |
| 10 | import app from "../app"; | |
| 11 | import { isValidCategory } from "../routes/discussions"; | |
| 12 | ||
| 13 | describe("discussions — isValidCategory", () => { | |
| 14 | it("accepts the five canonical categories", () => { | |
| 15 | expect(isValidCategory("general")).toBe(true); | |
| 16 | expect(isValidCategory("q-and-a")).toBe(true); | |
| 17 | expect(isValidCategory("ideas")).toBe(true); | |
| 18 | expect(isValidCategory("announcements")).toBe(true); | |
| 19 | expect(isValidCategory("show-and-tell")).toBe(true); | |
| 20 | }); | |
| 21 | ||
| 22 | it("rejects unknown categories", () => { | |
| 23 | expect(isValidCategory("")).toBe(false); | |
| 24 | expect(isValidCategory("random")).toBe(false); | |
| 25 | expect(isValidCategory("Q-AND-A")).toBe(false); | |
| 26 | expect(isValidCategory("sql injection'--")).toBe(false); | |
| 27 | }); | |
| 28 | }); | |
| 29 | ||
| 30 | describe("discussions — route smoke", () => { | |
| 31 | it("GET /:owner/:repo/discussions on missing repo → 404 HTML", async () => { | |
| 32 | const res = await app.request("/nobody/missing/discussions"); | |
| 33 | expect(res.status).toBe(404); | |
| 34 | const body = await res.text(); | |
| 35 | expect(body).toContain("Repository not found"); | |
| 36 | }); | |
| 37 | ||
| 38 | it("GET /:owner/:repo/discussions/new without auth → 302 /login", async () => { | |
| 39 | const res = await app.request("/any/repo/discussions/new"); | |
| 40 | expect(res.status).toBe(302); | |
| 41 | const loc = res.headers.get("location") || ""; | |
| 42 | expect(loc.startsWith("/login")).toBe(true); | |
| 43 | }); | |
| 44 | ||
| 45 | it("POST /:owner/:repo/discussions without auth → 302 /login", async () => { | |
| 46 | const res = await app.request("/any/repo/discussions", { | |
| 47 | method: "POST", | |
| 48 | body: new URLSearchParams({ title: "x", body: "y" }), | |
| 49 | headers: { "content-type": "application/x-www-form-urlencoded" }, | |
| 50 | }); | |
| 51 | expect(res.status).toBe(302); | |
| 52 | const loc = res.headers.get("location") || ""; | |
| 53 | expect(loc.startsWith("/login")).toBe(true); | |
| 54 | }); | |
| 55 | ||
| 56 | it("POST /:owner/:repo/discussions/1/comment without auth → 302 /login", async () => { | |
| 57 | const res = await app.request("/any/repo/discussions/1/comment", { | |
| 58 | method: "POST", | |
| 59 | body: new URLSearchParams({ body: "hi" }), | |
| 60 | headers: { "content-type": "application/x-www-form-urlencoded" }, | |
| 61 | }); | |
| 62 | expect(res.status).toBe(302); | |
| 63 | const loc = res.headers.get("location") || ""; | |
| 64 | expect(loc.startsWith("/login")).toBe(true); | |
| 65 | }); | |
| 66 | ||
| 67 | it("POST /:owner/:repo/discussions/1/lock without auth → 302 /login", async () => { | |
| 68 | const res = await app.request("/any/repo/discussions/1/lock", { | |
| 69 | method: "POST", | |
| 70 | }); | |
| 71 | expect(res.status).toBe(302); | |
| 72 | const loc = res.headers.get("location") || ""; | |
| 73 | expect(loc.startsWith("/login")).toBe(true); | |
| 74 | }); | |
| 75 | }); |