Build and Push Docker Image / build-and-push (push) Successful in 6m47s
- Admin: sign-up Open / Invite only / Closed with single-use invite links - Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes) - Series: group studies on the dashboard, filter by series, calendar file for a study's sessions - Lesson handout: printable page per session (passage, outline, questions, writing lines) - Save a chunk's notes as your own study template (synced) - Voice dictation for notes and scripts (browser speech recognition) - Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices - Export highlights and bookmarks as Markdown or Word, grouped by named colour - Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY) - Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
34 lines
1.7 KiB
JavaScript
34 lines
1.7 KiB
JavaScript
// @vitest-environment node
|
|
import { test, expect } from 'vitest';
|
|
import { checkShareAccess, expiryFromDays, hashPasscode } from './shareLinks.js';
|
|
|
|
const NOW = 1_800_000_000_000;
|
|
|
|
test('expiry is a number of days from now, or never', () => {
|
|
expect(expiryFromDays(null, NOW)).toBeNull();
|
|
expect(expiryFromDays(0, NOW)).toBeNull();
|
|
expect(expiryFromDays('x', NOW)).toBeNull();
|
|
expect(expiryFromDays(7, NOW)).toBe(NOW + 7 * 86_400_000);
|
|
expect(expiryFromDays(9999, NOW)).toBe(NOW + 365 * 86_400_000); // never more than a year
|
|
});
|
|
|
|
test('a link with no rules is open; an expired one says so', async () => {
|
|
expect((await checkShareAccess({}, NOW)).ok).toBe(true);
|
|
expect(await checkShareAccess({ expiresAt: NOW + 1000 }, NOW)).toEqual({ ok: true });
|
|
expect(await checkShareAccess({ expiresAt: NOW - 1 }, NOW)).toMatchObject({ ok: false, status: 410 });
|
|
});
|
|
|
|
test('a passcode is asked for, and only the right one opens the link', async () => {
|
|
const passHash = await hashPasscode(' moses ');
|
|
expect(passHash).not.toContain('moses');
|
|
expect(await checkShareAccess({ passHash }, NOW)).toMatchObject({ ok: false, status: 401, needsPasscode: true });
|
|
expect(await checkShareAccess({ passHash, supplied: 'aaron' }, NOW)).toMatchObject({ ok: false, status: 401, needsPasscode: true, error: expect.stringMatching(/not right/) });
|
|
expect(await checkShareAccess({ passHash, supplied: ' moses ' }, NOW)).toEqual({ ok: true });
|
|
expect(await hashPasscode(' ')).toBeNull();
|
|
});
|
|
|
|
test('an expired link is reported as expired even before the passcode is checked', async () => {
|
|
const passHash = await hashPasscode('x');
|
|
expect(await checkShareAccess({ passHash, expiresAt: NOW - 5, supplied: 'x' }, NOW)).toMatchObject({ status: 410 });
|
|
});
|