Blame · Line-by-line history
wikis.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 E3 — Wikis smoke tests. | |
| 3 | */ | |
| 4 | ||
| 5 | import { describe, it, expect } from "bun:test"; | |
| bdf47f0 | 6 | import { Hono } from "hono"; |
| 1e162a8 | 7 | import { slugifyTitle } from "../routes/wikis"; |
| bdf47f0 | 8 | import wikiRoutes from "../routes/wikis"; |
| 9 | ||
| 10 | // Mount only the router under test rather than importing the shared `../app`. | |
| 11 | // | |
| 12 | // `../app` is one singleton shared by the entire test run, and a static import | |
| 13 | // is hoisted — so it binds whichever `../db` was mocked at the moment some | |
| 14 | // OTHER test file first triggered its load, and nothing afterwards can rebind | |
| 15 | // it (mock.module swaps the registry entry; it does not rebind namespaces an | |
| 16 | // importer already holds). That is why "on missing repo → 404" passed alone | |
| 17 | // and failed in a full run: a leaked fake db returned a stubbed repo row, so | |
| 18 | // the repo was not missing. Bisected to voice-to-pr.test.ts. | |
| 19 | // | |
| 20 | // Mounting the wiki router directly makes these cases depend only on the code | |
| 21 | // they are testing, which is both correct and immune to that class of leak. | |
| 22 | const app = new Hono(); | |
| 23 | app.route("/", wikiRoutes); | |
| 1e162a8 | 24 | |
| 25 | describe("wikis — slugifyTitle", () => { | |
| 26 | it("lowercases and dashes simple titles", () => { | |
| 27 | expect(slugifyTitle("Home")).toBe("home"); | |
| 28 | expect(slugifyTitle("Getting Started")).toBe("getting-started"); | |
| 29 | }); | |
| 30 | ||
| 31 | it("strips punctuation", () => { | |
| 32 | expect(slugifyTitle("Hello, World!")).toBe("hello-world"); | |
| 33 | expect(slugifyTitle("What's up?")).toBe("whats-up"); | |
| 34 | }); | |
| 35 | ||
| 36 | it("collapses consecutive spaces/dashes", () => { | |
| 37 | expect(slugifyTitle("a b")).toBe("a-b"); | |
| 38 | expect(slugifyTitle("a---b")).toBe("a-b"); | |
| 39 | }); | |
| 40 | ||
| 41 | it("trims leading/trailing dashes", () => { | |
| 42 | expect(slugifyTitle(" hi ")).toBe("hi"); | |
| 43 | expect(slugifyTitle("-hi-")).toBe("hi"); | |
| 44 | }); | |
| 45 | ||
| 46 | it("returns empty string when nothing usable", () => { | |
| 47 | expect(slugifyTitle("")).toBe(""); | |
| 48 | expect(slugifyTitle("***")).toBe(""); | |
| 49 | }); | |
| 50 | }); | |
| 51 | ||
| 52 | describe("wikis — route smoke", () => { | |
| 53 | it("GET /:owner/:repo/wiki on missing repo → 404", async () => { | |
| 54 | const res = await app.request("/nobody/missing/wiki"); | |
| 55 | expect(res.status).toBe(404); | |
| 56 | }); | |
| 57 | ||
| 58 | it("GET /:owner/:repo/wiki/new without auth → 302 /login", async () => { | |
| 59 | const res = await app.request("/any/repo/wiki/new"); | |
| 60 | expect(res.status).toBe(302); | |
| 61 | expect(res.headers.get("location") || "").toMatch(/^\/login/); | |
| 62 | }); | |
| 63 | ||
| 64 | it("POST /:owner/:repo/wiki without auth → 302 /login", async () => { | |
| 65 | const res = await app.request("/any/repo/wiki", { | |
| 66 | method: "POST", | |
| 67 | body: new URLSearchParams({ title: "Home", body: "welcome" }), | |
| 68 | headers: { "content-type": "application/x-www-form-urlencoded" }, | |
| 69 | }); | |
| 70 | expect(res.status).toBe(302); | |
| 71 | expect(res.headers.get("location") || "").toMatch(/^\/login/); | |
| 72 | }); | |
| 73 | ||
| 74 | it("POST edit without auth → 302 /login", async () => { | |
| 75 | const res = await app.request("/any/repo/wiki/home/edit", { | |
| 76 | method: "POST", | |
| 77 | body: new URLSearchParams({ title: "x", body: "y" }), | |
| 78 | headers: { "content-type": "application/x-www-form-urlencoded" }, | |
| 79 | }); | |
| 80 | expect(res.status).toBe(302); | |
| 81 | expect(res.headers.get("location") || "").toMatch(/^\/login/); | |
| 82 | }); | |
| 83 | ||
| 84 | it("POST delete without auth → 302 /login", async () => { | |
| 85 | const res = await app.request("/any/repo/wiki/home/delete", { | |
| 86 | method: "POST", | |
| 87 | }); | |
| 88 | expect(res.status).toBe(302); | |
| 89 | expect(res.headers.get("location") || "").toMatch(/^\/login/); | |
| 90 | }); | |
| 91 | ||
| 92 | it("POST revert without auth → 302 /login", async () => { | |
| 93 | const res = await app.request("/any/repo/wiki/home/revert/1", { | |
| 94 | method: "POST", | |
| 95 | }); | |
| 96 | expect(res.status).toBe(302); | |
| 97 | expect(res.headers.get("location") || "").toMatch(/^\/login/); | |
| 98 | }); | |
| 99 | }); |