// @vitest-environment node import { test, expect, vi } from 'vitest'; import { resolveIqPath } from './iqbible.js'; test('only listed IQ Bible routes are reachable', () => { expect(resolveIqPath('commentaries/gill/JHN/3/16')?.key).toBe('/commentaries/gill/JHN/3/16'); expect(resolveIqPath('/geo/JHN/3')?.cache).toBe(true); expect(resolveIqPath('/me')).toBeNull(); expect(resolveIqPath('/licensing')).toBeNull(); expect(resolveIqPath('/commentaries/gill/../me')).toBeNull(); expect(resolveIqPath('/lexicon/greek/strongs/G26')).not.toBeNull(); }); test('lexicon sources and audio routes', () => { expect(resolveIqPath('/lexicon/greek/lsj/G0026')).not.toBeNull(); expect(resolveIqPath('/lexicon/hebrew/bdb/BDB1005')).not.toBeNull(); expect(resolveIqPath('/audio/eng_kjv/JHN/3')?.cache).toBe(false); expect(resolveIqPath('/ai/context', { ref: 'JHN.3.16' })?.heavy).toBe(true); }); test('the browser cannot send its own key and the query is normalised', () => { const r = resolveIqPath('/bibles/eng_kjv/reading-plan', { days: '30', apikey: 'x', 'bad key': '1', format: 'ical' }); expect(r.key).toBe('/bibles/eng_kjv/reading-plan?days=30&format=ical'); }); test('only verse and chapter text counts as cacheable text', async () => { const { isTextPath } = await import('./iqbible.js'); expect(isTextPath('/bibles/eng_kjv/JHN/3')).toBe(true); expect(isTextPath('/bibles/eng_kjv/JHN/3/16')).toBe(true); expect(isTextPath('/bibles/eng_kjv/random/verse')).toBe(false); expect(isTextPath('/bibles/eng_kjv/verse-of-the-day')).toBe(false); expect(isTextPath('/commentaries/gill/JHN/3')).toBe(false); expect(resolveIqPath('/bibles/parallel', { v: 'eng_kjv,spa_rv' })).not.toBeNull(); }); test('identical requests made at the same moment share one upstream call, and nothing is kept afterwards', async () => { vi.resetModules(); process.env.IQBIBLE_API_KEY = 'k'; const realFetch = globalThis.fetch; let upstreamCalls = 0; vi.stubGlobal('fetch', vi.fn(async (url, opts) => { if (String(url).startsWith('http://localhost')) return realFetch(url, opts); // the test's own requests upstreamCalls += 1; await new Promise((r) => setTimeout(r, 30)); return new Response(JSON.stringify({ data: 1 }), { status: 200, headers: { 'content-type': 'application/json' } }); })); const { registerIqBibleRoutes } = await import('./iqbible.js'); const express = (await import('express')).default; const app = express(); app.use((req, _res, next) => { req.session = { userId: 'u1' }; next(); }); registerIqBibleRoutes(app, (_req, _res, next) => next()); const srv = await new Promise((resolve) => { const s = app.listen(0, () => resolve(s)); }); const url = `http://localhost:${srv.address().port}/api/iq/devotionals/today`; try { const together = await Promise.all([realFetch(url), realFetch(url), realFetch(url)]); expect(together.map((r) => r.status)).toEqual([200, 200, 200]); expect(upstreamCalls).toBe(1); await realFetch(url); // afterwards: not remembered, so it asks again expect(upstreamCalls).toBe(2); } finally { srv.close(); vi.unstubAllGlobals(); delete process.env.IQBIBLE_API_KEY; } });