Files
worldmonitor/tests/insights-loader.test.mjs
Elie Habib 80b8071356 feat: server-side AI insights via Railway cron + bootstrap hydration (#1003)
Move the heavy AI insights pipeline (clustering, scoring, LLM brief)
from client-side (15-40s per user) to a 5-min Railway cron job. The
frontend reads pre-computed insights instantly via bootstrap hydration,
with graceful fallback to the existing client-side pipeline.

- Add _clustering.mjs: Jaccard clustering + importance scoring (pure JS)
- Add seed-insights.mjs: Railway cron reads digest, clusters, calls
  Groq/OpenRouter for brief, writes to Redis with LKG preservation
- Register insights key in bootstrap.js FAST_KEYS tier
- Add insights-loader.ts: module-level cached bootstrap reader
- Modify InsightsPanel.ts: server-first path (2-step progress) with
  client fallback (4-step, unchanged behavior)
- Add unit tests for clustering (12) and insights-loader (7)
2026-03-04 20:42:51 +04:00

66 lines
2.0 KiB
JavaScript

import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
describe('insights-loader', () => {
describe('getServerInsights (logic validation)', () => {
const MAX_AGE_MS = 15 * 60 * 1000;
function isFresh(generatedAt) {
const age = Date.now() - new Date(generatedAt).getTime();
return age < MAX_AGE_MS;
}
it('rejects data older than 15 minutes', () => {
const old = new Date(Date.now() - 16 * 60 * 1000).toISOString();
assert.equal(isFresh(old), false);
});
it('accepts data younger than 15 minutes', () => {
const fresh = new Date(Date.now() - 5 * 60 * 1000).toISOString();
assert.equal(isFresh(fresh), true);
});
it('accepts data from now', () => {
assert.equal(isFresh(new Date().toISOString()), true);
});
it('rejects exactly 15 minutes old data', () => {
const exact = new Date(Date.now() - MAX_AGE_MS).toISOString();
assert.equal(isFresh(exact), false);
});
});
describe('ServerInsights payload shape', () => {
it('validates required fields', () => {
const valid = {
worldBrief: 'Test brief',
briefProvider: 'groq',
status: 'ok',
topStories: [{ primaryTitle: 'Test', sourceCount: 2 }],
generatedAt: new Date().toISOString(),
clusterCount: 10,
multiSourceCount: 5,
fastMovingCount: 3,
};
assert.ok(valid.topStories.length >= 1);
assert.ok(['ok', 'degraded'].includes(valid.status));
});
it('allows degraded status with empty brief', () => {
const degraded = {
worldBrief: '',
status: 'degraded',
topStories: [{ primaryTitle: 'Test' }],
generatedAt: new Date().toISOString(),
};
assert.equal(degraded.worldBrief, '');
assert.equal(degraded.status, 'degraded');
});
it('rejects empty topStories', () => {
const empty = { topStories: [] };
assert.equal(empty.topStories.length >= 1, false);
});
});
});