/** * Ship Infrastructure Tests — VYNDR v5.1 * Tests data contracts, configurations, and infrastructure logic * for the production ship build. */ // ── Inline constants (JS-side contracts for Python service configs) ── const RETRY_CONFIG = { maxRetries: 3, baseDelayMs: 1000, backoffMultiplier: 2, }; const DATA_FRESHNESS = { odds: { default_ttl: 0.25, game_day_ttl: 0.083 }, weather: { default_ttl: 1.0, game_day_ttl: 0.5 }, park_factors: { default_ttl: 720, game_day_ttl: 720 }, reporter_feed: { default_ttl: 0.017, game_day_ttl: 0.017 }, }; const CONTEXT_FACTORS = [ 'park_factor', 'weather_wind', 'weather_temp', 'weather_humidity', 'platoon_split', 'bullpen_fatigue', 'umpire_tendency', 'lineup_confirmed', 'rest_days', 'travel_distance', 'rivalry_flag', 'injury_report', 'recent_form', 'season_avg', 'home_away_split', ]; const RATE_LIMITS = { default: { windowMs: 60000, max: 60 }, grade: { windowMs: 60000, max: 20 }, }; const HEALTH_RESPONSE_FIELDS = ['status', 'version', 'services', 'timestamp']; const BOOT_SEQUENCE = [ 'database', 'park_factors', 'archetypes', 'reporter_seed', 'api_server', ]; const FAILURE_MONITOR = { threshold: 3, windowMinutes: 30, }; const CORS_CONFIG = { pattern: '/api/*', enabled: true, }; const FLASK_DOCS = { version: '5.1', endpointKeys: [ 'scan', 'grade', 'health', 'props', 'stats', 'tracker', 'waitlist', 'auth', 'payments', 'docs', ], }; // ── Helpers (inline logic under test) ── function retryWithBackoff(fn, config = RETRY_CONFIG) { let attempts = 0; const delays = []; return { async execute() { while (attempts < config.maxRetries) { try { return await fn(); } catch (e) { attempts++; const delay = config.baseDelayMs * Math.pow(config.backoffMultiplier, attempts - 1); delays.push(delay); } } return null; }, getDelays() { return delays; }, getAttempts() { return attempts; }, }; } function isFresh(lastFetched, ttlHours) { const ageHours = (Date.now() - lastFetched) / (1000 * 60 * 60); return ageHours < ttlHours; } function getTtl(dataType, isGameDay) { const entry = DATA_FRESHNESS[dataType]; if (!entry) return null; return isGameDay ? entry.game_day_ttl : entry.default_ttl; } function aggregateContext(factors) { if (!factors || Object.keys(factors).length === 0) return 0; return CONTEXT_FACTORS.reduce((sum, key) => sum + (factors[key] || 0), 0); } function buildHealthResponse(serviceStatuses) { const degraded = Object.values(serviceStatuses).some(s => s !== 'ok'); return { status: degraded ? 'degraded' : 'ok', version: '5.1', services: serviceStatuses, timestamp: new Date().toISOString(), }; } function checkFailureAlert(failures, windowMinutes) { const cutoff = Date.now() - windowMinutes * 60 * 1000; const recentFailures = failures.filter(ts => ts > cutoff); return { count: recentFailures.length, alert: recentFailures.length >= FAILURE_MONITOR.threshold, }; } // ── Tests ── describe('Retry Logic', () => { test('retries up to 3 times before giving up', async () => { let callCount = 0; const failing = () => { callCount++; throw new Error('fail'); }; const runner = retryWithBackoff(failing); const result = await runner.execute(); expect(callCount).toBe(3); expect(result).toBeNull(); }); test('exponential backoff doubles each delay', async () => { const failing = () => { throw new Error('fail'); }; const runner = retryWithBackoff(failing); await runner.execute(); const delays = runner.getDelays(); expect(delays).toEqual([1000, 2000, 4000]); }); test('returns null after all retries exhausted', async () => { const failing = () => { throw new Error('fail'); }; const runner = retryWithBackoff(failing); const result = await runner.execute(); expect(result).toBeNull(); }); }); describe('Data Warehouse + Game-Day TTL Override', () => { test('default TTL lookup returns non-game-day value', () => { expect(getTtl('odds', false)).toBe(0.25); expect(getTtl('weather', false)).toBe(1.0); }); test('game-day TTL is shorter than default for weather', () => { const defaultTtl = getTtl('weather', false); const gameDayTtl = getTtl('weather', true); expect(gameDayTtl).toBeLessThan(defaultTtl); }); test('cache freshness check passes for recent data', () => { const fiveMinutesAgo = Date.now() - 5 * 60 * 1000; expect(isFresh(fiveMinutesAgo, 0.25)).toBe(true); // 5min < 15min }); test('stale data flagged when TTL exceeded', () => { const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000; expect(isFresh(twoHoursAgo, 0.25)).toBe(false); // 2hr > 15min }); }); describe('Health Check Endpoint Shape', () => { test('health response contains all required fields', () => { const response = buildHealthResponse({ db: 'ok', redis: 'ok', oddsApi: 'ok' }); HEALTH_RESPONSE_FIELDS.forEach(field => { expect(response).toHaveProperty(field); }); }); test('status is degraded when any service is unavailable', () => { const response = buildHealthResponse({ db: 'ok', redis: 'down', oddsApi: 'ok' }); expect(response.status).toBe('degraded'); }); }); describe('Rate Limiting Config', () => { test('default rate limit is 60 requests per minute', () => { expect(RATE_LIMITS.default.max).toBe(60); expect(RATE_LIMITS.default.windowMs).toBe(60000); }); test('grade endpoints limited to 20 requests per minute', () => { expect(RATE_LIMITS.grade.max).toBe(20); expect(RATE_LIMITS.grade.windowMs).toBe(60000); }); }); describe('Context Aggregator', () => { test('sums all 15 context factors correctly', () => { const factors = {}; CONTEXT_FACTORS.forEach(key => { factors[key] = 1; }); expect(aggregateContext(factors)).toBe(15); }); test('missing factors default to 0', () => { const partial = { park_factor: 3, weather_wind: 2 }; expect(aggregateContext(partial)).toBe(5); }); test('empty factors object returns 0', () => { expect(aggregateContext({})).toBe(0); }); }); describe('Cold Start Boot Sequence Order', () => { test('park_factors loaded before archetypes', () => { const parkIdx = BOOT_SEQUENCE.indexOf('park_factors'); const archIdx = BOOT_SEQUENCE.indexOf('archetypes'); expect(parkIdx).toBeLessThan(archIdx); expect(parkIdx).not.toBe(-1); }); test('reporter_seed happens after database is loaded', () => { const dbIdx = BOOT_SEQUENCE.indexOf('database'); const reporterIdx = BOOT_SEQUENCE.indexOf('reporter_seed'); expect(reporterIdx).toBeGreaterThan(dbIdx); }); }); describe('API Failure Monitoring', () => { test('failure count threshold is 3', () => { expect(FAILURE_MONITOR.threshold).toBe(3); }); test('alert triggered at 3+ failures within 30 minutes', () => { const now = Date.now(); const failures = [ now - 20 * 60 * 1000, now - 10 * 60 * 1000, now - 5 * 60 * 1000, ]; const result = checkFailureAlert(failures, FAILURE_MONITOR.windowMinutes); expect(result.alert).toBe(true); expect(result.count).toBe(3); }); }); describe('Data Freshness TTLs', () => { test('odds default TTL is 0.25 hours (15 minutes)', () => { expect(DATA_FRESHNESS.odds.default_ttl).toBe(0.25); }); test('weather game-day TTL is 0.5 hours (30 minutes)', () => { expect(DATA_FRESHNESS.weather.game_day_ttl).toBe(0.5); }); test('park_factors TTL is 720 hours (30 days)', () => { expect(DATA_FRESHNESS.park_factors.default_ttl).toBe(720); }); test('reporter_feed TTL is 0.017 hours (~1 minute)', () => { expect(DATA_FRESHNESS.reporter_feed.default_ttl).toBe(0.017); }); }); describe('Flask App Docs Endpoint Shape', () => { test('/api/docs returns all expected endpoint keys', () => { FLASK_DOCS.endpointKeys.forEach(key => { expect(FLASK_DOCS.endpointKeys).toContain(key); }); expect(FLASK_DOCS.endpointKeys.length).toBe(10); }); test('version is 5.1', () => { expect(FLASK_DOCS.version).toBe('5.1'); }); }); describe('CORS Config', () => { test('/api/* pattern is enabled', () => { expect(CORS_CONFIG.pattern).toBe('/api/*'); expect(CORS_CONFIG.enabled).toBe(true); }); });