Files
vyndr/tests/integration/snapshotSummary.test.js
T
builtbykev 7712f0a442 Heartbeat honesty: SYNC badge reads refreshed_at, not grade-lock updated_at
The "SIGNAL LIVE vs STALE 8h" contradiction was a field mismatch, not a dead
pipeline. updated_at is the grade-LOCK time (advances only on a full snapshot,
5×/day — grades never change in-game, so it is intentionally stable). The
SYNC badge measured the 20-min intraday cadence (expected_interval_s=1200)
against that 5×/day field → structurally guaranteed STALE between slots even
when intraday refreshes lines perfectly.

- snapshotService: full snapshot now seeds refreshed_at at lock time
- intradayRefresh already bumps refreshed_at every ~20 min (unchanged)
- /api/snapshot/summary + GET /:sport now expose refreshed_at (was written to
  Redis but never serialized → no public liveness signal existed)
- LiveLayer SYNC badge measures freshness from refreshed_at (fallback updated_at)

Exposing refreshed_at also gives a public heartbeat probe: it advances every
intraday slot, so pipeline liveness is verifiable without container logs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:09:18 -04:00

91 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Session 57 (Phase 0) — GET /api/snapshot/summary: the HeartbeatBar's honest
// data source. Real graded counts + latest pipeline run time from cache-only
// Redis reads; must never invent numbers on a cold cache.
const express = require('express');
const request = require('supertest');
const store = {};
jest.mock('../../src/utils/redis', () => ({
cacheGet: jest.fn(async (k) => (k in store ? store[k] : null)),
cacheSet: jest.fn(async (k, v) => { store[k] = v; return true; }),
}));
function mountApp() {
delete require.cache[require.resolve('../../src/routes/snapshot')];
const snapshotRoutes = require('../../src/routes/snapshot');
const app = express();
app.use('/api/snapshot', snapshotRoutes);
return app;
}
beforeEach(() => {
for (const k of Object.keys(store)) delete store[k];
jest.clearAllMocks();
});
describe('GET /api/snapshot/summary', () => {
test('sums real graded counts across sports and reports the latest run', async () => {
store['snapshot:mlb:latest'] = {
updated_at: '2026-07-10T18:00:00.000Z',
grades: [{ player: 'A' }, { player: 'B' }, { player: 'C' }],
};
store['snapshot:wnba:latest'] = {
updated_at: '2026-07-10T19:00:00.000Z',
grades: [{ player: 'D' }],
};
const res = await request(mountApp()).get('/api/snapshot/summary');
expect(res.status).toBe(200);
expect(res.body.graded).toBe(4);
expect(res.body.updated_at).toBe('2026-07-10T19:00:00.000Z'); // latest run
expect(res.body.sports.mlb).toBe(3);
expect(res.body.sports.wnba).toBe(1);
expect(res.body.sports.nba).toBe(0); // off-season → honest zero
});
test('refreshed_at is the freshness heartbeat, distinct from the grade-lock updated_at', async () => {
// updated_at = grade LOCK (5×/day, stable). refreshed_at = last intraday
// line refresh (~20 min). The SYNC badge measures against refreshed_at —
// measuring the 20-min cadence against the lock field caused "STALE 8h".
store['snapshot:mlb:latest'] = {
updated_at: '2026-07-10T18:00:00.000Z', // graded at 2pm ET
refreshed_at: '2026-07-10T21:40:00.000Z', // lines refreshed at 5:40pm ET
grades: [{ player: 'A' }],
};
const res = await request(mountApp()).get('/api/snapshot/summary');
expect(res.body.updated_at).toBe('2026-07-10T18:00:00.000Z');
expect(res.body.refreshed_at).toBe('2026-07-10T21:40:00.000Z');
});
test('refreshed_at falls back to updated_at when a snapshot predates the field', async () => {
// Old snapshots written before this fix have no refreshed_at — the freshness
// signal must still resolve (never null while a grade lock exists).
store['snapshot:mlb:latest'] = { updated_at: '2026-07-10T18:00:00.000Z', grades: [{ player: 'A' }] };
const res = await request(mountApp()).get('/api/snapshot/summary');
expect(res.body.refreshed_at).toBe('2026-07-10T18:00:00.000Z');
});
test('falls back to the grades:{sport} envelope when no snapshot exists', async () => {
store['grades:mlb'] = { updated_at: '2026-07-10T16:00:00.000Z', grades: [{ player: 'A' }, { player: 'B' }] };
const res = await request(mountApp()).get('/api/snapshot/summary');
expect(res.body.graded).toBe(2);
expect(res.body.updated_at).toBe('2026-07-10T16:00:00.000Z');
});
test('cold cache → zeros and null timestamp, never invented numbers', async () => {
const res = await request(mountApp()).get('/api/snapshot/summary');
expect(res.status).toBe(200);
expect(res.body.graded).toBe(0);
expect(res.body.updated_at).toBeNull();
});
test('is not captured by the /:sport route (summary ≠ a sport)', async () => {
store['snapshot:mlb:latest'] = { updated_at: 'x', grades: [{ player: 'A' }] };
const res = await request(mountApp()).get('/api/snapshot/summary');
// The /:sport handler would return { sport, grades, deltas }.
expect(res.body.sport).toBeUndefined();
expect(res.body.grades).toBeUndefined();
expect(typeof res.body.graded).toBe('number');
});
});