From 8cad274093d141dd54e425b511d8d1169033b6e0 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Tue, 29 Sep 2026 12:41:46 +0000 Subject: [PATCH] test: replace brittle checks with behavioral coverage --- tests/blog-share-image.test.ts | 24 -- tests/codex-stats.test.ts | 44 +- tests/cron-publication.test.ts | 205 ++++++++++ tests/favicons.test.ts | 18 - tests/github-accounts.test.ts | 34 +- tests/github-activity-days.test.tsx | 73 ++-- tests/github-activity-processor.test.ts | 163 ++++---- tests/github-activity-status.test.ts | 10 +- tests/github-activity-worker.test.ts | 2 - tests/github-backfill.test.ts | 15 - tests/github-commits.test.ts | 195 ++++----- tests/github-cron-config.test.ts | 71 +--- tests/github-pull-request-backfill.test.ts | 34 +- tests/github-reconciliation.test.ts | 9 +- tests/github-summary-cron-route.test.ts | 16 - tests/github-timeline-fallback.test.tsx | 16 - tests/github-work-unit-crosswalk.test.ts | 1 - .../github-work-unit-summary-provider.test.ts | 79 ++-- tests/github-work-unit-summary.test.ts | 10 +- tests/hourly-publication-resilience.test.ts | 386 ------------------ tests/knip.test.ts | 91 ----- tests/mdx-image.test.ts | 22 - tests/production-database-migration.test.ts | 32 +- tests/remark-embed-github.test.ts | 6 +- tests/resume-pdf.test.ts | 30 -- tests/setup.ts | 4 + tests/site-seo.test.ts | 267 +++--------- tests/site-url.test.ts | 40 -- tests/token-delegation.test.ts | 3 + tests/token-details.test.tsx | 89 ++-- 30 files changed, 638 insertions(+), 1351 deletions(-) delete mode 100644 tests/blog-share-image.test.ts create mode 100644 tests/cron-publication.test.ts delete mode 100644 tests/favicons.test.ts delete mode 100644 tests/github-summary-cron-route.test.ts delete mode 100644 tests/github-timeline-fallback.test.tsx delete mode 100644 tests/hourly-publication-resilience.test.ts delete mode 100644 tests/knip.test.ts delete mode 100644 tests/mdx-image.test.ts delete mode 100644 tests/resume-pdf.test.ts diff --git a/tests/blog-share-image.test.ts b/tests/blog-share-image.test.ts deleted file mode 100644 index 58d7620c..00000000 --- a/tests/blog-share-image.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, test } from "bun:test"; - -import { renderBlogShareImage } from "../src/lib/blog-share-image"; - -test("renders distinct, full-size PNG cards from article metadata", async () => { - const metadata = { - author: "Sid Jain", - date: "2026-09-07", - summary: "A personal project story.", - title: "Teaching Image Search What I Meant", - }; - const response = renderBlogShareImage(metadata); - const png = Buffer.from(await response.arrayBuffer()); - expect(response.headers.get("content-type")).toBe("image/png"); - expect(png.subarray(1, 4).toString()).toBe("PNG"); - expect(png.readUInt32BE(16)).toBe(1200); - expect(png.readUInt32BE(20)).toBe(630); - - const other = renderBlogShareImage({ - ...metadata, - title: "2018 Music in Review", - }); - expect(Buffer.from(await other.arrayBuffer()).equals(png)).toBe(false); -}); diff --git a/tests/codex-stats.test.ts b/tests/codex-stats.test.ts index fb6a1395..2850a568 100644 --- a/tests/codex-stats.test.ts +++ b/tests/codex-stats.test.ts @@ -1,14 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { createElement } from "react"; -import { renderToStaticMarkup } from "react-dom/server"; - -import { - activityThresholds, - CodexActivity, -} from "../src/components/codex-activity.tsx"; -import { CodexTotals, CodexUsageLimit } from "../src/components/codex-stats"; -import type { CodexAccountSnapshot } from "../src/lib/codex/stats"; +import { activityThresholds } from "../src/components/codex-activity.tsx"; import { buildPublicCodexStats, createCodexAccountSnapshot, @@ -91,7 +83,7 @@ const requireStats = (stats: T | null): T => { }; describe("public Codex statistics", () => { - test("daily totals use provider dates and reset timestamps display in IST", () => { + test("daily totals use provider dates across IST and UTC midnight", () => { const snapshot = createCodexAccountSnapshot( profile(120, [{ start_date: "2026-09-28", tokens: 120 }]), { @@ -121,15 +113,6 @@ describe("public Codex statistics", () => { }); expect(stats.totals.last7Days.value).toBe(120); expect(stats.totals.todayTokens.value).toBe(expectedToday); - const html = renderToStaticMarkup(createElement(CodexTotals, { stats })); - expect(html).toContain("Last 7 days"); - expect(html).toContain(">Today"); - expect(html).not.toContain("UTC"); - const limits = renderToStaticMarkup( - createElement(CodexUsageLimit, { stats }) - ); - expect(limits).toContain("Sep 29, 2026"); - expect(limits).not.toContain("UTC"); } }); @@ -143,26 +126,6 @@ describe("public Codex statistics", () => { expect(activityThresholds([10, 10, 10])).toEqual([10, 10, 10]); }); - test("keeps zero-activity calendar cells out of the tab order", () => { - const series = { - partial: false, - values: [ - { day: "2026-01-01", tokens: 0 }, - { day: "2026-01-02", tokens: 1000 }, - ], - }; - const html = renderToStaticMarkup( - createElement(CodexActivity, { - cumulative: series, - daily: series, - weekly: series, - }) - ); - - expect(html).not.toContain(": 0 tokens"); - expect(html).toContain("1,000 tokens"); - }); - test("whitelists upstream data and combines accounts", () => { const first = createCodexAccountSnapshot( profile(80, [ @@ -301,7 +264,6 @@ describe("public Codex statistics", () => { resetAt: null, }, ]); - expect(JSON.stringify(stats)).not.toContain("Spark"); const partial = requireStats( buildPublicCodexStats( @@ -525,7 +487,7 @@ test("snapshot saves keep profile history when upstream shortens or omits it", a dailyUsageBuckets: [{ startDate: "2020-01-01", tokens: 10 }], cumulativeDailyUsageBuckets: [{ startDate: "2020-01-01", tokens: 10 }], primaryLimit: null, - summary: {} as CodexAccountSnapshot["summary"], + summary: createCodexAccountSnapshot(profile(0, []), usage()).summary, topInvocations: null, }; const next = { diff --git a/tests/cron-publication.test.ts b/tests/cron-publication.test.ts new file mode 100644 index 00000000..a5215833 --- /dev/null +++ b/tests/cron-publication.test.ts @@ -0,0 +1,205 @@ +import { afterEach, expect, spyOn, test } from "bun:test"; + +import * as nextCache from "next/cache"; +import * as nextServer from "next/server"; + +import { POST as syncTokens } from "../src/app/api/cron/codex-stats/route"; +import { POST as summarizeWork } from "../src/app/api/cron/github-summary/route"; +import { POST as syncWork } from "../src/app/api/cron/github-worker/route"; +import * as tokenViews from "../src/lib/codex/public-stats"; +import * as tokenSync from "../src/lib/codex/sync"; +import * as workViews from "../src/lib/github-activity-feed"; +import * as workWorker from "../src/lib/github-activity-worker"; +import * as summaryWorker from "../src/lib/github-work-unit-summary-worker"; +import { env } from "./helpers"; + +const secret = "test-cron-secret-with-at-least-32-characters"; +const originalSecret = env.CRON_SECRET; + +afterEach(() => { + env.CRON_SECRET = originalSecret; +}); + +const request = (path: string) => + new Request(`https://example.com/api/cron/${path}`, { + method: "POST", + headers: { authorization: `Bearer ${secret}` }, + }); + +test("cron authentication rejects missing, wrong and unconfigured secrets before doing work", async () => { + const unexpected = new Error("An unauthenticated request reached a worker"); + const sync = spyOn(tokenSync, "syncCodexAccounts").mockRejectedValue( + unexpected + ); + const worker = spyOn(workWorker, "runGitHubActivityWorker").mockRejectedValue( + unexpected + ); + const summary = spyOn( + summaryWorker, + "runGitHubWorkUnitSummaryWorker" + ).mockRejectedValue(unexpected); + try { + for (const [configured, authorization] of [ + [secret, undefined], + [secret, `Bearer wrong-${secret}`], + [secret, `bearer ${secret}`], + [undefined, `Bearer ${secret}`], + ["short", "Bearer short"], + ]) { + env.CRON_SECRET = configured; + for (const handler of [syncTokens, syncWork, summarizeWork]) { + const response = await handler( + new Request("https://example.com/api/cron/test", { + method: "POST", + headers: authorization === undefined ? {} : { authorization }, + }) + ); + expect(response.status).toBe(401); + } + } + expect(sync).not.toHaveBeenCalled(); + expect(worker).not.toHaveBeenCalled(); + expect(summary).not.toHaveBeenCalled(); + } finally { + sync.mockRestore(); + worker.mockRestore(); + summary.mockRestore(); + } +}); + +test("Codex publication warms only after a successful sync changes data", async () => { + env.CRON_SECRET = secret; + const sync = spyOn(tokenSync, "syncCodexAccounts"); + const invalidate = spyOn(nextCache, "revalidateTag").mockImplementation( + () => {} + ); + const after = spyOn(nextServer, "after").mockImplementation(() => {}); + const warm = spyOn(tokenViews, "getPublicCodexStats").mockResolvedValue(null); + try { + sync.mockResolvedValue({ updated: 0 }); + expect((await syncTokens(request("codex-stats"))).status).toBe(200); + expect(invalidate).not.toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + + sync.mockRejectedValue(new Error("private upstream details")); + const failure = await syncTokens(request("codex-stats")); + expect(failure.status).toBe(503); + expect(await failure.json()).toEqual({ ok: false, error: "Error" }); + expect(invalidate).not.toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + + sync.mockResolvedValue({ updated: 1 }); + expect((await syncTokens(request("codex-stats"))).status).toBe(200); + expect(invalidate.mock.calls).toEqual([["public-codex-stats", "max"]]); + expect(after).toHaveBeenCalledTimes(1); + expect(warm).not.toHaveBeenCalled(); + const [[warmAfterResponse]] = after.mock.calls; + expect(typeof warmAfterResponse).toBe("function"); + if (typeof warmAfterResponse === "function") { + await warmAfterResponse(); + } + expect(warm).toHaveBeenCalledTimes(1); + } finally { + sync.mockRestore(); + invalidate.mockRestore(); + after.mockRestore(); + warm.mockRestore(); + } +}); + +test("GitHub rejects invalid requests and warms only a changed public feed", async () => { + env.CRON_SECRET = secret; + const stage = { + claimed: 0, + completed: 0, + deferred: 0, + failed: 0, + unavailable: 0, + }; + const result: workWorker.GitHubActivityWorkerResult = { + commits: stage, + deadlineReached: false, + observations: stage, + projection: null, + pullRequests: stage, + pullRequestDiscovery: stage, + pullRequestSignals: stage, + refs: stage, + }; + const worker = spyOn(workWorker, "runGitHubActivityWorker").mockResolvedValue( + result + ); + const invalidate = spyOn(nextCache, "revalidateTag").mockImplementation( + () => {} + ); + const after = spyOn(nextServer, "after").mockImplementation(() => {}); + const warm = spyOn(workViews, "getInitialGitHubActivity").mockResolvedValue( + null + ); + try { + expect((await syncWork(request("github-worker?batch=bad"))).status).toBe( + 400 + ); + expect(worker).not.toHaveBeenCalled(); + worker.mockRejectedValueOnce(new Error("private upstream details")); + const failure = await syncWork(request("github-worker")); + expect(failure.status).toBe(503); + expect(await failure.json()).toEqual({ ok: false, error: "Error" }); + expect(invalidate).not.toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + expect((await syncWork(request("github-worker"))).status).toBe(200); + expect(invalidate).not.toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + + result.projection = { + changed: true, + deletedUnits: 0, + exclusionReasonCounts: { + merged_pr_landing: 0, + canonical_branch_unknown: 0, + head_generation_incomplete: 0, + no_current_owner: 0, + pull_request_coverage_incomplete: 0, + repository_visibility_unknown: 0, + }, + feedRevisionChanged: false, + insertedUnits: 0, + orderingRevisionChanged: true, + summaryAttemptsQueued: 0, + summaryEvaluationsPending: 0, + summaryEvaluationsSettled: 0, + summaryInputsFailed: 0, + summaryInputsSet: 0, + updatedUnits: 1, + }; + expect((await syncWork(request("github-worker"))).status).toBe(200); + expect(invalidate).not.toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + + result.projection.feedRevisionChanged = true; + expect((await syncWork(request("github-worker?batch=2"))).status).toBe(200); + expect(worker).toHaveBeenLastCalledWith( + expect.objectContaining({ + includeProjection: true, + commitLimit: 2, + refLimit: 1, + }) + ); + expect(invalidate.mock.calls).toEqual([ + ["public-github-activity", { expire: 0 }], + ]); + expect(after).toHaveBeenCalledTimes(1); + expect(warm).not.toHaveBeenCalled(); + const [[warmAfterResponse]] = after.mock.calls; + expect(typeof warmAfterResponse).toBe("function"); + if (typeof warmAfterResponse === "function") { + await warmAfterResponse(); + } + expect(warm).toHaveBeenCalledTimes(1); + } finally { + worker.mockRestore(); + invalidate.mockRestore(); + after.mockRestore(); + warm.mockRestore(); + } +}); diff --git a/tests/favicons.test.ts b/tests/favicons.test.ts deleted file mode 100644 index 10786801..00000000 --- a/tests/favicons.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect, test } from "bun:test"; - -import { createElement } from "react"; -import { renderToStaticMarkup } from "react-dom/server"; - -import { ThemeProvider } from "../src/components/theme-provider"; - -test("server-rendered favicons follow system preference before hydration", () => { - const html = renderToStaticMarkup(createElement(ThemeProvider)); - const icons = html.match(/]+rel="icon"[^>]*>/gu); - expect(icons).toHaveLength(2); - for (const [index, theme] of ["light", "dark"].entries()) { - expect(icons?.[index]).toContain(`href="/portraits/icon-${theme}.png"`); - expect(icons?.[index]).toContain( - `media="(prefers-color-scheme: ${theme})"` - ); - } -}); diff --git a/tests/github-accounts.test.ts b/tests/github-accounts.test.ts index dd1d9648..439567be 100644 --- a/tests/github-accounts.test.ts +++ b/tests/github-accounts.test.ts @@ -1,4 +1,5 @@ import { afterEach, expect, test } from "bun:test"; +import assert from "node:assert/strict"; import { githubTokensFrom, @@ -38,12 +39,14 @@ test("parses arbitrary configured account keys and rejects malformed or ambiguou '"private-value"', "private-value", ]) { - expect(() => githubTokensFrom(value, ["alice"])).toThrow(TypeError); - try { - githubTokensFrom(value, ["alice"]); - } catch (error) { - expect(String(error)).not.toContain("private-value"); - } + assert.throws( + () => githubTokensFrom(value, ["alice"]), + (thrown: unknown) => { + expect(thrown).toBeInstanceOf(TypeError); + expect(String(thrown)).not.toContain("private-value"); + return true; + } + ); } }); @@ -51,8 +54,8 @@ test("credential rotation, removal and order never change authors or require net globalThis.fetch = mockFetch(() => { throw new Error("Unexpected discovery request"); }); - const authors = TRACKED_GITHUB_USER_IDS; - const accounts = TRACKED_GITHUB_ACCOUNTS; + const authors = { ...TRACKED_GITHUB_USER_IDS }; + const accounts = [...TRACKED_GITHUB_ACCOUNTS]; delete env.GITHUB_TOKEN; for (const tokens of [ { f0rr0: "first", yuppiestechdev: "second" }, @@ -78,8 +81,11 @@ test("credential rotation, removal and order never change authors or require net test("existing verification checks both stable identity and configured login in one request", async () => { let calls = 0; let identity = { id: 8_574_219, login: "F0rr0" }; - globalThis.fetch = mockFetch(async (input) => { + globalThis.fetch = mockFetch(async (input, init) => { calls += 1; + expect(new Headers(init?.headers).get("authorization")).toBe( + "Bearer test-token" + ); expect(new URL(input instanceof Request ? input.url : input).pathname).toBe( "/user" ); @@ -88,12 +94,14 @@ test("existing verification checks both stable identity and configured login in await assertGitHubTokenIdentity("f0rr0", "test-token"); expect(calls).toBe(1); identity = { id: 99_666_891, login: "f0rr0" }; - expect(assertGitHubTokenIdentity("f0rr0", "test-token")).rejects.toThrow( - "not authenticated" + await assert.rejects( + assertGitHubTokenIdentity("f0rr0", "test-token"), + /not authenticated/u ); identity = { id: 8_574_219, login: "renamed" }; - expect(assertGitHubTokenIdentity("f0rr0", "test-token")).rejects.toThrow( - "not authenticated" + await assert.rejects( + assertGitHubTokenIdentity("f0rr0", "test-token"), + /not authenticated/u ); }); diff --git a/tests/github-activity-days.test.tsx b/tests/github-activity-days.test.tsx index 1c11166d..31c2181a 100644 --- a/tests/github-activity-days.test.tsx +++ b/tests/github-activity-days.test.tsx @@ -63,40 +63,44 @@ test("work uses accessible status icons, PR totals and useful title fallbacks; i const repository = { key: "1", label: "Private", url: null, avatarUrl: null }; const days = buildPublicGitHubActivityDays({ days: ["2026-09-28"], - workUnits: ["open", "draft", "merged", "closed"].map((status) => ({ - activityAt: "2026-09-28T12:00:00Z", - day: "2026-09-28", - destination: null, - id: status, - kind: "pull-request" as const, - headline: null, - summary: null, - summarizing: false, - repository, - facts: { - additions: 100, - deletions: 80, - ownedCommitCount: 2, - uniqueFileCount: 1, - languages: null, - dateRange: null, - }, - pullRequest: { - title: "Add filtering", - status: status as "open" | "draft" | "merged" | "closed", - statusChangedAt: "2026-09-28T12:00:00Z", - diff: { additions: 20, deletions: 0, files: 1 }, - }, - })), - issues: ["open", "completed", "not-planned", "closed"].map((status) => ({ - activityAt: "2026-09-28T12:00:00Z", - day: "2026-09-28", - destination: null, - id: `issue-${status}`, - title: "Improve filtering", - repository, - status: status as "open" | "completed" | "not-planned" | "closed", - })), + workUnits: (["open", "draft", "merged", "closed"] as const).map( + (status) => ({ + activityAt: "2026-09-28T12:00:00Z", + day: "2026-09-28", + destination: null, + id: status, + kind: "pull-request" as const, + headline: null, + summary: null, + summarizing: false, + repository, + facts: { + additions: 100, + deletions: 80, + ownedCommitCount: 2, + uniqueFileCount: 1, + languages: null, + dateRange: null, + }, + pullRequest: { + title: "Add filtering", + status, + statusChangedAt: "2026-09-28T12:00:00Z", + diff: { additions: 20, deletions: 0, files: 1 }, + }, + }) + ), + issues: (["open", "completed", "not-planned", "closed"] as const).map( + (status) => ({ + activityAt: "2026-09-28T12:00:00Z", + day: "2026-09-28", + destination: null, + id: `issue-${status}`, + title: "Improve filtering", + repository, + status, + }) + ), }); const html = renderToStaticMarkup( @@ -117,6 +121,7 @@ test("work uses accessible status icons, PR totals and useful title fallbacks; i expect(html).toContain(label); } expect(html).not.toContain("+100"); + expect(html).toContain(">+20<"); expect(html).not.toContain("−80"); expect(html).not.toContain("Direct canonical"); }); diff --git a/tests/github-activity-processor.test.ts b/tests/github-activity-processor.test.ts index a5b12941..935369ec 100644 --- a/tests/github-activity-processor.test.ts +++ b/tests/github-activity-processor.test.ts @@ -1,8 +1,14 @@ -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { + afterEach, + beforeEach, + describe, + expect, + setSystemTime, + test, +} from "bun:test"; import assert from "node:assert/strict"; import { - GitHubGraphQlResponseError, fetchGitHubActivityCommitSource, fetchGitHubAssociatedPullRequests, fetchGitHubPullRequestMembership, @@ -41,11 +47,13 @@ const restoreEnvironmentValue = ( }; beforeEach(() => { + setSystemTime(new Date("2026-09-29T12:00:00Z")); delete env.GITHUB_TOKENS; delete env.GITHUB_TOKEN; }); afterEach(() => { + setSystemTime(); globalThis.fetch = originalFetch; restoreEnvironmentValue("GITHUB_TOKENS", originalTokens); restoreEnvironmentValue("GITHUB_TOKEN", originalDefaultToken); @@ -154,10 +162,10 @@ describe("GitHub activity commit acquisition", () => { sha, }; - expect(fetchGitHubActivityCommitSource(reference)).rejects.toMatchObject({ + await assert.rejects(fetchGitHubActivityCommitSource(reference), { code: "source_invalid", }); - expect(fetchGitHubActivityCommitSource(reference)).rejects.toMatchObject({ + await assert.rejects(fetchGitHubActivityCommitSource(reference), { code: "source_invalid", }); const root = await fetchGitHubActivityCommitSource(reference); @@ -320,7 +328,7 @@ describe("GitHub activity commit acquisition", () => { }) ); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historySinceAt: new Date("2026-08-01T00:00:00.000Z"), historyUntilAt: null, @@ -333,8 +341,9 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ code: "source_incomplete" }); + }), + { code: "source_incomplete" } + ); }); test("rejects a pushed sequence that contradicts durable commit order", async () => { @@ -353,7 +362,7 @@ describe("GitHub activity commit acquisition", () => { }) ); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historySinceAt: new Date("2026-08-01T00:00:00.000Z"), historyUntilAt: null, @@ -366,8 +375,9 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ code: "source_incomplete" }); + }), + { code: "source_incomplete" } + ); }); test("isolates malformed foreign commits and accepts an empty tracked message", async () => { @@ -435,7 +445,7 @@ describe("GitHub activity commit acquisition", () => { }) ); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historySinceAt: new Date("2026-08-01T00:00:00.000Z"), historyUntilAt: null, @@ -448,8 +458,9 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ code: "source_invalid" }); + }), + { code: "source_invalid" } + ); }); test("accepts a ref rewind with no newly reachable commits", async () => { @@ -601,7 +612,7 @@ describe("GitHub activity commit acquisition", () => { return new Response(null, { status: 404 }); }); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historyUntilAt: null, account: "f0rr0" as const, @@ -614,8 +625,9 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ status: 404 }); + }), + { status: 404 } + ); expect(paths).toEqual([ `/repos/example-org/example-repo/compare/${beforeSha}...${afterSha}`, @@ -679,7 +691,7 @@ describe("GitHub activity commit acquisition", () => { return new Response(null, { status: 409 }); }); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historyUntilAt: null, account: "f0rr0" as const, @@ -692,8 +704,9 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ code: "source_incomplete" }); + }), + { code: "source_incomplete" } + ); expect(calls).toBe(1); }); @@ -719,7 +732,7 @@ describe("GitHub activity commit acquisition", () => { ) ); - expect( + await assert.rejects( fetchGitHubPushObservationSource({ historyUntilAt: null, account: "f0rr0" as const, @@ -732,13 +745,14 @@ describe("GitHub activity commit acquisition", () => { refName: "refs/heads/main", repository: "example-org/example-repo", repositoryId: "123", - }) - ).rejects.toMatchObject({ - code: "source_incomplete", - kind: "rate_limited", - retryable: true, - retryAt: expect.any(Date), - }); + }), + { + code: "source_incomplete", + kind: "rate_limited", + retryable: true, + retryAt: new Date("2026-09-29T12:02:00Z"), + } + ); }); test("bounds a new branch by the observed count without slicing history", async () => { @@ -1053,13 +1067,14 @@ describe("GitHub pull request merge commit resolution", () => { }) ); - expect( - resolveGitHubPullRequestMergeCommits(["PR_partial"], "test-token") - ).rejects.toMatchObject({ - code: "source_incomplete", - kind: "partial_response", - retryable: true, - }); + await assert.rejects( + resolveGitHubPullRequestMergeCommits(["PR_partial"], "test-token"), + { + code: "source_incomplete", + kind: "partial_response", + retryable: true, + } + ); }); test("classifies HTTP-200 GraphQL rate limits with their reset time", async () => { @@ -1081,14 +1096,15 @@ describe("GitHub pull request merge commit resolution", () => { ) ); - expect( - resolveGitHubPullRequestMergeCommits(["PR_limited"], "test-token") - ).rejects.toMatchObject({ - code: "source_incomplete", - kind: "rate_limited", - retryable: true, - retryAt: resetAt, - }); + await assert.rejects( + resolveGitHubPullRequestMergeCommits(["PR_limited"], "test-token"), + { + code: "source_incomplete", + kind: "rate_limited", + retryable: true, + retryAt: resetAt, + } + ); }); test("waits at least one minute for a headerless GraphQL secondary limit", async () => { @@ -1100,23 +1116,18 @@ describe("GitHub pull request merge commit resolution", () => { }) ); - let caught; - try { - await resolveGitHubPullRequestMergeCommits( + await assert.rejects( + resolveGitHubPullRequestMergeCommits( ["PR_secondary_limited"], "test-token" - ); - } catch (error) { - caught = error; - } - expect(caught).toMatchObject({ - code: "source_incomplete", - kind: "rate_limited", - retryable: true, - }); - assert.ok(caught instanceof GitHubGraphQlResponseError && caught.retryAt); - expect(caught.retryAt.getTime()).toBeGreaterThanOrEqual( - requestedAt + 60_000 + ), + { + name: "GitHubGraphQlResponseError", + code: "source_incomplete", + kind: "rate_limited", + retryable: true, + retryAt: new Date(requestedAt + 60_000), + } ); }); @@ -1128,13 +1139,14 @@ describe("GitHub pull request merge commit resolution", () => { }) ); - expect( - resolveGitHubPullRequestMergeCommits(["PR_hidden"], "test-token") - ).rejects.toMatchObject({ - code: "source_incomplete", - kind: "access_denied", - retryable: true, - }); + await assert.rejects( + resolveGitHubPullRequestMergeCommits(["PR_hidden"], "test-token"), + { + code: "source_incomplete", + kind: "access_denied", + retryable: true, + } + ); }); }); @@ -1191,7 +1203,7 @@ describe("GitHub pull request acquisition", () => { Response.json([{ ...pullRequest, node_id: null }]) ); - expect( + await assert.rejects( fetchGitHubAssociatedPullRequests({ author: "f0rr0" as const, committedAt: "2026-08-28T12:00:00.000Z", @@ -1199,8 +1211,9 @@ describe("GitHub pull request acquisition", () => { repository: repository.full_name, repositoryId: String(repository.id), sha: "3".repeat(40), - }) - ).rejects.toMatchObject({ code: "source_invalid" }); + }), + { code: "source_invalid" } + ); }); test("resolves an associated REST 2026 merged PR through GraphQL", async () => { @@ -1641,7 +1654,7 @@ describe("GitHub pull request acquisition", () => { }); }); - expect( + await assert.rejects( fetchGitHubPullRequestMembershipWithToken( { account: "f0rr0" as const, @@ -1652,8 +1665,9 @@ describe("GitHub pull request acquisition", () => { 251, "test-token", { expectedBaseSha: baseSha, expectedHeadSha: headSha } - ) - ).rejects.toMatchObject({ code: "source_incomplete" }); + ), + { code: "source_incomplete" } + ); }); test("rejects comparison links that skip a page", async () => { @@ -1675,7 +1689,7 @@ describe("GitHub pull request acquisition", () => { ) ); - expect( + await assert.rejects( fetchGitHubPullRequestMembershipWithToken( { account: "f0rr0" as const, @@ -1686,8 +1700,9 @@ describe("GitHub pull request acquisition", () => { 251, "test-token", { expectedBaseSha: baseSha, expectedHeadSha: headSha } - ) - ).rejects.toMatchObject({ code: "source_invalid" }); + ), + { code: "source_invalid" } + ); }); }); @@ -1745,7 +1760,7 @@ describe("GitHub activity provider deadlines", () => { ), ]; for (const call of callsWithDeadline) { - expect(call()).rejects.toBeInstanceOf(GitHubRequestDeadlineError); + await assert.rejects(call(), GitHubRequestDeadlineError); } expect(calls).toBe(0); }); diff --git a/tests/github-activity-status.test.ts b/tests/github-activity-status.test.ts index 49e9836f..601fb744 100644 --- a/tests/github-activity-status.test.ts +++ b/tests/github-activity-status.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import assert from "node:assert/strict"; import { comparePublicActivityRevisions, @@ -56,17 +57,18 @@ describe("public GitHub activity status", () => { globalThis.fetch = mockFetch(async () => Response.json({ revision: "bad" }) ); - expect(fetchPublicActivityHead(settledHead)).rejects.toThrow("invalid"); + await assert.rejects(fetchPublicActivityHead(settledHead), /invalid/u); globalThis.fetch = mockFetch( async () => new Response(null, { status: 503 }) ); - expect(fetchPublicActivityHead(settledHead)).rejects.toThrow( - "unavailable" + await assert.rejects( + fetchPublicActivityHead(settledHead), + /unavailable/u ); globalThis.fetch = mockFetch(async () => { throw new TypeError("offline"); }); - expect(fetchPublicActivityHead(settledHead)).rejects.toThrow("offline"); + await assert.rejects(fetchPublicActivityHead(settledHead), /offline/u); } finally { globalThis.fetch = originalFetch; } diff --git a/tests/github-activity-worker.test.ts b/tests/github-activity-worker.test.ts index 232c059e..67d4aeef 100644 --- a/tests/github-activity-worker.test.ts +++ b/tests/github-activity-worker.test.ts @@ -9,7 +9,6 @@ import { githubActivityRetryAt, githubPullRequestSnapshotDisposition, githubPrReconciliationCutoff, - GITHUB_PR_RECONCILIATION_MAX_AGE_DAYS, nextGitHubPullRequestReconciliationAt, workerBatchSizeFrom, workerDeadlineReached, @@ -132,7 +131,6 @@ describe("GitHub activity worker bounds", () => { }); test("keeps open PR reconciliation unbounded with age-aware cadence", () => { - expect(GITHUB_PR_RECONCILIATION_MAX_AGE_DAYS).toBe(Infinity); const now = new Date("2026-08-28T12:00:00.000Z"); expect(githubPrReconciliationCutoff(Infinity, now)).toBeNull(); expect( diff --git a/tests/github-backfill.test.ts b/tests/github-backfill.test.ts index 7e4a1b6d..b71d5aba 100644 --- a/tests/github-backfill.test.ts +++ b/tests/github-backfill.test.ts @@ -705,18 +705,3 @@ describe("GitHub factual history backfill", () => { expect(workerPasses).toBe(1); }); }); - -test("workflow secrets avoid GitHub's reserved prefix except its built-in token", async () => { - const directory = new URL("../.github/workflows/", import.meta.url); - let checked = 0; - for await (const file of new Bun.Glob("*.yml").scan(directory.pathname)) { - const workflow = await Bun.file(new URL(file, directory)).text(); - for (const [, name] of workflow.matchAll(/\bsecrets\.([A-Za-z_]\w*)/g)) { - expect( - name === "GITHUB_TOKEN" || !name.toUpperCase().startsWith("GITHUB_") - ).toBe(true); - checked += 1; - } - } - expect(checked).toBeGreaterThan(0); -}); diff --git a/tests/github-commits.test.ts b/tests/github-commits.test.ts index c99cefb3..b3e117c7 100644 --- a/tests/github-commits.test.ts +++ b/tests/github-commits.test.ts @@ -1,11 +1,17 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { + afterEach, + beforeEach, + describe, + expect, + setSystemTime, + test, +} from "bun:test"; import assert from "node:assert/strict"; import { setTimeout as delay } from "node:timers/promises"; import { fetchGitHub, GitHubRequestDeadlineError, - GitHubResponseError, githubApiUrl, } from "../src/lib/github-api.ts"; import { @@ -24,10 +30,7 @@ import { repositoryFrom, } from "../src/lib/github-commits-core.ts"; import { isGitHubAccountPaused } from "../src/lib/github-commits-store.ts"; -import { - assertGitHubTokenIdentity, - collectGitHubEvents, -} from "../src/lib/github-commits.ts"; +import { collectGitHubEvents } from "../src/lib/github-commits.ts"; import { mockFetch } from "./helpers.ts"; const originalFetch = globalThis.fetch; @@ -46,7 +49,12 @@ const branchLineageRef = ( active = true ) => ({ active, branchLineageId, headSha, refName }); +beforeEach(() => { + setSystemTime(new Date("2026-09-29T12:00:00Z")); +}); + afterEach(() => { + setSystemTime(); globalThis.fetch = originalFetch; }); @@ -988,30 +996,6 @@ describe("token identity", () => { ).toBeNull(); expect(authenticatedGitHubAccountFrom({ login: "f0rr0" })).toBeNull(); }); - - test("verifies the authenticated account before an inventory scan", async () => { - globalThis.fetch = mockFetch(async (input, init) => { - expect(new Request(input).url).toBe("https://api.github.com/user"); - expect(new Headers(init?.headers).get("authorization")).toBe( - "Bearer token" - ); - return Response.json({ id: 8_574_219, login: "f0rr0" }); - }); - - expect( - assertGitHubTokenIdentity("f0rr0", "token") - ).resolves.toBeUndefined(); - }); - - test("rejects a token authenticated as the wrong account", async () => { - globalThis.fetch = mockFetch(async () => - Response.json({ id: 123_456, login: "f0rr0" }) - ); - - expect(assertGitHubTokenIdentity("f0rr0", "token")).rejects.toThrow( - "The GitHub token is not authenticated as f0rr0" - ); - }); }); describe("account pause state", () => { @@ -1071,12 +1055,7 @@ describe("bounded event collection", () => { latestEventId: "12", notModified: true, }); - expect(collected.nextPollAt.getTime()).toBeGreaterThanOrEqual( - startedAt + 299_000 - ); - expect(collected.nextPollAt.getTime()).toBeLessThanOrEqual( - startedAt + 301_000 - ); + expect(collected.nextPollAt.getTime()).toBe(startedAt + 300_000); }); test("collects a real sparse pull request event without poisoning its page", async () => { @@ -1147,7 +1126,8 @@ describe("bounded event collection", () => { globalThis.fetch = mockFetch(async () => Response.json([accountEvent("12")]) ); - expect(collectGitHubEvents("f0rr0", "token", null)).rejects.toBeInstanceOf( + await assert.rejects( + collectGitHubEvents("f0rr0", "token", null), TypeError ); }); @@ -1161,7 +1141,8 @@ describe("bounded event collection", () => { }, }) ); - expect(collectGitHubEvents("f0rr0", "token", null)).rejects.toBeInstanceOf( + await assert.rejects( + collectGitHubEvents("f0rr0", "token", null), TypeError ); }); @@ -1173,11 +1154,12 @@ describe("bounded event collection", () => { return githubEventResponse([]); }); - expect( + await assert.rejects( collectGitHubEvents("f0rr0", "token", null, null, { deadlineAt: Date.now() - 1, - }) - ).rejects.toBeInstanceOf(GitHubRequestDeadlineError); + }), + GitHubRequestDeadlineError + ); expect(calls).toBe(0); }); }); @@ -1190,12 +1172,13 @@ describe("GitHub request deferral", () => { return Response.json({}); }); - expect( + await assert.rejects( fetchGitHub(githubApiUrl("/user"), { deadlineAt: Date.now() - 1, token: "token", - }) - ).rejects.toBeInstanceOf(GitHubRequestDeadlineError); + }), + GitHubRequestDeadlineError + ); expect(calls).toBe(0); }); @@ -1209,14 +1192,14 @@ describe("GitHub request deferral", () => { }); const startedAt = Date.now(); - expect( + await assert.rejects( fetchGitHub(githubApiUrl("/user"), { deadlineAt: startedAt + 50, token: "token", - }) - ).rejects.toBeInstanceOf(GitHubRequestDeadlineError); + }), + GitHubRequestDeadlineError + ); expect(calls).toBe(1); - expect(Date.now() - startedAt).toBeLessThan(1000); }); test("does not immediately retry a rate-limited request", async () => { @@ -1230,16 +1213,15 @@ describe("GitHub request deferral", () => { }); const startedAt = Date.now(); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - assert.ok(caught instanceof GitHubResponseError); - expect(caught).toMatchObject({ retryable: true, status: 429 }); - assert.ok(caught instanceof GitHubResponseError && caught.retryAt); - expect(caught.retryAt.getTime()).toBeGreaterThanOrEqual(startedAt + 59_000); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 429, + retryable: true, + retryAt: new Date(startedAt + 60_000), + } + ); expect(calls).toBe(1); }); @@ -1254,16 +1236,15 @@ describe("GitHub request deferral", () => { status: 403, }) ); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - assert.ok(caught instanceof GitHubResponseError); - expect(caught).toMatchObject({ retryable: true, status: 403 }); - assert.ok(caught.retryAt); - expect(caught.retryAt.toISOString()).toBe("2033-05-18T03:33:20.000Z"); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 403, + retryable: true, + retryAt: new Date("2033-05-18T03:33:20.000Z"), + } + ); }); test("defers a headerless secondary-limit response for at least one minute", async () => { @@ -1278,16 +1259,15 @@ describe("GitHub request deferral", () => { ) ); const startedAt = Date.now(); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - assert.ok(caught instanceof GitHubResponseError); - expect(caught).toMatchObject({ retryable: true, status: 403 }); - assert.ok(caught instanceof GitHubResponseError && caught.retryAt); - expect(caught.retryAt.getTime()).toBeGreaterThanOrEqual(startedAt + 59_000); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 403, + retryable: true, + retryAt: new Date(startedAt + 60_000), + } + ); }); test("keeps a real permission 403 terminal", async () => { @@ -1301,18 +1281,15 @@ describe("GitHub request deferral", () => { { status: 403 } ) ); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - assert.ok(caught instanceof GitHubResponseError); - expect(caught).toMatchObject({ - retryAt: null, - retryable: false, - status: 403, - }); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 403, + retryable: false, + retryAt: null, + } + ); }); test("fails closed when a 403 error body exceeds the inspection bound", async () => { @@ -1322,17 +1299,15 @@ describe("GitHub request deferral", () => { { status: 403 } ) ); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - expect(caught).toMatchObject({ - retryAt: null, - retryable: false, - status: 403, - }); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 403, + retryable: false, + retryAt: null, + } + ); }); test("gives a headerless 429 a usable retry time", async () => { @@ -1340,15 +1315,15 @@ describe("GitHub request deferral", () => { Response.json({ message: "Too many requests" }, { status: 429 }) ); const startedAt = Date.now(); - let caught; - try { - await fetchGitHub(githubApiUrl("/user"), { token: "token" }); - } catch (error) { - caught = error; - } - expect(caught).toMatchObject({ retryable: true, status: 429 }); - assert.ok(caught instanceof GitHubResponseError && caught.retryAt); - expect(caught.retryAt.getTime()).toBeGreaterThanOrEqual(startedAt + 59_000); + await assert.rejects( + fetchGitHub(githubApiUrl("/user"), { token: "token" }), + { + name: "GitHubResponseError", + status: 429, + retryable: true, + retryAt: new Date(startedAt + 60_000), + } + ); }); test("allows authenticated GraphQL query POSTs", async () => { diff --git a/tests/github-cron-config.test.ts b/tests/github-cron-config.test.ts index 96f8c526..ba7792b3 100644 --- a/tests/github-cron-config.test.ts +++ b/tests/github-cron-config.test.ts @@ -1,19 +1,9 @@ import { describe, expect, test } from "bun:test"; +import assert from "node:assert/strict"; import { GitHubRequestDeadlineError } from "../src/lib/github-api.ts"; import { - GITHUB_CRON_EXECUTION_DURATION_MS, githubCronStatusFromFailedAccounts, - GITHUB_EVENTS_CRON_JOB, - GITHUB_HEAD_REFS_CRON_JOB, - GITHUB_REF_REPOSITORY_BATCH_SIZE, - GITHUB_ROUTINE_MAX_DURATION_SECONDS, - GITHUB_SUMMARY_CRON_JOB, - GITHUB_SUMMARY_REQUEST_BUDGET, - GITHUB_WORKER_EXECUTION_DURATION_MS, - GITHUB_WORKER_HTTP_TIMEOUT_MS, - GITHUB_WORKER_MAX_DURATION_SECONDS, - GITHUB_WORKER_CRON_JOB, githubRefRepositoryLimitFrom, } from "../src/lib/github-cron-config.ts"; import { @@ -23,60 +13,8 @@ import { sortGitHubRefRepositories, } from "../src/lib/github-ref-reconciliation-batch.ts"; -const minutesFrom = (schedule: string) => { - const [minute] = schedule.split(" ", 1); - if (minute === "*/3") { - return Array.from({ length: 20 }, (_, index) => index * 3); - } - if (minute === "*/5") { - return Array.from({ length: 12 }, (_, index) => index * 5); - } - const range = /^(\d+)-(\d+)\/(\d+)$/.exec(minute); - if (range !== null) { - const [, start, end, step] = range.map(Number); - const minutes = []; - for (let value = start; value <= end; value += step) { - minutes.push(value); - } - return minutes; - } - return minute.split(",").map(Number); -}; - describe("GitHub cron configuration", () => { - test("factual jobs stay staggered while summaries run 20 times per hour", () => { - const jobs = [ - GITHUB_EVENTS_CRON_JOB, - GITHUB_WORKER_CRON_JOB, - GITHUB_HEAD_REFS_CRON_JOB, - ]; - const allMinutes = jobs.flatMap((job) => minutesFrom(job.schedule)); - expect(new Set(jobs.map((job) => job.name)).size).toBe(jobs.length); - expect(new Set(allMinutes).size).toBe(allMinutes.length); - expect(minutesFrom(GITHUB_WORKER_CRON_JOB.schedule)).toEqual( - Array.from({ length: 12 }, (_, index) => 2 + index * 5) - ); - - expect(minutesFrom(GITHUB_SUMMARY_CRON_JOB.schedule)).toEqual( - Array.from({ length: 20 }, (_, index) => index * 3) - ); - }); - test("bounds scheduled repository reconciliation", () => { - expect(GITHUB_REF_REPOSITORY_BATCH_SIZE).toBe(8); - expect(GITHUB_ROUTINE_MAX_DURATION_SECONDS).toBe(15); - expect(GITHUB_CRON_EXECUTION_DURATION_MS).toBe( - GITHUB_ROUTINE_MAX_DURATION_SECONDS * 1000 - ); - expect(GITHUB_WORKER_MAX_DURATION_SECONDS).toBe(60); - expect(GITHUB_WORKER_EXECUTION_DURATION_MS).toBeLessThan( - GITHUB_WORKER_HTTP_TIMEOUT_MS - ); - expect(GITHUB_WORKER_HTTP_TIMEOUT_MS).toBe( - GITHUB_WORKER_MAX_DURATION_SECONDS * 1000 - ); - expect(GITHUB_SUMMARY_REQUEST_BUDGET.daily).toBe(100); - expect(GITHUB_SUMMARY_REQUEST_BUDGET.monthly).toBe(3000); expect(githubRefRepositoryLimitFrom(null)).toBe(8); expect(githubRefRepositoryLimitFrom("1")).toBe(1); expect(githubRefRepositoryLimitFrom("8")).toBe(8); @@ -86,15 +24,16 @@ describe("GitHub cron configuration", () => { }); test("fails before claiming a ref lease when the shared deadline is exhausted", async () => { - expect( + await assert.rejects( reconcileGitHubRepositoryRefBatch({ account: "f0rr0", deadlineAt: Date.now() - 1, kind: "head", repositoryLimit: 8, token: "token", - }) - ).rejects.toBeInstanceOf(GitHubRequestDeadlineError); + }), + GitHubRequestDeadlineError + ); }); test("treats every partial account result as an operational failure", () => { diff --git a/tests/github-pull-request-backfill.test.ts b/tests/github-pull-request-backfill.test.ts index fdd66077..d4096d39 100644 --- a/tests/github-pull-request-backfill.test.ts +++ b/tests/github-pull-request-backfill.test.ts @@ -1,4 +1,12 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { + afterEach, + beforeEach, + describe, + expect, + setSystemTime, + test, +} from "bun:test"; +import assert from "node:assert/strict"; import type { GitHubPullRequestBackfillCandidate } from "../src/lib/github-pull-request-backfill.ts"; import { @@ -19,7 +27,12 @@ const originalFetch = globalThis.fetch; const sinceAt = new Date("2026-08-01T00:00:00.000Z"); const untilAt = new Date("2026-08-31T23:59:59.999Z"); +beforeEach(() => { + setSystemTime(new Date("2026-09-29T12:00:00Z")); +}); + afterEach(() => { + setSystemTime(); globalThis.fetch = originalFetch; }); @@ -254,14 +267,15 @@ describe("GitHub authored pull request backfill", () => { ); }); - expect( + await assert.rejects( collectGitHubAuthoredPullRequestBackfillCandidates({ account: "f0rr0", deadlineAt: Date.now() + 60_000, token: "test-token", updatedSinceAt: sinceAt, - }) - ).rejects.toThrow("invalid authored pull request pagination"); + }), + /invalid authored pull request pagination/u + ); }); test("fails closed when a repeated node crosses the history cutoff", async () => { @@ -283,14 +297,15 @@ describe("GitHub authored pull request backfill", () => { ); }); - expect( + await assert.rejects( collectGitHubAuthoredPullRequestBackfillCandidates({ account: "f0rr0", deadlineAt: Date.now() + 60_000, token: "test-token", updatedSinceAt: sinceAt, - }) - ).rejects.toThrow("invalid authored pull request pagination"); + }), + /invalid authored pull request pagination/u + ); }); test("does not scan the accessible repository catalog", async () => { @@ -364,16 +379,13 @@ describe("GitHub authored pull request backfill", () => { } ); - expect(Date.now() - startedAt).toBeLessThan(1000); expect(result).toMatchObject({ complete: false, scannedPullRequests: 1, stopReason: "provider_retry", unavailablePullRequests: 0, }); - expect(result.retryAt?.getTime()).toBeGreaterThanOrEqual( - startedAt + retrySeconds * 1000 - ); + expect(result.retryAt?.getTime()).toBe(startedAt + retrySeconds * 1000); expect(checkpointWrites).toBe(0); }); diff --git a/tests/github-reconciliation.test.ts b/tests/github-reconciliation.test.ts index 61cebcb1..c6a6c3ec 100644 --- a/tests/github-reconciliation.test.ts +++ b/tests/github-reconciliation.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test"; +import assert from "node:assert/strict"; import type { GitHubRepositoryInventoryFacts } from "../src/lib/github-commits-core.ts"; import { @@ -87,8 +88,9 @@ describe("GitHub repository reconciliation", () => { Response.json([{ ...repository, topics: undefined }]) ); - expect(collectAccessibleGitHubRepositories("token")).rejects.toThrow( - "invalid repository response" + await assert.rejects( + collectAccessibleGitHubRepositories("token"), + /invalid repository response/u ); }); @@ -131,7 +133,8 @@ describe("GitHub repository reconciliation", () => { }); }); - expect(collectAccessibleGitHubRepositories("token")).rejects.toBeInstanceOf( + await assert.rejects( + collectAccessibleGitHubRepositories("token"), TypeError ); expect(requests).toBe(1); diff --git a/tests/github-summary-cron-route.test.ts b/tests/github-summary-cron-route.test.ts deleted file mode 100644 index 75fea10b..00000000 --- a/tests/github-summary-cron-route.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, test } from "bun:test"; - -import { POST } from "../src/app/api/cron/github-summary/route.ts"; - -describe("GitHub summary cron route", () => { - test("rejects an unauthenticated invocation before running summary work", async () => { - const response = await POST( - new Request("https://f0rr0.dev/api/cron/github-summary", { - method: "POST", - }) - ); - - expect(response.status).toBe(401); - expect(await response.json()).toEqual({ ok: false }); - }); -}); diff --git a/tests/github-timeline-fallback.test.tsx b/tests/github-timeline-fallback.test.tsx deleted file mode 100644 index 4615d010..00000000 --- a/tests/github-timeline-fallback.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { expect, test } from "bun:test"; - -import { renderToStaticMarkup } from "react-dom/server"; - -import { GitHubTimeline } from "../src/components/github-timeline.tsx"; -import { primaryGitHubProfile } from "../src/content/resume.ts"; - -test("a cold outage omits the homepage preview and keeps the work page useful", () => { - expect( - renderToStaticMarkup() - ).toBe(""); - const html = renderToStaticMarkup(); - expect(html).toContain(`href="${primaryGitHubProfile.url}"`); - expect(html).toContain("Work activity is temporarily unavailable."); - expect(html).not.toContain("github-activity-paginated-days"); -}); diff --git a/tests/github-work-unit-crosswalk.test.ts b/tests/github-work-unit-crosswalk.test.ts index 2fb0be1c..a06d2d4d 100644 --- a/tests/github-work-unit-crosswalk.test.ts +++ b/tests/github-work-unit-crosswalk.test.ts @@ -366,7 +366,6 @@ describe("GitHub work-unit crosswalk", () => { ], passed: false, }); - expect(report.version).toBe(4); }); test("fails if a verified merge landing is projected as ref-owned work", () => { diff --git a/tests/github-work-unit-summary-provider.test.ts b/tests/github-work-unit-summary-provider.test.ts index 2a52b180..c5f8403a 100644 --- a/tests/github-work-unit-summary-provider.test.ts +++ b/tests/github-work-unit-summary-provider.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, spyOn, test } from "bun:test"; import assert from "node:assert/strict"; import type { generateText } from "ai"; @@ -91,7 +91,6 @@ describe("GitHub work-unit summary provider", () => { }), }); - expect(call).toBeDefined(); assert.ok(call); expect(call.model).toMatchObject({ modelId: GITHUB_WORK_UNIT_SUMMARY_PROVIDER_POLICY.model, @@ -133,21 +132,21 @@ describe("GitHub work-unit summary provider", () => { }), outputTokens: 6, }); - expect(result.latencyMs).toBeGreaterThanOrEqual(0); }); test("propagates an upstream failure unchanged for worker retry", async () => { const upstream = new Error("network unavailable"); let calls = 0; - expect( + await assert.rejects( generateGitHubWorkUnitSummary(request(), { generateText: mockGenerateText(async () => { calls += 1; throw upstream; }), - }) - ).rejects.toBe(upstream); + }), + (error: unknown) => error === upstream + ); expect(calls).toBe(1); }); @@ -165,7 +164,7 @@ describe("GitHub work-unit summary provider", () => { }; }), }); - expect(semanticFailure).rejects.toMatchObject({ + await assert.rejects(semanticFailure, { name: "GitHubWorkUnitSummaryInvalidOutputError", reason: "url", }); @@ -176,7 +175,7 @@ describe("GitHub work-unit summary provider", () => { throw new NoOutputGeneratedError(); }), }); - expect(structuredFailure).rejects.toMatchObject({ + await assert.rejects(structuredFailure, { name: "GitHubWorkUnitSummaryInvalidOutputError", reason: "invalid_shape", }); @@ -199,7 +198,7 @@ describe("GitHub work-unit summary provider", () => { }); }), }); - expect(schemaFailure).rejects.toMatchObject({ + await assert.rejects(schemaFailure, { name: "GitHubWorkUnitSummaryInvalidOutputError", reason: "invalid_shape", }); @@ -244,51 +243,55 @@ describe("GitHub work-unit summary provider", () => { }; }; for (const serializedInput of invalidInputs) { - let receivedError; - try { - await generateGitHubWorkUnitSummary(request({ serializedInput }), { + await assert.rejects( + generateGitHubWorkUnitSummary(request({ serializedInput }), { generateText: mockGenerateText(generateUnusedSummary), - }); - } catch (error) { - receivedError = error; - } - expect(receivedError).toBeInstanceOf( + }), GitHubWorkUnitSummaryInvalidInputError ); - expect(receivedError).toMatchObject({ - name: "GitHubWorkUnitSummaryInvalidInputError", - }); } expect(calls).toBe(0); }); test("does not start after the deadline and aborts an in-flight request", async () => { let calls = 0; - expect( + await assert.rejects( generateGitHubWorkUnitSummary(request({ deadlineAt: Date.now() - 1 }), { generateText: mockGenerateText(async () => { calls += 1; return { output: { headline: "Unused", summary: "Unused." }, usage }; }), - }) - ).rejects.toMatchObject({ name: "TimeoutError" }); + }), + { name: "TimeoutError" } + ); expect(calls).toBe(0); - let observedSignal: AbortSignal | undefined; - const inFlight = generateGitHubWorkUnitSummary( - request({ deadlineAt: Date.now() + 20 }), - { - generateText: mockGenerateText(async ({ abortSignal }) => { - observedSignal = abortSignal; - await Bun.sleep(30); - assert.ok(abortSignal); - abortSignal.throwIfAborted(); - return { output: { headline: "Unused", summary: "Unused." }, usage }; - }), - } + const now = 1_790_683_200_000; + const clock = spyOn(Date, "now").mockReturnValue(now); + const controller = new AbortController(); + const timeout = spyOn(AbortSignal, "timeout").mockReturnValue( + controller.signal ); - expect(inFlight).rejects.toMatchObject({ name: "TimeoutError" }); - assert.ok(observedSignal); - expect(observedSignal.aborted).toBe(true); + const reason = new DOMException("Deadline reached", "TimeoutError"); + try { + await assert.rejects( + generateGitHubWorkUnitSummary(request({ deadlineAt: now + 1000 }), { + generateText: mockGenerateText(async ({ abortSignal }) => { + expect(abortSignal).toBe(controller.signal); + controller.abort(reason); + abortSignal?.throwIfAborted(); + return { + output: { headline: "Unused", summary: "Unused." }, + usage, + }; + }), + }), + reason + ); + expect(timeout.mock.calls).toEqual([[1000]]); + } finally { + clock.mockRestore(); + timeout.mockRestore(); + } }); }); diff --git a/tests/github-work-unit-summary.test.ts b/tests/github-work-unit-summary.test.ts index d3757bc6..c5b39455 100644 --- a/tests/github-work-unit-summary.test.ts +++ b/tests/github-work-unit-summary.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import assert from "node:assert/strict"; import type { GitHubWorkUnitSummaryCandidate, @@ -437,16 +438,15 @@ describe("GitHub work-unit summary evidence", () => { }); test("rejects an invalid projection membership as a contract error", async () => { - expect( + await assert.rejects( buildGitHubWorkUnitSummaryInput( candidate({ membership: { members: [], unitKey: "pr-42" } }) - ) - ).rejects.toThrow("membership"); + ), + /membership/u + ); }); test("compacts oversized net and composite evidence without dropping files or changes", async () => { - expect(GITHUB_WORK_UNIT_SUMMARY_MAX_INPUT_TOKENS).toBe(32_000); - expect(GITHUB_WORK_UNIT_SUMMARY_MAX_PAYLOAD_BYTES).toBe(393_216); const largeFile = (filename: string): GitHubWorkUnitSummaryFileEvidence => { const additions = Array.from( { length: 40 }, diff --git a/tests/hourly-publication-resilience.test.ts b/tests/hourly-publication-resilience.test.ts deleted file mode 100644 index dd37352e..00000000 --- a/tests/hourly-publication-resilience.test.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { expect, test } from "bun:test"; - -// Isolate module mocks from the rest of the test suite. -const check = (source: string) => { - const result = Bun.spawnSync([process.execPath, "--eval", source], { - cwd: new URL("..", import.meta.url).pathname, - }); - expect(result.stderr.toString()).toBe(""); - expect(result.exitCode).toBe(0); -}; - -test("a policy rebuild is queued once so retries retain only unfinished repositories", () => { - check(` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - import { githubPublicFeedHead, githubRepositories } from "./src/db/schema.ts"; - const head = { policyDigest: null, token: null }; - let fullRebuilds = 0; - let transactions = 0; - const transaction = { - execute: async () => {}, - select: () => ({ from: () => ({ where: () => ({ - for: async () => [head], then: resolve => resolve([head]), - }) }) }), - update: table => ({ set: values => ({ where: () => { - if (table === githubRepositories) fullRebuilds++; - if (table === githubPublicFeedHead) { - if ("projectionRequestToken" in values) head.token = values.projectionRequestToken; - if ("summaryPolicyDigest" in values) head.policyDigest = values.summaryPolicyDigest; - } - return { returning: async () => [{ token: head.token }], then: resolve => resolve([]) }; - } }) }), - }; - mock.module("./src/db/client.ts", () => ({ getDatabase: () => ({ - transaction: async callback => { transactions++; return await callback(transaction); }, - }) })); - const { ensureGitHubWorkUnitProjectionRequest: ensure } = await import("./src/lib/github-work-unit-projection-state.ts"); - const first = await ensure(); - assert.ok(first); - assert.equal(transactions, 1); - assert.match(head.policyDigest, /^[a-f0-9]{64}$/); - assert.equal(await ensure(), first); - assert.equal(fullRebuilds, 1); - // Completing a batch or ingesting a new push must not repeat the policy rebuild. - head.token = crypto.randomUUID(); - assert.equal(await ensure(), head.token); - assert.equal(fullRebuilds, 1); - head.token = null; - assert.equal(await ensure(), null); - assert.equal(fullRebuilds, 1); - // A new policy must queue all repositories even when scoped work is already pending. - head.token = crypto.randomUUID(); - head.policyDigest = null; - assert.notEqual(await ensure(), first); - assert.equal(fullRebuilds, 2); - `); -}); - -test("publication keeps source dates through delayed syncs, metadata revisions and IST midnight", () => { - check(` - import assert from "node:assert/strict"; - import { mock, setSystemTime } from "bun:test"; - import { PgDialect } from "drizzle-orm/pg-core"; - import { githubActivitySnapshots, githubPublicFeedHead, githubWorkUnits } from "./src/db/schema.ts"; - setSystemTime(new Date("2026-09-28T18:30:00Z")); - const work = (id, activityAt, additions = 200) => ({ - id, activityAt, repository: { key: "1" }, facts: { additions } - }); - const rows = [ - work("canonical:1:2026-08-04", "2026-08-04T09:05:36Z"), - work("late", "2026-09-28T18:29:59Z"), - work("changed-pr", "2026-09-28T18:30:00Z", 240), - ...["legacy-merge", "status-hydration"].map(id => ({ - ...work(id, "2026-09-28T18:00:00Z"), - pullRequest: { status: "merged", statusChangedAt: id === "legacy-merge" - ? "2026-09-28T18:00:00Z" : "2026-09-01T12:00:00Z" }, - })), - work("future", "2026-09-29T18:30:00Z"), - ]; - const units = rows.map(row => ({ - id: row.id, identityKey: row.id, factsDigest: "new-metadata", revision: 7 - })); - const previous = [{ - identityKey: rows[0].id, day: "2026-08-04", payload: rows[0], - factsDigest: "old-metadata", workUnitRevision: 6, - }, { - identityKey: "changed-pr", day: "2026-09-28", - payload: work("changed-pr", "2026-09-28T12:00:00Z"), - }, ...["legacy-merge", "status-hydration"].map(id => ({ - identityKey: id, day: "2026-09-27", payload: work(id, "2026-09-27T12:00:00Z"), - }))]; - const issue = { id: "late-issue", activityAt: "2026-09-28T18:29:59Z", repository: { key: "1" } }; - const writes = []; - const query = result => { - const chain = { - where: () => chain, orderBy: () => chain, innerJoin: () => chain, - then: resolve => resolve(result), - }; - return chain; - }; - const transaction = { - select: () => ({ from: table => query(table === githubPublicFeedHead - ? [{ initialized: new Date("2026-09-27") }] : table === githubWorkUnits ? units : []) }), - selectDistinctOn: () => ({ from: () => query(previous) }), - execute: async sql => { - const cleanup = new PgDialect().sqlToQuery(sql); - assert.ok(cleanup.sql.includes("s.day between $1::date and $2::date")); - assert.deepEqual(cleanup.params, ["2026-09-28", "2026-09-29", '["1"]', '["2"]']); - return { rows: [] }; - }, - insert: table => ({ values: value => ({ onConflictDoUpdate: options => ({ returning: async () => { - assert.equal(table, githubActivitySnapshots); - const condition = new PgDialect().sqlToQuery(options.setWhere); - assert.deepEqual(condition.params, ["2026-09-28", value.payload.activityAt]); - assert.ok(condition.sql.includes('"github_activity_snapshots"."day" >= $1')); - assert.ok(condition.sql.includes("::timestamptz <= $2::timestamptz")); - writes.push(value); - return []; - } }) }) }), - }; - mock.module("./src/db/client.ts", () => ({ getDatabase: () => ({ transaction: callback => callback(transaction) }) })); - mock.module("./src/lib/github-work-unit-projection-state.ts", () => ({ acquireGitHubWorkUnitProjectionLock: async () => {} })); - mock.module("./src/lib/github-activity-store.ts", () => ({ readCurrentPublicGitHubRows: async () => ({ workUnits: rows, issues: [issue] }) })); - const { publishGitHubActivitySnapshots } = await import("./src/lib/github-activity-snapshots.ts"); - await publishGitHubActivitySnapshots(["1"], ["2"]); - assert.deepEqual(writes.map(row => [row.identityKey, row.day, row.payload.activityAt]), [ - ["late", "2026-09-28", "2026-09-28T18:29:59Z"], - ["changed-pr", "2026-09-29", "2026-09-28T18:30:00Z"], - ["legacy-merge", "2026-09-28", "2026-09-28T18:00:00Z"], - ["late-issue", "2026-09-28", "2026-09-28T18:29:59Z"], - ]); - assert.equal(writes[1].payload.facts.additions, 240); - `); -}); - -test("every worker publishes and warms changed activity without changing ingestion limits or auth", () => { - check(` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - const calls = []; - const events = []; - const callbacks = []; - let changed = false; - let fail = false; - mock.module("next/cache", () => ({ revalidateTag: (...args) => events.push(args) })); - mock.module("next/server", () => ({ after: callback => callbacks.push(callback) })); - mock.module("./src/lib/github-activity-feed.ts", () => ({ getInitialGitHubActivity: async () => { events.push("warmed"); } })); - mock.module("./src/lib/operational-error.ts", () => ({ reportOperationalError: () => "Error" })); - mock.module("./src/env.ts", () => ({ env: { CRON_SECRET: "hourly-publication-test-secret-32-characters" } })); - mock.module("./src/lib/github-activity-worker.ts", () => ({ - runGitHubActivityWorker: async options => { - if (fail) throw new Error("Publication failed"); - calls.push(options); - return { projection: options.includeProjection ? { feedRevisionChanged: changed } : null }; - } - })); - const { POST } = await import("./src/app/api/cron/github-worker/route.ts"); - const request = (query, authorized = true) => POST(new Request( - "https://example.com/api/cron/github-worker" + query, - { method: "POST", headers: authorized ? { authorization: "Bearer hourly-publication-test-secret-32-characters" } : {} } - )); - assert.equal((await request("?publish=1", false)).status, 401); - for (const query of ["?batch=0", "?batch=no", "?batch="]) { - assert.equal((await request(query)).status, 400); - } - assert.equal(calls.length, 0); - assert.equal((await request("")).status, 200); - assert.equal((await request("?publish=1")).status, 200); - assert.equal((await request("?publish=1&batch=2")).status, 200); - assert.equal(calls[0].includeProjection, true); - assert.equal(calls[1].includeProjection, true); - assert.deepEqual({ ...calls[0], includeProjection: true }, calls[1]); - assert.equal(calls[2].includeProjection, true); - assert.equal(calls[2].commitLimit, 2); - assert.equal(calls[2].refLimit, 1); - assert.deepEqual(events, []); - assert.equal(callbacks.length, 0); - changed = true; - assert.equal((await request("?publish=1")).status, 200); - assert.deepEqual(events, [["public-github-activity", { expire: 0 }]]); - assert.equal(callbacks.length, 1); - await callbacks.shift()(); - assert.deepEqual(events, [["public-github-activity", { expire: 0 }], "warmed"]); - events.length = 0; - fail = true; - assert.equal((await request("?publish=1")).status, 503); - assert.deepEqual(events, []); - assert.equal(callbacks.length, 0); - `); -}); - -test("Codex sync warms the public cache after a successful change, never after a failed or unchanged sync", () => { - check(` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - const events = []; - const callbacks = []; - let fail = false; - let updated = 2; - mock.module("next/cache", () => ({ revalidateTag: (...args) => events.push(args) })); - mock.module("next/server", () => ({ after: callback => callbacks.push(callback) })); - mock.module("./src/lib/codex/public-stats.ts", () => ({ getPublicCodexStats: async () => { events.push("warmed"); } })); - mock.module("./src/env.ts", () => ({ env: { CRON_SECRET: "codex-cache-test-secret-32-characters" } })); - mock.module("./src/lib/operational-error.ts", () => ({ reportOperationalError: () => "Error" })); - mock.module("./src/lib/codex/sync.ts", () => ({ syncCodexAccounts: async () => { - if (fail) throw new Error("Sync failed"); - events.push("saved"); - return { updated }; - } })); - const { POST } = await import("./src/app/api/cron/codex-stats/route.ts"); - const request = (authorized = true) => POST(new Request("https://example.com/api/cron/codex-stats", { - method: "POST", headers: authorized ? { authorization: "Bearer codex-cache-test-secret-32-characters" } : {} - })); - assert.equal((await request(false)).status, 401); - assert.deepEqual(events, []); - assert.equal((await request()).status, 200); - assert.deepEqual(events, ["saved", ["public-codex-stats", "max"]]); - assert.equal(callbacks.length, 1); - await callbacks.shift()(); - assert.deepEqual(events, ["saved", ["public-codex-stats", "max"], "warmed"]); - events.length = 0; - updated = 0; - assert.equal((await request()).status, 200); - assert.deepEqual(events, ["saved"]); - assert.equal(callbacks.length, 0); - events.length = 0; - fail = true; - assert.equal((await request()).status, 503); - assert.deepEqual(events, []); - assert.equal(callbacks.length, 0); - `); -}); - -test("public reads retain successful snapshots on outage and recover without caching failures", () => { - check(` - import assert from "node:assert/strict"; - import { AsyncLocalStorage } from "node:async_hooks"; - import { mock } from "bun:test"; - Object.assign(globalThis, { AsyncLocalStorage }); - mock.module("server-only", () => ({})); - mock.module("./src/lib/operational-error.ts", () => ({ reportOperationalError: () => {} })); - const { workAsyncStorage } = await import("next/dist/server/app-render/work-async-storage.external.js"); - let unavailable = true; - let version = 1; - let reads = 0; - const read = async () => { - reads++; - if (unavailable) throw new Error("Database unavailable"); - return { version }; - }; - mock.module("./src/lib/github-activity-store.ts", () => ({ - PUBLIC_GITHUB_ACTIVITY_DAY_PAGE_SIZE: 7, - readPublicGitHubActivityPage: read, - readPublicGitHubActivityHead: read, - })); - mock.module("./src/db/client.ts", () => ({ - isDatabaseConfigured: () => true, - getDatabase: () => ({ select: () => { - const query = { from: () => query, where: () => query, orderBy: () => query, - then: (resolve, reject) => read().then(() => []).then(resolve, reject) }; - return query; - } }), - })); - mock.module("./src/lib/codex/stats.ts", () => ({ buildPublicCodexStats: () => ({ version }) })); - const { getInitialGitHubActivity } = await import("./src/lib/github-activity-feed.ts"); - const { getPublicCodexStats } = await import("./src/lib/codex/public-stats.ts"); - const entries = new Map(); - let stale = false; - const incrementalCache = { - generateSimpleCacheKey: async key => { - if (key.includes("public-github-activity-initial-")) { - // A persistent cache from the previous deployment must never reach the new UI. - entries.set(key.replace(/initial-v[0-9]+/, "initial-v3"), { - kind: "FETCH", revalidate: 60, - data: { headers: {}, status: 200, url: "", body: '{"version":"old-issue-format"}' }, - }); - } - return key; - }, - get: async key => entries.has(key) ? { value: entries.get(key), isStale: stale } : null, - set: async (key, value) => { entries.set(key, value); }, - }; - const request = async () => { - const store = { incrementalCache, forceDynamic: true, pendingRevalidates: {} }; - const result = await workAsyncStorage.run(store, () => Promise.all([ - getInitialGitHubActivity(), getPublicCodexStats() - ])); - await Promise.all(Object.values(store.pendingRevalidates)); - return result; - }; - assert.deepEqual(await request(), [null, null]); - assert.equal(entries.size, 1); - unavailable = false; - assert.deepEqual(await request(), [{ version: 1 }, { version: 1 }]); - const afterFill = reads; - assert.deepEqual(await request(), [{ version: 1 }, { version: 1 }]); - assert.equal(reads, afterFill); - stale = true; - unavailable = true; - const errors = []; - console.error = (...args) => errors.push(args); - assert.deepEqual(await request(), [{ version: 1 }, { version: 1 }]); - assert.equal(errors.length, 3); - assert.equal(entries.size, 4); - unavailable = false; - version = 2; - assert.deepEqual(await request(), [{ version: 1 }, { version: 1 }]); - stale = false; - assert.deepEqual(await request(), [{ version: 2 }, { version: 2 }]); - `); -}); - -test("live refresh retries a stale page on the next successful poll and stops once caught up", () => { - check(` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - const React = await import("react"); - const ReactQuery = await import("@tanstack/react-query"); - let refreshes = 0; - let previousDependencies; - const context = { - feedRevision: "7", isRefreshing: false, latestAvailable: false, - refreshCompletion: 0, markLatestAvailable: () => {}, - refreshLatest: () => { refreshes++; }, - }; - const head = { feedRevision: "8", revision: "9", lastPublishedAt: null, summarizing: false }; - const query = { data: head, dataUpdatedAt: 1 }; - const ref = { current: "7" }; - mock.module("react", () => ({ - ...React, - use: () => context, - useRef: () => ref, - useEffect: (effect, dependencies) => { - if (!previousDependencies || dependencies.some((value, index) => !Object.is(value, previousDependencies[index]))) effect(); - previousDependencies = dependencies; - }, - })); - mock.module("@tanstack/react-query", () => ({ ...ReactQuery, useQuery: () => query })); - const { GitHubActivityStatus } = await import("./src/components/github-activity-status.tsx"); - const render = () => GitHubActivityStatus({ initialHead: { ...head, feedRevision: "7" } }); - render(); - assert.equal(refreshes, 1); - render(); - assert.equal(refreshes, 1); - query.dataUpdatedAt++; - render(); - assert.equal(refreshes, 2); - context.feedRevision = "8"; - query.dataUpdatedAt++; - render(); - assert.equal(refreshes, 2); - const { renderToStaticMarkup } = await import("react-dom/server"); - let markup = renderToStaticMarkup(render()); - assert.match(markup, /visibility:hidden/); - context.latestAvailable = true; - context.isRefreshing = true; - markup = renderToStaticMarkup(render()); - assert.match(markup, /visibility:visible/); - assert.match(markup, /disabled=""/); - assert.match(markup, /Refreshing…/); - context.isRefreshing = false; - markup = renderToStaticMarkup(render()); - assert.match(markup, /Refresh work/); - assert.doesNotMatch(markup, /disabled=""/); - `); -}); - -test("repeated connection refusals keep a bounded reconnect delay", () => { - check(` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - mock.module("./src/env.ts", () => ({ - env: { DATABASE_URL: "postgresql://test:test@127.0.0.1:1/unavailable" } - })); - const { getDatabase, closeDatabase } = await import("./src/db/client.ts"); - const start = performance.now(); - try { - for (let attempt = 0; attempt < 8; attempt++) { - await assert.rejects(getDatabase().execute("select 1")); - } - assert.ok(performance.now() - start < 12000); - } finally { - await closeDatabase(); - } - `); -}, 15_000); diff --git a/tests/knip.test.ts b/tests/knip.test.ts deleted file mode 100644 index 677eb681..00000000 --- a/tests/knip.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { expect, test } from "bun:test"; -import { - copyFile, - mkdir, - mkdtemp, - rm, - symlink, - writeFile, -} from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const root = fileURLToPath(new URL("..", import.meta.url)); - -test("Knip catches code kept alive only by tests without dropping build or maintenance callers", async () => { - const directory = await mkdtemp(path.join(tmpdir(), "knip-regression-")); - try { - await copyFile(path.join(root, "knip.ts"), path.join(directory, "knip.ts")); - await symlink( - path.join(root, "node_modules"), - path.join(directory, "node_modules") - ); - const files = { - "package.json": JSON.stringify({ - name: "knip-regression", - private: true, - type: "module", - dependencies: { next: "16.3.4", zod: "4.4.3" }, - scripts: { test: "bun test ./tests" }, - }), - "src/app/page.tsx": - 'import { live } from "../shared"; export default function Page() { return live; }', - "src/shared.ts": - "export const live = 1; export const onlyUsedByTest = 2; export const unreferencedExport = 3;", - "src/test-only.ts": "export const testOnly = 1;", - "src/orphan.ts": "export const orphan = 1;", - "tests/fixture.test.ts": - 'import { onlyUsedByTest } from "../src/shared"; import { testOnly } from "../src/test-only"; void onlyUsedByTest; void testOnly;', - "scripts/maintain.ts": - 'import { maintenance } from "../src/maintenance"; void maintenance;', - "src/maintenance.ts": "export const maintenance = 1;", - "next.config.ts": - 'import plugin from "./src/build-plugin.mjs"; export default { plugin };', - "src/build-plugin.mjs": "export default function plugin() {}", - ".github/workflows/maintenance.yml": - "name: maintenance\non: workflow_dispatch\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - run: bun scripts/maintain.ts\n", - }; - await Promise.all( - Object.entries(files).map(async ([file, contents]) => { - const target = path.join(directory, file); - await mkdir(path.dirname(target), { recursive: true }); - await writeFile(target, contents); - }) - ); - - for (const production of [false, true]) { - const child = Bun.spawn( - [ - process.execPath, - path.join(root, "node_modules/knip/bin/knip.js"), - "--include", - "files,exports,dependencies", - "--reporter", - "json", - ...(production ? ["--production"] : []), - ], - { cwd: directory, stdout: "pipe", stderr: "pipe" } - ); - const [code, output, errors] = await Promise.all([ - child.exited, - new Response(child.stdout).text(), - new Response(child.stderr).text(), - ]); - expect(code, errors).toBe(1); - expect(output).toContain("src/orphan.ts"); - expect(output).toContain("unreferencedExport"); - expect(output).toContain('"zod"'); - for (const symbol of ["onlyUsedByTest", "src/test-only.ts"]) { - expect( - output.includes(symbol), - `${production ? "production" : "default"}: ${symbol}` - ).toBe(production); - } - expect(output).not.toContain("src/maintenance.ts"); - expect(output).not.toContain("src/build-plugin.mjs"); - } - } finally { - await rm(directory, { recursive: true, force: true }); - } -}, 30_000); diff --git a/tests/mdx-image.test.ts b/tests/mdx-image.test.ts deleted file mode 100644 index 2a3734f7..00000000 --- a/tests/mdx-image.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, test } from "bun:test"; - -import { createElement } from "react"; -import { renderToStaticMarkup } from "react-dom/server"; - -import MDXImage from "../src/components/mdx/MDXImage.tsx"; - -test("fallback images preserve dimensions so the page reserves their aspect ratio", () => { - const html = renderToStaticMarkup( - createElement(MDXImage, { - alt: "Architecture overview", - height: "450", - src: "https://example.com/diagram.svg", - width: "800", - }) - ); - - expect(html).toContain('width="800"'); - expect(html).toContain('height="450"'); - expect(html).toContain('alt="Architecture overview"'); - expect(html).toContain('loading="lazy"'); -}); diff --git a/tests/production-database-migration.test.ts b/tests/production-database-migration.test.ts index d2686bc3..6b2326e5 100644 --- a/tests/production-database-migration.test.ts +++ b/tests/production-database-migration.test.ts @@ -6,10 +6,7 @@ import { supabaseCronJobsFrom, supabaseCronUrlsFrom, } from "../scripts/configure-supabase-cron.ts"; -import { - productionMigrationDatabaseUrl, - shouldApplyProductionMigrations, -} from "../scripts/migrate-production-database.ts"; +import { shouldApplyProductionMigrations } from "../scripts/migrate-production-database.ts"; describe("production migration environment", () => { test("runs only for Vercel production deployments", () => { @@ -65,33 +62,6 @@ describe("production Supabase cron URLs", () => { }); }); -describe("production migration database URL", () => { - test("prefers an explicitly non-pooling URL", () => { - expect( - productionMigrationDatabaseUrl({ - DATABASE_URL: "postgresql://fallback:secret@runtime.example:6543/db", - DATABASE_URL_UNPOOLED: - "postgresql://primary:secret@db.example:5432/postgres", - }) - ).toBe("postgresql://primary:secret@db.example:5432/postgres"); - }); - - test("requires a separate administration URL instead of rewriting the runtime pooler", () => { - expect(() => - productionMigrationDatabaseUrl({ - DATABASE_URL: - "postgresql://postgres.project:secret@aws-0-region.pooler.supabase.com:6543/postgres", - }) - ).toThrow("DATABASE_URL_UNPOOLED is not configured"); - expect(() => - productionMigrationDatabaseUrl({ - DATABASE_URL_UNPOOLED: - "postgresql://postgres.project:secret@aws-0-region.pooler.supabase.com:6543/postgres", - }) - ).toThrow("direct connection or session pooler"); - }); -}); - const enabledNames = ( configuration: Parameters[0], codex = false diff --git a/tests/remark-embed-github.test.ts b/tests/remark-embed-github.test.ts index 683725b5..4598da4b 100644 --- a/tests/remark-embed-github.test.ts +++ b/tests/remark-embed-github.test.ts @@ -5,6 +5,7 @@ import { githubTransformer, parseGitHubUrl, } from "../src/lib/remark-embed-github.mjs"; +import { mockFetch } from "./helpers"; const commit = "3e5eed1208b9b444830febcfeecb82a8f3259a3d"; describe("GitHub code reference embeds", () => { @@ -81,7 +82,7 @@ test("GitHub previews preserve PR states, numbers, and escaped remote content", ["closed", true, false, "Closed"], ["closed", false, true, "Merged"], ] as const) { - globalThis.fetch = (async () => + globalThis.fetch = mockFetch(async () => Response.json({ state, draft, @@ -93,7 +94,8 @@ test("GitHub previews preserve PR states, numbers, and escaped remote content", additions: 20, deletions: 3, changed_files: 1, - })) as unknown as typeof fetch; + }) + ); const html = await githubTransformer.getHTML(url); expect(html).toContain(`#3086`); expect(html).not.toContain("#3,086"); diff --git a/tests/resume-pdf.test.ts b/tests/resume-pdf.test.ts deleted file mode 100644 index 392e784f..00000000 --- a/tests/resume-pdf.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect, test } from "bun:test"; -import { execFileSync } from "node:child_process"; -import { readFileSync } from "node:fs"; -import { createRequire } from "node:module"; - -import { GET } from "../src/app/resume/sid-jain-resume.pdf/route.ts"; - -test("the PDF route compiles the resume into a downloadable PDF", async () => { - const response = GET(); - expect(response.status).toBe(200); - expect(response.headers.get("Content-Type")).toBe("application/pdf"); - const bytes = new Uint8Array(await response.arrayBuffer()); - expect(new TextDecoder().decode(bytes.slice(0, 5))).toBe("%PDF-"); - expect(bytes.length).toBeGreaterThan(1000); -}); - -test("Typst logo sizes support fractional values and subsequent classes", () => { - const { typstPath } = createRequire(import.meta.url)("@flukxr/typst-cli"); - const [helpers] = readFileSync("career/typst/resume.typ", "utf-8").split( - "#set document(" - ); - execFileSync(typstPath, ["compile", "--input", "resume={}", "-", "-"], { - input: `${helpers} -#assert.eq(size-from-class("h-3.5 w-6", "h", 99pt), spacing(3.5)) -#assert.eq(size-from-class("h-3.5 w-6", "w", 99pt), spacing(6)) -#assert.eq(size-from-class("rounded-sm", "w", 99pt), 99pt) -`, - stdio: ["pipe", "ignore", "pipe"], - }); -}); diff --git a/tests/setup.ts b/tests/setup.ts index 55f8ccd3..df04cc06 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,4 +1,8 @@ +import { mock } from "bun:test"; import { AsyncLocalStorage } from "node:async_hooks"; +// This import is a Next build-time boundary, not application behavior. +await mock.module("server-only", () => ({})); + // Next installs this global in its server runtime; Bun uses the same native implementation. Object.assign(globalThis, { AsyncLocalStorage }); diff --git a/tests/site-seo.test.ts b/tests/site-seo.test.ts index 7ced04c7..e6582443 100644 --- a/tests/site-seo.test.ts +++ b/tests/site-seo.test.ts @@ -1,207 +1,71 @@ import { expect, test } from "bun:test"; -import { readFileSync } from "node:fs"; -test("deployment aliases never replace public identity, and only previews receive noindex headers", () => { - for (const deployment of ["preview", "production"] as const) { - const result = Bun.spawnSync( - [ - process.execPath, - "--eval", - `const { default: config } = await import("./next.config.ts"); - const { siteConfig, publicUrl } = await import("./src/lib/site.ts"); - console.log(JSON.stringify({ origin: siteConfig.url, article: publicUrl("/writing/example"), headers: await config.headers(), redirects: await config.redirects(), rewrites: await config.rewrites?.() ?? [] }));`, - ], - { - env: { - ...process.env, - NODE_ENV: "production", - VERCEL_ENV: deployment, - VERCEL_URL: "deployment-alias.vercel.app", - VERCEL_PROJECT_PRODUCTION_URL: "project-alias.vercel.app", - }, - } - ); - expect(result.exitCode).toBe(0); - const output = JSON.parse(result.stdout.toString()); - expect(output.origin).toBe("https://project-alias.vercel.app"); - expect(output.article).toBe( - "https://project-alias.vercel.app/writing/example" - ); - expect(output.redirects).toEqual([ - { - source: "/blog/:path*", - destination: "/writing/:path*", - permanent: true, - }, +import nextConfig from "../next.config"; +import { buildBlogMetadata, buildPageMetadata } from "../src/lib/page-metadata"; +import { publicUrl, siteConfig } from "../src/lib/site"; +import { buildBlogPostingJsonLd } from "../src/lib/structured-data"; +import { env } from "./helpers"; + +test("preview deployments are excluded from indexing without hiding production", async () => { + const original = env.VERCEL_ENV; + try { + env.VERCEL_ENV = "preview"; + expect(await nextConfig.headers?.()).toEqual([ { - source: "/work-log/:path*", - destination: "/work/:path*", - permanent: true, + source: "/:path*", + headers: [{ key: "X-Robots-Tag", value: "noindex" }], }, - { source: "/resume", destination: "/journey", permanent: true }, ]); - expect(output.rewrites).toEqual([]); - expect(output.headers).toEqual( - deployment === "preview" - ? [ - { - source: "/:path*", - headers: [{ key: "X-Robots-Tag", value: "noindex" }], - }, - ] - : [] - ); + for (const deployment of [ + "production", + "development", + undefined, + ] as const) { + env.VERCEL_ENV = deployment; + expect(await nextConfig.headers?.()).toEqual([]); + } + } finally { + env.VERCEL_ENV = original; } }); -test("an alternate profile drives site identity, structured exports, education and PDF URLs", () => { - const result = Bun.spawnSync( - [ - process.execPath, - "--eval", - ` - const { githubAccounts } = await import("./src/content/site.ts"); - githubAccounts.splice(0, githubAccounts.length, {login: "alice", id: "12345678"}); - const { resumeData, socialProfiles } = await import("./src/content/resume.ts"); - Object.assign(resumeData.person, { - name: "Alice Example", role: "Engineer", email: "alice@example.com", - image: "/alice.png", alternateNames: ["alice"], - address: {city: "London", region: "England", countryCode: "GB"} - }); - const linkedin = socialProfiles.find(profile => profile.network === "LinkedIn"); - Object.assign(linkedin, {username: "alice", url: "https://linkedin.com/in/alice"}); - resumeData.summary = "Builds useful software."; - resumeData.experience.splice(0); - resumeData.education.splice(0, resumeData.education.length, { - company: "Example University", url: "https://university.example", tagline: "Education", - roles: [{title: "Computer Science", dates: "2020 - 2024", location: "London"}] - }); - const { siteConfig, resumePdfUrl } = await import("./src/lib/site.ts"); - const { buildJsonResume, buildLlmsTxt } = await import("./src/lib/resume.ts"); - const { buildProfilePageJsonLd } = await import("./src/lib/structured-data.ts"); - console.log(JSON.stringify({siteConfig, resumePdfUrl, resume: buildJsonResume(), profile: buildProfilePageJsonLd(), guide: buildLlmsTxt()})); - `, - ], - { env: { ...process.env, VERCEL_PROJECT_PRODUCTION_URL: "alice.example" } } - ); - expect(result.exitCode).toBe(0); - const output = JSON.parse(result.stdout.toString()); - expect(output.siteConfig).toMatchObject({ - name: "Alice Example", - url: "https://alice.example", - author: { handle: "alice", image: "/alice.png" }, - }); - expect(output.resume.basics).toMatchObject({ - name: "Alice Example", - email: "alice@example.com", - location: { city: "London", countryCode: "GB" }, - }); - expect(output.resume.basics.profiles).toContainEqual({ - network: "GitHub", - username: "alice", - url: "https://github.com/alice", +test("page metadata preserves overrides but always uses the canonical page URL", () => { + const image = { url: "/example.png", alt: "Example" }; + const metadata = buildPageMetadata({ + title: "Example", + description: "An example page.", + path: "/example", + type: "profile", + image, + robots: { index: false, follow: false }, + alternates: { + canonical: "https://wrong.example", + types: { "application/rss+xml": "/rss.xml" }, + }, }); - expect(output.profile.mainEntity).toMatchObject({ - name: "Alice Example", - alternateName: ["alice"], - alumniOf: [ - { - "@type": "EducationalOrganization", - name: "Example University", - sameAs: "https://university.example", - }, - ], + expect(metadata.title).toEqual({ absolute: `Example | ${siteConfig.name}` }); + expect(metadata.alternates).toEqual({ + canonical: publicUrl("/example"), + types: { "application/rss+xml": "/rss.xml" }, }); - expect(output.resume.education[0].institution).toBe("Example University"); - expect(output.resumePdfUrl).toBe("/resume/sid-jain-resume.pdf"); - expect(output.guide).toContain( - "https://alice.example/resume/sid-jain-resume.pdf" - ); -}); - -test("main page content produces consistent search and social metadata", async () => { - const { pages } = await import("../src/content/pages"); - const { buildPageMetadata } = await import("../src/lib/page-metadata"); - const { siteConfig, publicUrl } = await import("../src/lib/site"); - - for (const page of Object.values(pages)) { - const metadata = buildPageMetadata(page); - expect(metadata.title.absolute).toBe( - page.path === "/" ? siteConfig.name : `${page.title} | ${siteConfig.name}` - ); - expect(metadata.description).toBe("Software and Writing"); - expect(metadata.openGraph.title).toBe(metadata.title.absolute); - expect(metadata.twitter.title).toBe(metadata.title.absolute); - expect(metadata.openGraph.description).toBe(metadata.description); - expect(metadata.twitter.description).toBe(metadata.description); - expect(metadata.alternates.canonical).toBe(publicUrl(page.path)); - expect(metadata.openGraph.url).toBe(metadata.alternates.canonical); - expect(metadata.openGraph.locale).toBe(siteConfig.locale); - expect(metadata.openGraph.siteName).toBe(siteConfig.name); - expect(metadata.openGraph.type).toBe( - page.path === "/journey" ? "profile" : "website" - ); - expect(metadata.twitter.card).toBe("summary_large_image"); - expect(metadata.openGraph.images).toEqual([siteConfig.shareImage]); - expect(metadata.twitter.images).toEqual([siteConfig.shareImage]); - expect(JSON.stringify(metadata)).not.toMatch(/[—·]/); - } - - const minimal = buildPageMetadata({ title: "Example", path: "/example" }); - expect(minimal.description).toBe("Software and Writing"); - expect(minimal.openGraph.description).toBe("Software and Writing"); - expect(minimal.twitter.description).toBe("Software and Writing"); - expect(minimal.openGraph.type).toBe("website"); - expect(Object.hasOwn(minimal, "robots")).toBe(false); - const { buildProfilePageJsonLd } = await import("../src/lib/structured-data"); - const profile = buildProfilePageJsonLd(); - const journey = buildPageMetadata(pages.journey); - expect(profile.description).toBe(journey.description); - expect(profile.name).toBe(journey.title.absolute); - expect(profile.url).toBe(journey.alternates.canonical); - const { buildBlogCollectionJsonLd } = - await import("../src/lib/structured-data"); - const collection = buildBlogCollectionJsonLd([]); - expect(collection.name).toBe(buildPageMetadata(pages.writing).title.absolute); - expect(collection.description).toBe("Software and Writing"); - expect(collection.url).toBe(publicUrl(pages.writing.path)); - const { resumeData } = await import("../src/content/resume"); - const { siteNavigation } = await import("../src/content/site"); - for (const [key, link] of Object.entries(siteNavigation)) { - expect(resumeData.navItems).toContainEqual({ - href: link.path, - label: link.title, + expect(metadata.robots).toEqual({ index: false, follow: false }); + for (const social of [metadata.openGraph, metadata.twitter]) { + expect(social).toMatchObject({ + title: `Example | ${siteConfig.name}`, + description: "An example page.", + images: [image], }); - const page = pages[key as keyof typeof siteNavigation]; - expect(page.path).toBe(link.path); - expect(page.title).not.toBe(link.title); } - expect(pages.home.title).toBe(siteConfig.name); - expect(pages.home.title).not.toBe(siteConfig.author.role); - expect(siteConfig.shareImage.alt).toContain(siteConfig.name); - const { default: sharp } = await import("sharp"); - const image = await sharp(`public${siteConfig.shareImage.url}`).metadata(); - expect(image.width).toBe(siteConfig.shareImage.width); - expect(image.height).toBe(siteConfig.shareImage.height); - expect(image.format).toBe("png"); - - const writing = buildPageMetadata(pages.writing); - expect(writing.alternates.types).toEqual({ - "application/rss+xml": "/rss.xml", - }); - const published = buildPageMetadata({ ...pages.tokens, robots: undefined }); - expect(Object.hasOwn(published, "robots")).toBe(false); - const hidden = buildPageMetadata({ - ...pages.tokens, - robots: { index: false, follow: false }, - }); - expect(hidden.robots).toEqual({ index: false, follow: false }); + expect(metadata.openGraph.type).toBe("profile"); + const home = buildPageMetadata({ title: "Home", path: "/" }); + expect(home.title).toEqual({ absolute: "Home" }); + expect(home.description).toBe(siteConfig.title); + expect(home.openGraph.images).toEqual([siteConfig.shareImage]); + expect(home.openGraph.type).toBe("website"); + expect(Object.hasOwn(home, "robots")).toBe(false); }); -test("blog metadata and JSON-LD share authored fields, URLs, images and dates", async () => { - const { buildBlogMetadata } = await import("../src/lib/page-metadata"); - const { buildBlogPostingJsonLd } = await import("../src/lib/structured-data"); - const { siteConfig } = await import("../src/lib/site"); +test("blog metadata and JSON-LD share authored fields, URLs, images and dates", () => { const post = { slug: "example", importPath: "example/page.mdx", @@ -229,28 +93,25 @@ test("blog metadata and JSON-LD share authored fields, URLs, images and dates", expect(metadata.description).toBe(post.metadata.summary); expect(schema.headline).toBe(post.metadata.title); expect(schema.description).toBe(metadata.description); - expect(schema.url).toBe(metadata.alternates.canonical); + expect(metadata.alternates.canonical).toBe(publicUrl("/writing/example")); + expect(schema.url).toBe(publicUrl("/writing/example")); expect(schema.url).toBe(metadata.openGraph.url); - expect(schema.image).toBe(metadata.openGraph.images[0].url); + expect(metadata.openGraph.images[0].url).toBe( + publicUrl("/writing/example/share-image") + ); + expect(schema.image).toBe(publicUrl("/writing/example/share-image")); expect(metadata.twitter.images).toEqual(metadata.openGraph.images); expect(metadata.twitter.images[0].alt).toBe(post.metadata.title); - expect(schema.datePublished).toBe(metadata.openGraph.publishedTime); + expect(metadata.openGraph.publishedTime).toBe("2026-09-01T00:00:00.000Z"); + expect(schema.datePublished).toBe("2026-09-01T00:00:00.000Z"); + expect(metadata.openGraph.modifiedTime).toBe(updatedAt?.toISOString()); expect(schema.dateModified).toBe( - metadata.openGraph.modifiedTime ?? metadata.openGraph.publishedTime + updatedAt ? "2026-09-02T00:00:00.000Z" : "2026-09-01T00:00:00.000Z" ); expect(schema.author).toMatchObject(metadata.authors[0]); expect(schema.keywords).toEqual(metadata.keywords); expect(metadata.alternates.types).toEqual({ "application/rss+xml": new URL("/rss.xml", siteConfig.url).href, }); - expect(JSON.stringify({ metadata, schema })).not.toMatch(/[—·]/); - } - for (const path of new Bun.Glob("src/content/blog/**/*.mdx").scanSync(".")) { - const source = readFileSync(path, "utf-8"); - const authoredMetadata = /export const metadata = \{[\s\S]*?^\};/m.exec( - source - )?.[0]; - expect(authoredMetadata, path).toBeDefined(); - expect(authoredMetadata, path).not.toMatch(/[—·]/); } }); diff --git a/tests/site-url.test.ts b/tests/site-url.test.ts index 3c01f1f0..6b893f74 100644 --- a/tests/site-url.test.ts +++ b/tests/site-url.test.ts @@ -30,43 +30,3 @@ test("derives local and production URLs without an author-specific fallback", () } expect(() => siteOriginFrom({ VERCEL: "1" })).toThrow(); }); - -test("browser deployment configuration keeps previews and local development out of analytics", () => { - for (const deployment of ["production", "preview", "development"]) { - const result = Bun.spawnSync( - [ - process.execPath, - "--eval", - ` - globalThis.window = { location: { origin: "http://localhost:4200" } }; - const { env } = await import("./src/env.ts"); - const { CANONICAL_SITE_URL } = await import("./src/lib/site-url.ts"); - const blocked = ["DATABASE_URL", "GITHUB_TOKENS", "VERCEL_PROJECT_PRODUCTION_URL"].every(key => { - try { env[key]; return false; } catch { return true; } - }); - console.log(JSON.stringify({ origin: CANONICAL_SITE_URL, blocked, production: env.NEXT_PUBLIC_VERCEL_ENV === "production", region: env.NEXT_PUBLIC_POSTHOG_REGION })); - `, - ], - { - env: - deployment === "development" - ? {} - : { - NEXT_PUBLIC_VERCEL_ENV: deployment, - NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL: "example.com", - NODE_ENV: "production", - }, - } - ); - expect(result.exitCode).toBe(0); - expect(JSON.parse(result.stdout.toString())).toEqual({ - origin: - deployment === "development" - ? "http://localhost:4200" - : "https://example.com", - production: deployment === "production", - region: "us", - blocked: true, - }); - } -}); diff --git a/tests/token-delegation.test.ts b/tests/token-delegation.test.ts index 93eee859..7f1376de 100644 --- a/tests/token-delegation.test.ts +++ b/tests/token-delegation.test.ts @@ -15,7 +15,10 @@ test("donut callouts stay separated for narrow charts and adjacent small slices" radius, height ); + expect(labels).toHaveLength(values.length); for (const label of labels) { + expect([-1, 1]).toContain(label.side); + expect(Number.isFinite(label.y)).toBe(true); expect(label.y).toBeGreaterThanOrEqual(36); expect(label.y).toBeLessThanOrEqual(height - 36); for (const other of labels) { diff --git a/tests/token-details.test.tsx b/tests/token-details.test.tsx index e487dc6f..8c176102 100644 --- a/tests/token-details.test.tsx +++ b/tests/token-details.test.tsx @@ -1,9 +1,11 @@ -import { expect, test } from "bun:test"; +import { expect, spyOn, test } from "bun:test"; +import assert from "node:assert/strict"; import { renderToStaticMarkup } from "react-dom/server"; import { TokenUsageDetails } from "../src/components/token-details"; import { tokenPreferences } from "../src/content/tokens"; +import * as database from "../src/db/client"; import { analyticsSchemas, buildTokenDetails, @@ -11,6 +13,10 @@ import { mergeAnalyticsSnapshots, } from "../src/lib/codex/analytics"; import type { AnalyticsSnapshot } from "../src/lib/codex/analytics"; +import { + getPublicCodexStats, + getPublicTokenDetails, +} from "../src/lib/codex/public-stats"; import { mockFetch } from "./helpers"; const now = new Date("2026-09-23T12:00:00Z"); @@ -57,6 +63,29 @@ const fixture = () => ({ skills: { ...meta, response: { data: [] } }, }); +test("disabled public tokens return no data before consulting the database", async () => { + const { enabled } = tokenPreferences; + const configured = spyOn(database, "isDatabaseConfigured").mockReturnValue( + true + ); + const read = spyOn(database, "getDatabase").mockImplementation(() => { + throw new Error("must not read private data"); + }); + try { + tokenPreferences.enabled = false; + expect(await getPublicCodexStats()).toBeNull(); + for (const days of [7, 30, 365]) { + expect(await getPublicTokenDetails(days)).toBeNull(); + } + expect(configured).not.toHaveBeenCalled(); + expect(read).not.toHaveBeenCalled(); + } finally { + tokenPreferences.enabled = enabled; + configured.mockRestore(); + read.mockRestore(); + } +}); + test("combines counts and weighted cache rate while preserving privacy", () => { const first: AnalyticsSnapshot = fixture(); const second = fixture(); @@ -74,24 +103,6 @@ test("combines counts and weighted cache rate while preserving privacy", () => { expect(JSON.stringify(result)).not.toContain("private-tool"); expect(JSON.stringify(first.activity)).not.toContain("credits"); expect(JSON.stringify(first.activity)).not.toContain("secret"); - const html = renderToStaticMarkup( - - ); - expect(html).toContain('id="models-title"'); - expect(html).not.toContain("Account 2"); - expect(html).not.toContain("]*aria-selected="true"[^>]*>Last 30 days<\/button>/ - ); - expect(html).not.toContain("/tokens?days="); - expect(html).not.toContain("private-tool"); }); test("missing, zero, retained, and disabled sections stay distinct", () => { @@ -128,9 +139,6 @@ test("missing, zero, retained, and disabled sections stay distinct", () => { expect(hidden.plugins).toBeNull(); expect(hidden.models).toBeNull(); expect(JSON.stringify(hidden)).not.toContain("private-tool"); - expect( - renderToStaticMarkup() - ).not.toContain('id="breakdowns"'); expect( analyticsSchemas.activity.safeParse({ data: [{ date: "not-a-date" }] }) .success @@ -223,36 +231,6 @@ test("optional source failures retain original timestamps while successful sourc expect(disabled).toEqual({}); }); -test("disabled configuration hides the route and public data without querying a database", () => { - const result = Bun.spawnSync( - [ - process.execPath, - "--eval", - ` - import assert from "node:assert/strict"; - import { mock } from "bun:test"; - import { tokenPreferences } from "./src/content/tokens.ts"; - mock.module("server-only", () => ({})); - mock.module("./src/env.ts", () => ({ env: {} })); - mock.module("./src/components/site-shell.tsx", () => ({ SiteShell: () => null })); - mock.module("./src/content/tokens.ts", () => ({ tokenPreferences: { ...tokenPreferences, enabled: false } })); - mock.module("./src/db/client.ts", () => ({ - isDatabaseConfigured: () => { throw new Error("must not query"); }, - getDatabase: () => { throw new Error("must not query"); }, - })); - const { getPublicCodexStats, getPublicTokenDetails } = await import("./src/lib/codex/public-stats.ts"); - assert.equal(await getPublicCodexStats(), null); - assert.equal(await getPublicTokenDetails(7), null); - const { default: TokensPage } = await import("./src/app/(portfolio)/tokens/page.tsx"); - await assert.rejects(TokensPage({ searchParams: Promise.resolve({}) }), /NEXT_HTTP_ERROR_FALLBACK;404/); - `, - ], - { cwd: new URL("..", import.meta.url).pathname } - ); - expect(result.stderr.toString()).toBe(""); - expect(result.exitCode).toBe(0); -}); - test("ranked tools carry dynamic logos without exposing excluded tool metadata", () => { const account: AnalyticsSnapshot = { ...fixture(), @@ -581,7 +559,7 @@ test("backfill preserves old days and missing fields, replaces corrections witho expect(result.activity?.response.data).toEqual( previous.activity.response.data ); - expect( + await assert.rejects( fetchAnalytics( {}, mockFetch(async () => new Response(null, { status: 503 })), @@ -589,6 +567,7 @@ test("backfill preserves old days and missing fields, replaces corrections witho previous, tokenPreferences, { start: "2020-01-01", end: "2020-12-30" } - ) - ).rejects.toThrow("backfill failed"); + ), + /backfill failed/u + ); });