CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
live-events.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.
| 8ff60bc | 1 | /** |
| 2 | * Smoke tests for GET /live-events/:topic — primarily the topic | |
| 3 | * grammar. The full streaming path needs a live DB (read-gate), so we | |
| 4 | * focus on: | |
| 5 | * - 400 on invalid topics (single-letter, missing kind, bad chars) | |
| 6 | * - non-400 on the valid forms (single-segment `kind:id`, multi- | |
| 7 | * segment `kind:id:scope1:scope2`). | |
| 8 | * | |
| 9 | * The route uses softAuth; no cookie required. | |
| 10 | */ | |
| 11 | ||
| 12 | import { describe, it, expect } from "bun:test"; | |
| 13 | import app from "../app"; | |
| 14 | ||
| 15 | const NON_400_OK = [200, 301, 302, 303, 307, 401, 403, 404, 503]; | |
| 16 | ||
| 17 | describe("GET /live-events/:topic — topic grammar", () => { | |
| 18 | it("rejects an empty path beyond the prefix", async () => { | |
| 19 | const res = await app.request("/live-events/", { redirect: "manual" }); | |
| 20 | // 404 for "no param" (Hono path doesn't match) is acceptable. | |
| 21 | expect([400, 404]).toContain(res.status); | |
| 22 | }); | |
| 23 | ||
| 24 | it("rejects a kind-only topic (no id)", async () => { | |
| 25 | const res = await app.request("/live-events/repo"); | |
| 26 | expect([400, 404]).toContain(res.status); | |
| 27 | }); | |
| 28 | ||
| 29 | it("rejects topics with disallowed characters", async () => { | |
| 30 | const res = await app.request( | |
| 31 | "/live-events/" + encodeURIComponent("repo:has spaces") | |
| 32 | ); | |
| 33 | expect(res.status).toBe(400); | |
| 34 | }); | |
| 35 | ||
| 36 | it("rejects topics with embedded slash", async () => { | |
| 37 | const res = await app.request( | |
| 38 | "/live-events/" + encodeURIComponent("repo:foo/bar") | |
| 39 | ); | |
| 40 | expect(res.status).toBe(400); | |
| 41 | }); | |
| 42 | ||
| 43 | it("accepts the canonical single-segment form (repo:<uuid>)", async () => { | |
| 44 | const uuid = "00000000-0000-0000-0000-000000000000"; | |
| 45 | const res = await app.request(`/live-events/repo:${uuid}`, { | |
| 46 | redirect: "manual", | |
| 47 | }); | |
| 48 | // Either 404 (repo not found in DB) or some auth response — but | |
| 49 | // critically not 400. The grammar must accept this shape. | |
| 50 | expect(res.status).not.toBe(400); | |
| 51 | expect(NON_400_OK).toContain(res.status); | |
| 52 | }); | |
| 53 | ||
| 54 | it("accepts a multi-segment topic (repo:<uuid>:issue:7)", async () => { | |
| 55 | const uuid = "00000000-0000-0000-0000-000000000000"; | |
| 56 | const res = await app.request( | |
| 57 | `/live-events/repo:${uuid}:issue:7`, | |
| 58 | { redirect: "manual" } | |
| 59 | ); | |
| 60 | expect(res.status).not.toBe(400); | |
| 61 | expect(NON_400_OK).toContain(res.status); | |
| 62 | }); | |
| 63 | ||
| 64 | it("accepts a multi-segment PR topic (repo:<uuid>:pr:9)", async () => { | |
| 65 | const uuid = "00000000-0000-0000-0000-000000000000"; | |
| 66 | const res = await app.request( | |
| 67 | `/live-events/repo:${uuid}:pr:9`, | |
| 68 | { redirect: "manual" } | |
| 69 | ); | |
| 70 | expect(res.status).not.toBe(400); | |
| 71 | expect(NON_400_OK).toContain(res.status); | |
| 72 | }); | |
| 73 | ||
| 74 | it("accepts a non-repo kind (passes the read-gate)", async () => { | |
| 75 | // `user:42` is not a repo so the auth-gate is bypassed; should | |
| 76 | // open a stream (200 + text/event-stream) or fail later. | |
| 77 | const res = await app.request(`/live-events/user:42`, { | |
| 78 | redirect: "manual", | |
| 79 | }); | |
| 80 | expect(res.status).not.toBe(400); | |
| 81 | }); | |
| 82 | }); |