feat(github): usar o ultimo retrato da contagem quando nao ha token ou a API falha

data/github-stats.json guarda 68 repositorios proprios (25 publicos e 43
privados, sem forks) conferidos em 2026-10-08. Com token valido a contagem
ao vivo vale mais; o retrato evita que o hero fique sem o numero.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Evandro Fonseca JuniorandClaude Sonnet 5.5 committed 2026-10-08 15:26:57 -03:00
1 parent 942f6bb45c
commit c0493f1e77
3 files changed
+82 -11

No files matched your search

+46 -8
View File
@@ -2,6 +2,7 @@ import {
countOwnRepos,
GITHUB_STATS_REVALIDATE_SECONDS,
loadGithubRepoCounts,
loadGithubStatsSnapshot,
} from "./github-stats";
const repo = (
@@ -16,6 +17,9 @@ function jsonResponse(body: unknown, status = 200) {
} 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([
@@ -33,19 +37,38 @@ describe("countOwnRepos", () => {
});
describe("loadGithubRepoCounts", () => {
it("returns null without a token and never calls the API", async () => {
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),
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);
await loadGithubRepoCounts("secret-token", fetchImpl, noSnapshot);
const [url, init] = fetchImpl.mock.calls[0];
expect(new URL(url).origin).toBe("https://api.github.com");
@@ -64,7 +87,9 @@ describe("loadGithubRepoCounts", () => {
.mockResolvedValueOnce(jsonResponse(firstPage))
.mockResolvedValueOnce(jsonResponse(secondPage));
await expect(loadGithubRepoCounts("token", fetchImpl)).resolves.toEqual({
await expect(
loadGithubRepoCounts("token", fetchImpl, noSnapshot),
).resolves.toEqual({
total: 101,
public: 100,
private: 1,
@@ -72,17 +97,30 @@ describe("loadGithubRepoCounts", () => {
expect(fetchImpl).toHaveBeenCalledTimes(2);
});
it("returns null when the API rejects the token", async () => {
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)).resolves.toBeNull();
await expect(
loadGithubRepoCounts("bad", fetchImpl, async () => snapshot),
).resolves.toEqual(snapshot);
});
it("returns null on network failure instead of breaking the page", async () => {
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)).resolves.toBeNull();
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));
});
});