Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions scripts/fetch-hive-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,10 @@ async function fetchContributorWeeklyStats(repos = FALLBACK_FACTORY_REPOS) {
return finalizeContributorStats(acc);
}

function loadHistory() {
function loadHistory(file = OUTPUT_FILE) {
try {
if (fs.existsSync(OUTPUT_FILE)) {
return JSON.parse(fs.readFileSync(OUTPUT_FILE, "utf8"));
if (fs.existsSync(file)) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
} catch {
// ignore corrupt file — start fresh
Expand Down Expand Up @@ -571,8 +571,10 @@ module.exports = {
createStatsAccumulator,
extractMetrics,
finalizeContributorStats,
loadHistory,
MAX_WEEKLY_SERIES,
MAX_WEEKS,
registryHeaders,
safeNum,
trackedProjectRepos,
};
78 changes: 78 additions & 0 deletions scripts/fetch-hive-history.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { tmpdir } = require("node:os");

const {
accumulateRepoStats,
computeStatsWindows,
createStatsAccumulator,
extractMetrics,
finalizeContributorStats,
loadHistory,
MAX_WEEKS,
registryHeaders,
safeNum,
trackedProjectRepos,
} = require("./fetch-hive-history.js");

Expand Down Expand Up @@ -434,3 +439,76 @@ test("extractMetrics survives wrong-typed containers without throwing", () => {
assert.equal(metrics.mergedToday, undefined);
assert.equal(metrics.medianMergeMins, undefined);
});

// safeNum is the coercion gate behind every numeric field: it turns anything
// that is not a finite number into undefined so a chart plots a gap, not a
// garbage value. loadHistory is the read path for the tracked seed file — a
// regression here either silently drops history or crashes the run.

test("safeNum keeps only finite numbers", () => {
assert.equal(safeNum(42), 42);
assert.equal(safeNum(0), 0);
assert.equal(safeNum(3.14), 3.14);
});

test("safeNum rejects non-numbers, NaN and +/- Infinity", () => {
for (const v of [
NaN,
Infinity,
-Infinity,
"12",
null,
undefined,
{},
[],
true,
{ a: 1 },
]) {
assert.equal(safeNum(v), undefined, `expected undefined for ${String(v)}`);
}
});

test("loadHistory returns the seeded default when no file exists", () => {
const missing = path.join(tmpdir(), `hive-missing-${process.pid}.json`);
assert.equal(fs.existsSync(missing), false);

const history = loadHistory(missing);

assert.deepEqual(history, {
entries: [],
contributors: {},
contributorsByRepo: {},
contributorStats: {},
contributorWeekStarts: [],
lastContributorFetch: null,
lastWeeklyStatsFetch: null,
});
});

test("loadHistory parses a valid history file", () => {
const file = path.join(tmpdir(), `hive-valid-${process.pid}.json`);
const seed = {
entries: [{ t: 1, acmmLevel: 3 }],
contributors: { a: 5 },
contributorWeekStarts: [1735689600],
};
fs.writeFileSync(file, JSON.stringify(seed), "utf8");
try {
assert.deepEqual(loadHistory(file), seed);
} finally {
fs.rmSync(file);
}
});

test("loadHistory starts fresh on a corrupt file instead of throwing", () => {
const file = path.join(tmpdir(), `hive-corrupt-${process.pid}.json`);
fs.writeFileSync(file, "{ not: valid json,,, ", "utf8");
try {
const history = loadHistory(file);
assert.deepEqual(history.entries, []);
assert.deepEqual(history.contributors, {});
assert.deepEqual(history.contributorWeekStarts, []);
} finally {
fs.rmSync(file);
}
});
Loading