import { countOwnRepos, GITHUB_STATS_REVALIDATE_SECONDS, loadGithubRepoCounts, loadGithubStatsSnapshot, } from "./github-stats"; const repo = ( overrides: Partial<{ private: boolean; fork: boolean; archived: boolean }>, ) => ({ private: false, fork: false, archived: false, ...overrides }); function jsonResponse(body: unknown, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => body, } as unknown as Response; } const noSnapshot = async () => null; const snapshot = { total: 68, public: 25, private: 43 }; describe("countOwnRepos", () => { it("counts public and private repos and ignores forks and archived ones", () => { const counts = countOwnRepos([ repo({}), repo({}), repo({ private: true }), repo({ fork: true }), repo({ private: true, fork: true }), repo({ archived: true }), repo({ private: true, archived: true }), ]); expect(counts).toEqual({ total: 3, public: 2, private: 1 }); }); }); describe("loadGithubRepoCounts", () => { it("returns null without a token and without a snapshot, and never calls the API", async () => { const fetchImpl = jest.fn(); await expect( loadGithubRepoCounts(undefined, fetchImpl, noSnapshot), ).resolves.toBeNull(); expect(fetchImpl).not.toHaveBeenCalled(); }); it("falls back to the last known snapshot without a token", async () => { const fetchImpl = jest.fn(); await expect( loadGithubRepoCounts(undefined, fetchImpl, async () => snapshot), ).resolves.toEqual(snapshot); expect(fetchImpl).not.toHaveBeenCalled(); }); it("prefers the live count over the snapshot", async () => { const fetchImpl = jest .fn() .mockResolvedValue(jsonResponse([repo({}), repo({ private: true })])); await expect( loadGithubRepoCounts("token", fetchImpl, async () => snapshot), ).resolves.toEqual({ total: 2, public: 1, private: 1 }); }); it("sends the token only to the GitHub API and revalidates daily", async () => { const fetchImpl = jest.fn().mockResolvedValue(jsonResponse([repo({})])); await loadGithubRepoCounts("secret-token", fetchImpl, noSnapshot); const [url, init] = fetchImpl.mock.calls[0]; expect(new URL(url).origin).toBe("https://api.github.com"); expect(url).toContain("affiliation=owner"); expect(url).toContain("visibility=all"); expect(init.headers.authorization).toBe("Bearer secret-token"); expect(init.next.revalidate).toBe(GITHUB_STATS_REVALIDATE_SECONDS); expect(GITHUB_STATS_REVALIDATE_SECONDS).toBe(86_400); }); it("walks every page before counting", async () => { const firstPage = Array.from({ length: 100 }, () => repo({})); const secondPage = [repo({ private: true }), repo({ fork: true })]; const fetchImpl = jest .fn() .mockResolvedValueOnce(jsonResponse(firstPage)) .mockResolvedValueOnce(jsonResponse(secondPage)); await expect( loadGithubRepoCounts("token", fetchImpl, noSnapshot), ).resolves.toEqual({ total: 101, public: 100, private: 1, }); expect(fetchImpl).toHaveBeenCalledTimes(2); }); it("uses the snapshot when the API rejects the token", async () => { const fetchImpl = jest .fn() .mockResolvedValue(jsonResponse({ message: "Bad credentials" }, 401)); await expect( loadGithubRepoCounts("bad", fetchImpl, async () => snapshot), ).resolves.toEqual(snapshot); }); it("uses the snapshot on network failure instead of breaking the page", async () => { const fetchImpl = jest.fn().mockRejectedValue(new Error("offline")); await expect( loadGithubRepoCounts("token", fetchImpl, async () => snapshot), ).resolves.toEqual(snapshot); }); }); describe("loadGithubStatsSnapshot", () => { it("reads a coherent snapshot from data/github-stats.json", async () => { const counts = await loadGithubStatsSnapshot(); expect(counts).not.toBeNull(); expect(counts?.total).toBe((counts?.public ?? 0) + (counts?.private ?? 0)); }); });