Blame · Line-by-line history
hot-files.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.
| 74d8c4d | 1 | /** |
| 2 | * Tests for src/lib/hot-files.ts and src/routes/hot-files.tsx. | |
| 3 | */ | |
| 4 | ||
| 5 | import { describe, test, expect } from "bun:test"; | |
| 6 | ||
| 7 | // ─── Pure helper tests ──────────────────────────────────────────────────────── | |
| 8 | ||
| 9 | // Risk classification logic — mirrors hot-files.ts without importing the async fn. | |
| 10 | const HIGH_RISK_PATTERNS = ["auth", "security", "schema", "db/", "middleware", "routes/git", "crypto"]; | |
| 11 | const MEDIUM_RISK_PATTERNS = ["route", "api", "lib/", ".sql"]; | |
| 12 | ||
| 13 | function classifyRisk(filePath: string): "high" | "medium" | "low" { | |
| 14 | const lower = filePath.toLowerCase(); | |
| 15 | if (HIGH_RISK_PATTERNS.some((p) => lower.includes(p))) return "high"; | |
| 16 | if (MEDIUM_RISK_PATTERNS.some((p) => lower.includes(p))) return "medium"; | |
| 17 | return "low"; | |
| 18 | } | |
| 19 | ||
| 20 | describe("hot-files risk classification", () => { | |
| 21 | test("auth file → high", () => expect(classifyRisk("src/lib/auth.ts")).toBe("high")); | |
| 22 | test("middleware file → high", () => expect(classifyRisk("src/middleware/rate-limit.ts")).toBe("high")); | |
| 23 | test("schema file → high", () => expect(classifyRisk("src/db/schema.ts")).toBe("high")); | |
| 24 | test("route file → medium", () => expect(classifyRisk("src/routes/issues.tsx")).toBe("medium")); | |
| 25 | test("sql migration → medium", () => expect(classifyRisk("drizzle/0001.sql")).toBe("medium")); | |
| 26 | test("api lib → medium", () => expect(classifyRisk("src/lib/api-helper.ts")).toBe("medium")); | |
| 27 | test("test file → low", () => expect(classifyRisk("src/__tests__/orgs.test.ts")).toBe("low")); | |
| 28 | test("readme → low", () => expect(classifyRisk("README.md")).toBe("low")); | |
| 29 | test("case-insensitive: AUTH.TS → high", () => expect(classifyRisk("AUTH.TS")).toBe("high")); | |
| 30 | }); | |
| 31 | ||
| 32 | // Extension extraction | |
| 33 | function extractExt(p: string): string { | |
| 34 | const dot = p.lastIndexOf("."); | |
| 35 | if (dot === -1 || dot === p.length - 1) return ""; | |
| 36 | return p.slice(dot + 1); | |
| 37 | } | |
| 38 | ||
| 39 | describe("hot-files extension extraction", () => { | |
| 40 | test(".ts extension", () => expect(extractExt("src/lib/foo.ts")).toBe("ts")); | |
| 41 | test(".tsx extension", () => expect(extractExt("src/routes/bar.tsx")).toBe("tsx")); | |
| 42 | test("no extension → empty string", () => expect(extractExt("Makefile")).toBe("")); | |
| 43 | test("trailing dot → empty string", () => expect(extractExt("file.")).toBe("")); | |
| 44 | test(".sql extension", () => expect(extractExt("drizzle/0001.sql")).toBe("sql")); | |
| 45 | }); | |
| 46 | ||
| 47 | // Path truncation | |
| 48 | function truncatePath(path: string, maxChars = 40): string { | |
| 49 | if (path.length <= maxChars) return path; | |
| 50 | return "…" + path.slice(path.length - maxChars); | |
| 51 | } | |
| 52 | ||
| 53 | describe("hot-files path truncation", () => { | |
| 54 | test("short path unchanged", () => expect(truncatePath("src/foo.ts")).toBe("src/foo.ts")); | |
| 55 | test("long path truncated with ellipsis", () => { | |
| 56 | const long = "src/routes/very/deeply/nested/path/component/file.tsx"; | |
| 57 | const result = truncatePath(long, 40); | |
| 58 | expect(result.startsWith("…")).toBe(true); | |
| 59 | expect(result.length).toBe(41); // 1 for "…" + 40 | |
| 60 | }); | |
| 61 | test("exactly maxChars is unchanged", () => { | |
| 62 | const exact = "a".repeat(40); | |
| 63 | expect(truncatePath(exact, 40)).toBe(exact); | |
| 64 | }); | |
| 65 | }); | |
| 66 | ||
| 67 | // ─── Route smoke test ───────────────────────────────────────────────────────── | |
| 68 | ||
| 69 | import app from "../app"; | |
| 70 | ||
| 71 | const HAS_DB = Boolean(process.env.DATABASE_URL); | |
| 72 | ||
| 73 | describe("GET /:owner/:repo/insights/hotfiles", () => { | |
| 74 | test.skipIf(!HAS_DB)("non-existent repo returns 404", async () => { | |
| 75 | const res = await app.request("/__nx_owner__/__nx_repo__/insights/hotfiles"); | |
| 76 | expect(res.status).toBe(404); | |
| 77 | }); | |
| 78 | }); |