import { beforeEach, describe, expect, mock, test } from "bun:test"; import { Database } from "bun:sqlite"; import { readFileSync } from "node:fs"; import { applyCombinedSchema } from "../test-schema.test-helper"; import type { RepoCardData } from "~/lib/og"; import type { RepoRow } from "~/lib/types"; import { loadRuntimeFromDisk, pngSize } from "./disk-runtime"; mock.module("cloudflare:workers", () => ({ env: { PUBLIC_URL: "https://forge.example.com", BUCKET: { get: async () => null }, IMAGES: {}, }, waitUntil: () => undefined, WorkflowEntrypoint: class {}, DurableObject: class {}, WorkerEntrypoint: class {}, })); mock.module("@tanstack/react-start/server", () => ({ getRequestHeader: () => undefined })); const db = new Database(":memory:"); applyCombinedSchema(db); for (const migration of ["0064_repo_topics.sql", "0065_repo_languages.sql"]) { db.exec(readFileSync(new URL(`../../../migrations/${migration}`, import.meta.url), "utf8")); } mock.module("../db", () => ({ first: async (sql: string, ...params: unknown[]) => db.query(sql).get(...(params as never[])) ?? null, all: async (sql: string, ...params: unknown[]) => db.query(sql).all(...(params as never[])), run: async () => ({ meta: { changes: 0 } }), batch: async () => [], stmt: (sql: string, ...params: unknown[]) => ({ sql, params }), placeholders: (count: number) => Array(count).fill("?").join(", "), chunks: (items: T[]) => [items], MAX_IN_LIST: 90, })); let renders = 0; let limited = false; mock.module("../limits", () => ({ requestIp: () => "203.0.113.1", enforceRateLimit: async () => { if (limited) throw new Error("rate limited"); renders++; }, })); const runtime = loadRuntimeFromDisk(); mock.module("./runtime", () => ({ loadRuntime: () => runtime })); const store = new Map(); (globalThis as { caches?: unknown }).caches = { open: async () => ({ match: async (request: Request) => store.get(request.url)?.clone(), put: async (request: Request, response: Response) => { store.set(request.url, response); }, }), }; const { sharedScopes } = await import("../test-shared-scopes"); mock.module("../scoped-db", () => sharedScopes(db)); const { handleOgRoutes, OG_FALLBACK_CACHE, OG_VERSIONED_CACHE } = await import("./routes"); const og = await import("~/lib/og"); db.exec(` INSERT INTO accounts (id, name, kind, display_name, bio, created_at) VALUES ('alice', 'alice', 'user', 'Alice', 'Writes compilers.', 0), ('acme', 'acme', 'org', NULL, NULL, 0), ('gone', 'gone', 'user', NULL, NULL, 0); UPDATE accounts SET disabled_at = 1, disabled_reason = 'admin' WHERE id = 'gone'; INSERT INTO repos (id, owner_id, name, description, visibility, status, default_branch, artifacts_name, artifacts_remote, created_at, updated_at, hidden_at, stars_count) VALUES ('pub', 'alice', 'public-repo', 'A public repository', 'public', 'ready', 'main', 'a1', 'r1', 0, 0, NULL, 5), ('priv', 'alice', 'secret', 'Private plans', 'private', 'ready', 'main', 'a2', 'r2', 0, 0, NULL, 0), ('hid', 'alice', 'hidden', 'Hidden by an admin', 'public', 'ready', 'main', 'a3', 'r3', 0, 0, 1, 0), ('down', 'gone', 'taken', 'Owner taken down', 'public', 'ready', 'main', 'a4', 'r4', 0, 0, NULL, 0); `); const ctx = { waitUntil: (promise: Promise) => void promise } as unknown as ExecutionContext; async function get(path: string, method = "GET") { const url = new URL(path, "https://forge.example.com"); const response = await handleOgRoutes(new Request(url, { method }), url, ctx); if (!response) throw new Error(`no response for ${path}`); return { response, bytes: new Uint8Array(await response.arrayBuffer()) }; } function headers(response: Response) { return Object.fromEntries(response.headers.entries()); } beforeEach(() => { limited = false; }); describe("og renderer", () => { test("renders a 1200x630 PNG", async () => { const { response, bytes } = await get("/og/site.png"); expect(response.headers.get("content-type")).toBe("image/png"); expect(pngSize(bytes)).toEqual({ width: og.OG_WIDTH, height: og.OG_HEIGHT }); }); test("renders repo, account and feature cards", async () => { for (const path of ["/og/alice/public-repo.png", "/og/alice.png", "/og/acme.png", "/og/feature/review.png"]) { const { bytes } = await get(path); expect(pngSize(bytes)).toEqual({ width: og.OG_WIDTH, height: og.OG_HEIGHT }); } }); test("ignores other paths and refuses writes", async () => { const url = new URL("https://forge.example.com/avatars/x"); expect(await handleOgRoutes(new Request(url), url, ctx)).toBeNull(); expect((await get("/og/site.png", "POST")).response.status).toBe(405); }); }); describe("privacy fallback", () => { test("private, hidden, taken-down, missing and malformed cards are byte-identical to the generic card", async () => { const generic = await get("/og/__nothing_here__.png"); const site = await get("/og/site.png"); expect(generic.bytes).toEqual(site.bytes); const paths = [ "/og/alice/secret.png", "/og/alice/hidden.png", "/og/gone/taken.png", "/og/gone.png", "/og/nobody/nothing.png", "/og/nobody.png", "/og/feature/nope.png", "/og/a/b/c.png", "/og/%E0%A4%A.png", `/og/alice/secret.png?v=${og.siteCardVersion()}`, ]; for (const path of paths) { const { response, bytes } = await get(path); expect(bytes).toEqual(generic.bytes); expect(headers(response)).toEqual(headers(generic.response)); } }); test("a public repository renders its own card", async () => { const generic = await get("/og/nobody/nothing.png"); const repo = await get("/og/alice/public-repo.png"); expect(repo.bytes).not.toEqual(generic.bytes); }); test("ignores the session cookie", async () => { const url = new URL("https://forge.example.com/og/alice/secret.png"); const request = new Request(url, { headers: { cookie: "gitfor_session=anything" } }); const response = await handleOgRoutes(request, url, ctx); const generic = await get("/og/nobody.png"); expect(new Uint8Array(await (response as Response).arrayBuffer())).toEqual(generic.bytes); }); }); const BASE: RepoCardData = { owner: "alice", name: "public-repo", description: "A public repository", stars: 5, forks: 0, openIssues: 0, openPulls: 0, avatarKey: null, latestRelease: null, topics: [], languages: [], }; describe("version hash", () => { test("changes with every field on the card", () => { const base = og.repoCardVersion(BASE); const changes: Partial[] = [ { description: "New description" }, { stars: 6 }, { forks: 1 }, { openIssues: 1 }, { openPulls: 1 }, { avatarKey: "alice-abc" }, { name: "renamed" }, { owner: "bob" }, { latestRelease: "v1.0.0" }, { topics: ["rust"] }, { languages: [{ name: "Go", percent: 100 }] }, ]; for (const change of changes) expect(og.repoCardVersion({ ...BASE, ...change })).not.toBe(base); expect(og.repoCardVersion({ ...BASE })).toBe(base); }); test("a new release changes the advertised URL", async () => { const before = og.repoCardVersion(BASE); db.exec(`INSERT INTO releases (id, repo_id, tag, name, author_id, target_sha, created_at, published_at) VALUES ('rel1', 'pub', 'v2.0.0', 'v2', 'alice', 'abc', 1, 1)`); const { repoCardData } = await import("./data"); const { loadRepo } = await import("../repos"); const data = await repoCardData((await loadRepo("alice", "public-repo")) as RepoRow); expect(data.latestRelease).toBe("v2.0.0"); expect(og.repoCardVersion(data)).not.toBe(before); db.exec("DELETE FROM releases WHERE id = 'rel1'"); }); test("topics and the language mix come from the repository row", async () => { db.exec(`UPDATE repos SET topics = '["rust","cli","a","b","c","d"]', languages = '[{"name":"Rust","bytes":9000},{"name":"Shell","bytes":994},{"name":"Other","bytes":6}]' WHERE id = 'pub'`); const { repoCardData } = await import("./data"); const { loadRepo } = await import("../repos"); const data = await repoCardData((await loadRepo("alice", "public-repo")) as RepoRow); expect(data.topics).toEqual(["rust", "cli", "a", "b", "c"]); expect(data.languages).toEqual([ { name: "Rust", percent: 90 }, { name: "Shell", percent: 10 }, { name: "Other", percent: 1 }, ]); db.exec("UPDATE repos SET topics = '[]', languages = '[]' WHERE id = 'pub'"); }); test("includes the renderer version", () => { expect(og.ogVersion(["x"])).toMatch(/^[0-9a-f]{8}$/); expect(og.ogVersion(["x"])).not.toBe(og.ogVersion(["y"])); }); test("account cards change with the profile", () => { const account = { name: "alice", kind: "user" as const, displayName: "Alice", bio: null, avatarKey: null, publicRepos: 1, }; expect(og.accountCardVersion(account)).not.toBe(og.accountCardVersion({ ...account, bio: "Hi" })); expect(og.accountCardVersion(account)).not.toBe(og.accountCardVersion({ ...account, publicRepos: 2 })); }); }); describe("caching", () => { test("matching versions get the long cache, stale or missing versions revalidate hourly", async () => { const { repoCardData } = await import("./data"); const { loadRepo } = await import("../repos"); const version = og.repoCardVersion(await repoCardData((await loadRepo("alice", "public-repo")) as RepoRow)); const current = await get(`/og/alice/public-repo.png?v=${version}`); expect(current.response.headers.get("cache-control")).toBe(OG_VERSIONED_CACHE); expect(OG_VERSIONED_CACHE).toBe("public, max-age=1209600"); expect((await get("/og/alice/public-repo.png?v=deadbeef")).response.headers.get("cache-control")).toBe( OG_FALLBACK_CACHE, ); expect((await get("/og/alice/public-repo.png")).response.headers.get("cache-control")).toBe(OG_FALLBACK_CACHE); expect((await get(og.siteOgPath())).response.headers.get("cache-control")).toBe(OG_VERSIONED_CACHE); }); test("cache hits skip rendering and the rate limit", async () => { await get("/og/acme.png"); const before = renders; limited = true; const again = await get("/og/acme.png"); expect(pngSize(again.bytes)).not.toBeNull(); expect(renders).toBe(before); }); test("cache misses are rate limited", async () => { store.clear(); limited = true; await expect(get("/og/alice.png")).rejects.toThrow("rate limited"); }); test("HEAD returns headers without a body", async () => { const { response, bytes } = await get("/og/site.png", "HEAD"); expect(bytes.byteLength).toBe(0); expect(Number(response.headers.get("content-length"))).toBeGreaterThan(0); }); });