Build and Push Docker Image / build-and-push (push) Successful in 7m14s
Sync and data safety - Autosave flushes on leaving a study or the tab; storage-full errors are reported - Unsynced studies, deletes and ink are queued and retried when back online - Deletes propagate across devices (with a "Keep it" undo) and no longer resurrect - Server assigns study versions (no client-clock conflicts); "Combine both" merges a conflicted study chunk by chunk - Reading-plan progress made offline is merged, not overwritten; plans, ink and marks are cleared from the device on sign-out Security - Block SSRF through the ntfy reminder URL; failure-only rate limiting per IP and account; constant-time login; TOTP replay protection; input validation; SESSION_SECRET required in production; admin user delete fixed - Forgot password: one-time code sent to the account's ntfy topic Features - IQ Bible proxy (route allow-list, text-only caching per its terms): more commentaries, lexicons, study aids, daily verse and devotional, plan calendar export, narrations, podcast background - Study templates, reading-plan catch-up, account export, daily database snapshots Tests and tooling - ESLint config; App tests split into src/test; tests for sync queues, library hook, merge, templates, IQ Bible UI and reset form Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
35 lines
1.7 KiB
JavaScript
35 lines
1.7 KiB
JavaScript
// @vitest-environment node
|
|
import { test, expect } 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();
|
|
});
|