| 1 | import { readFileSync } from "node:fs"; |
| 2 | import { describe, expect, it } from "vitest"; |
| 3 | |
| 4 | function routeSource(path: string): string { |
| 5 | return readFileSync(new URL(`../app/api/${path}/route.ts`, import.meta.url), "utf8"); |
| 6 | } |
| 7 | |
| 8 | function librarySource(path: string): string { |
| 9 | return readFileSync(new URL(path, import.meta.url), "utf8"); |
| 10 | } |
| 11 | |
| 12 | describe("public API security contracts", () => { |
| 13 | it("keeps the unauthenticated feed cached and detached from the server token", () => { |
| 14 | const source = routeSource("github/feed"); |
| 15 | expect(source).toContain('export const dynamic = "force-static"'); |
| 16 | expect(source).toContain("fetchFeed(undefined, 50)"); |
| 17 | expect(source).not.toContain("GITHUB_TOKEN"); |
| 18 | expect(source).not.toContain('dynamic = "force-dynamic"'); |
| 19 | }); |
| 20 | |
| 21 | it("validates the draft namespace before admin discard can reach KV", () => { |
| 22 | const source = routeSource("admin/post"); |
| 23 | expect(source).toContain("parseDraftKey(draftKey)"); |
| 24 | expect(source.indexOf("parseDraftKey(draftKey)")).toBeLessThan( |
| 25 | source.indexOf("getDraft(env.CURATED_KV, draftKey)"), |
| 26 | ); |
| 27 | expect(source.indexOf("getDraft(env.CURATED_KV, draftKey)")).toBeLessThan( |
| 28 | source.indexOf("deleteDraft(env.CURATED_KV, draftKey)"), |
| 29 | ); |
| 30 | }); |
| 31 | |
| 32 | it("bounds the public login body before comparing the maintainer token", () => { |
| 33 | const source = routeSource("admin/login"); |
| 34 | expect(source).toContain("readBoundedUrlEncodedForm(req, MAX_LOGIN_BODY_BYTES)"); |
| 35 | expect(source).not.toContain("req.formData()"); |
| 36 | expect(source.indexOf("readBoundedUrlEncodedForm(req, MAX_LOGIN_BODY_BYTES)")).toBeLessThan( |
| 37 | source.indexOf("safeEqual(submitted, env.MAINTAINER_TOKEN)"), |
| 38 | ); |
| 39 | }); |
| 40 | |
| 41 | it("keeps paid review callers on the canonical persisted draft namespaces", () => { |
| 42 | const source = librarySource("./community-agent-tasks.ts"); |
| 43 | expect(source).toContain('hasFreshDraft(env.CURATED_KV, "triage"'); |
| 44 | expect(source).toContain('hasFreshDraft(env.CURATED_KV, "pr-review"'); |
| 45 | expect(source).not.toContain('hasFreshDraft(env.CURATED_KV, "issue"'); |
| 46 | expect(source).not.toContain('hasFreshDraft(env.CURATED_KV, "pr"'); |
| 47 | }); |
| 48 | |
| 49 | it("digest post: never returns ok:true for a no-op (happy path returns real url/number)", () => { |
| 50 | const source = routeSource("admin/post"); |
| 51 | // The old false-success sentinel must be gone |
| 52 | expect(source).not.toContain("digest-skipped"); |
| 53 | expect(source).not.toContain("Digest pages are not posted as comments"); |
| 54 | // Happy path must surface the real GitHub Issue outcome |
| 55 | expect(source).toContain("number: issue.number"); |
| 56 | expect(source).toContain("url: issue.html_url"); |
| 57 | // The draft must be marked posted and stored back on success |
| 58 | const digestBlock = source.slice(source.indexOf('draft.type === "digest"')); |
| 59 | expect(digestBlock.indexOf("draft.posted = true")).toBeLessThan( |
| 60 | digestBlock.indexOf('action: "posted"'), |
| 61 | ); |
| 62 | }); |
| 63 | |
| 64 | it("digest post: GitHub API failure surfaces a 502 error, not ok:true", () => { |
| 65 | const source = routeSource("admin/post"); |
| 66 | // On a failed digest GitHub call the handler must return a non-ok error payload |
| 67 | expect(source).toContain("digestRes.ok"); |
| 68 | // Must propagate the GitHub status rather than swallowing it |
| 69 | const digestErrorPath = source.slice( |
| 70 | source.indexOf("digestRes.ok"), |
| 71 | source.indexOf("digestRes.ok") + 300, |
| 72 | ); |
| 73 | expect(digestErrorPath).toContain("status: 502"); |
| 74 | expect(digestErrorPath).not.toContain('ok: true'); |
| 75 | }); |
| 76 | }); |
| 77 |