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

api.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.

api.test.tsBlame110 lines · 3 contributors
fc1817aClaude1import { describe, it, expect, beforeAll, afterAll } from "bun:test";
2import { join } from "path";
3import { rm, mkdir } from "fs/promises";
4import app from "../app";
5
6const TEST_REPOS = join(import.meta.dir, "../../.test-repos-api");
7
8beforeAll(async () => {
9 process.env.GIT_REPOS_PATH = TEST_REPOS;
10 process.env.DATABASE_URL = process.env.DATABASE_URL || "";
11 await mkdir(TEST_REPOS, { recursive: true });
12});
13
14afterAll(async () => {
15 await rm(TEST_REPOS, { recursive: true, force: true });
16});
17
18describe("API routes", () => {
19 it("GET / returns home page", async () => {
20 const res = await app.request("/");
21 expect(res.status).toBe(200);
22 const text = await res.text();
23 expect(text).toContain("gluecron");
24 });
25
63807e5Claude26 it("GET /api/repos/:owner/:name returns 404 or 503, never 500", async () => {
fc1817aClaude27 const res = await app.request("/api/repos/nobody/nothing");
63807e5Claude28 // Without DB: 503 (db unreachable). With DB + missing repo: 404.
29 // API must degrade gracefully — 500 is NOT acceptable.
30 expect([404, 503]).toContain(res.status);
fc1817aClaude31 });
32
754596cccantynz-alt33 it("POST /api/repos returns 401 for an anonymous caller, never 500", async () => {
fc1817aClaude34 const res = await app.request("/api/repos", {
35 method: "POST",
36 headers: { "Content-Type": "application/json" },
37 body: JSON.stringify({}),
38 });
754596cccantynz-alt39 // Was 400: the handler had no auth gate, so an anonymous request reached
40 // field validation. Auth is now the first check, so it never gets there.
41 expect(res.status).toBe(401);
63807e5Claude42 });
43
44 it("GET /api/users/:u/repos returns 404 or 503, never 500", async () => {
45 const res = await app.request("/api/users/nobody/repos");
46 expect([404, 503]).toContain(res.status);
fc1817aClaude47 });
48});
49
50describe("Git HTTP routes", () => {
51 it("returns 400 for invalid service", async () => {
52 const res = await app.request("/test/repo.git/info/refs?service=invalid");
53 expect(res.status).toBe(400);
54 });
55
56 it("returns 404 for non-existent repo", async () => {
57 const res = await app.request(
58 "/nobody/nothing.git/info/refs?service=git-upload-pack"
59 );
60 expect(res.status).toBe(404);
61 });
62});
a2b53b6ccanty labs63
64describe("Git HTTP param stripping (Hono :repo.git regression)", () => {
65 // The Hono upgrade changed what `:repo.git` captures: the param now
66 // arrives as "repo.git" → "<name>.git" (suffix included). Every handler
67 // must resolve names through gitParams, which strips the suffix — passing
68 // the raw segment to repoExists/getRepoPath yields "<name>.git.git" on
69 // disk and 404s every clone/push platform-wide.
70 const { gitParams } = require("../routes/git").__test;
71 const fakeCtx = (params: Record<string, string>) =>
72 ({ req: { param: () => params } }) as any;
73
74 it("strips .git from the modern 'repo.git' param key", () => {
75 expect(gitParams(fakeCtx({ owner: "a", "repo.git": "proj.git" }))).toEqual({
76 owner: "a",
77 repo: "proj",
78 });
79 });
80
81 it("handles dotted repo names (only the final .git is stripped)", () => {
82 expect(
83 gitParams(fakeCtx({ owner: "ccantynz", "repo.git": "Gluecron.com.git" }))
84 ).toEqual({ owner: "ccantynz", repo: "Gluecron.com" });
85 });
86
87 it("falls back to the legacy 'repo' param key", () => {
88 expect(gitParams(fakeCtx({ owner: "a", repo: "proj.git" }))).toEqual({
89 owner: "a",
90 repo: "proj",
91 });
92 });
93
94 it("pins live Hono param behavior for the :repo.git pattern", async () => {
95 // If a future Hono bump changes param naming again, this fails loudly
96 // instead of silently 404ing production clones.
97 const { Hono } = await import("hono");
98 const probe = new Hono();
99 let seen: Record<string, string> = {};
100 probe.get("/:owner/:repo.git/info/refs", (c) => {
101 seen = c.req.param() as Record<string, string>;
102 return c.text("ok");
103 });
104 await probe.request("/alice/My.Repo.git/info/refs");
105 expect(gitParams({ req: { param: () => seen } } as any)).toEqual({
106 owner: "alice",
107 repo: "My.Repo",
108 });
109 });
110});