import { test } from 'node:test'; import assert from 'node:assert/strict'; import { parseTarget, computeEstimate, pruneHistory, summary, MS_PER_DAY } from '../src/pure.js'; const DAY = MS_PER_DAY; const NOW = 1_700_000_000_000; // fixed reference test('parseTarget: integer numbers', () => { assert.equal(parseTarget(25), 25); assert.equal(parseTarget(25000000), 25_000_000); assert.equal(parseTarget(2.5e7), 25_000_000); }); test('parseTarget: string numbers with commas', () => { assert.equal(parseTarget('25'), 25); assert.equal(parseTarget('25,000,000'), 25_000_000); assert.equal(parseTarget('25,000,000.5'), 25_000_000); }); test('parseTarget: magnitude suffixes (case-insensitive)', () => { assert.equal(parseTarget('25K'), 25_000); assert.equal(parseTarget('25m'), 25_000_000); assert.equal(parseTarget('1.5B'), 1_500_000_000); assert.equal(parseTarget('100t'), 100_000_000_000_000); }); test('parseTarget: rejects invalid input', () => { assert.equal(parseTarget(0), null); assert.equal(parseTarget(-1), null); assert.equal(parseTarget('abc'), null); assert.equal(parseTarget(''), null); assert.equal(parseTarget(null), null); assert.equal(parseTarget(undefined), null); assert.equal(parseTarget('25M.5'), null); assert.equal(parseTarget('1.5.5M'), null); assert.equal(parseTarget('0'), null); }); test('computeEstimate: typical case', () => { const r = computeEstimate(14_328_501, 25_000_000, 247, 4520); assert.equal(r.remaining, 10_671_499); assert.equal(r.trainsToGo, 43_205); assert.equal(r.days, 2_361); assert.ok(r.eta instanceof Date); }); test('computeEstimate: target reached', () => { const r = computeEstimate(25_000_000, 25_000_000, 247, 4520); assert.equal(r.remaining, 0); assert.equal(r.trainsToGo, 0); assert.equal(r.days, 0); assert.equal(r.eta, null); }); test('computeEstimate: target below current', () => { const r = computeEstimate(30_000_000, 25_000_000, 247, 4520); assert.equal(r.remaining, 0); assert.equal(r.trainsToGo, 0); assert.equal(r.days, 0); assert.equal(r.eta, null); }); test('computeEstimate: zero perTrain or perDay does not crash', () => { const a = computeEstimate(100, 200, 0, 50); assert.equal(a.trainsToGo, 0); assert.equal(a.days, 2); const b = computeEstimate(100, 200, 50, 0); assert.equal(b.trainsToGo, 2); assert.equal(b.days, 0); assert.equal(b.eta, null); }); test('pruneHistory: keeps entries within 30 days, drops older', () => { const entries = [ { ts: NOW, delta: 100 }, { ts: NOW - 1 * DAY, delta: 100 }, { ts: NOW - 29 * DAY, delta: 100 }, { ts: NOW - 30 * DAY, delta: 100 }, // exactly 30 days, dropped (strict <) { ts: NOW - 31 * DAY, delta: 100 }, ]; const out = pruneHistory(entries, NOW); assert.equal(out.length, 3); assert.deepEqual(out.map((e) => e.ts), [NOW, NOW - 1 * DAY, NOW - 29 * DAY]); }); test('pruneHistory: does not mutate input', () => { const entries = [{ ts: NOW - 31 * DAY, delta: 100 }]; const copy = [...entries]; pruneHistory(entries, NOW); assert.deepEqual(entries, copy); }); test('pruneHistory: empty input', () => { assert.deepEqual(pruneHistory([], NOW), []); }); test('summary: empty history returns zeros', () => { const s = summary([], NOW); assert.equal(s.trainsToday, 0); assert.equal(s.sevenDayAvgPerDay, 0); assert.equal(s.perDay, 0); }); test('summary: counts trains within last 24h', () => { const entries = [ { ts: NOW - 1 * 60_000, delta: 100 }, // 1 min ago { ts: NOW - 12 * 3_600_000, delta: 100 }, // 12h ago { ts: NOW - 23 * 3_600_000, delta: 100 }, // 23h ago { ts: NOW - 25 * 3_600_000, delta: 100 }, // 25h ago (not today) ]; const s = summary(entries, NOW); assert.equal(s.trainsToday, 3); }); test('summary: 7-day average is total/7', () => { // 14 entries in last 7 days (spaced 12h apart, all within window) const entries = []; for (let i = 0; i < 14; i++) { entries.push({ ts: NOW - (i / 2) * DAY, delta: 100 }); } const s = summary(entries, NOW); assert.equal(s.sevenDayAvgPerDay, 2); }); test('summary: perDay uses most recent delta when available', () => { const entries = [ { ts: NOW - 1 * 60_000, delta: 247 }, { ts: NOW - 12 * 3_600_000, delta: 300 }, ]; const s = summary(entries, NOW); // 2 trains in last 7 days → 2/7 avg // perDay = avg * most recent delta = (2/7) * 247 = ~70.57 assert.equal(s.perDay, Math.floor((2 / 7) * 247)); }); test('summary: perDay is 0 when no recent delta', () => { const entries = [{ ts: NOW - 31 * DAY, delta: 100 }]; const s = summary(entries, NOW); assert.equal(s.perDay, 0); });