Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
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.

wikis.test.tsBlame99 lines · 2 contributors
1e162a8Claude1/**
2 * Block E3 — Wikis smoke tests.
3 */
4
5import { describe, it, expect } from "bun:test";
bdf47f0ccantynz-alt6import { Hono } from "hono";
1e162a8Claude7import { slugifyTitle } from "../routes/wikis";
bdf47f0ccantynz-alt8import 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.
22const app = new Hono();
23app.route("/", wikiRoutes);
1e162a8Claude24
25describe("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
52describe("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});