70 lines
3.6 KiB
JavaScript
70 lines
3.6 KiB
JavaScript
'use strict';
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { parseSchedule, scheduled, publicSchedule, answerMatches, createReadingAccess } = require('../reading-access');
|
|
|
|
const windows = parseSchedule(JSON.stringify({
|
|
0: [['08:00', '01:00']], 1: [['07:00', '12:00'], ['13:00', '24:00']],
|
|
2: [['07:00', '24:00']], 3: [['07:00', '24:00']], 4: [['07:00', '24:00']],
|
|
5: [['07:00', '24:00']], 6: [['08:00', '01:00']],
|
|
}), 'Europe/Berlin');
|
|
const at = iso => new Date(iso);
|
|
|
|
test('multiple daily windows and weekend overnight carry in local time', () => {
|
|
assert.equal(scheduled(windows, at('2026-10-12T08:00:00Z')), true); // Monday 10:00
|
|
assert.equal(scheduled(windows, at('2026-10-12T10:30:00Z')), false); // lunch gap 12:30
|
|
assert.equal(scheduled(windows, at('2026-10-12T11:30:00Z')), true);
|
|
assert.equal(scheduled(windows, at('2026-10-10T22:30:00Z')), true); // Sunday 00:30 from Saturday
|
|
assert.equal(scheduled(windows, at('2026-10-11T22:30:00Z')), true); // Monday 00:30 from Sunday
|
|
assert.equal(scheduled(windows, at('2026-10-11T23:30:00Z')), false); // Monday 01:30
|
|
});
|
|
|
|
test('schedule config rejects malformed and zero-length windows', () => {
|
|
assert.throws(() => parseSchedule('{"1":[["12:00","12:00"]]}', 'Europe/Berlin'));
|
|
assert.throws(() => parseSchedule('{"7":[]}', 'Europe/Berlin'));
|
|
assert.throws(() => parseSchedule('{"1":[["25:00","26:00"]]}', 'Europe/Berlin'));
|
|
});
|
|
|
|
test('public schedule exposes only the configured read-only windows', () => {
|
|
const view = publicSchedule(windows);
|
|
assert.equal(view.enabled, true);
|
|
assert.equal(view.timezone, 'Europe/Berlin');
|
|
assert.deepEqual(view.days[1], [['07:00', '12:00'], ['13:00', '24:00']]);
|
|
assert.deepEqual(view.days[6], [['08:00', '01:00']]);
|
|
assert.deepEqual(publicSchedule(null), { enabled: false, timezone: null, days: [] });
|
|
});
|
|
|
|
test('answers accept aliases and small typos but not unrelated words', () => {
|
|
assert.equal(answerMatches(' luffy ', ['Monkey D. Luffy', 'Luffy']), true);
|
|
assert.equal(answerMatches('Lufy', ['Luffy']), true);
|
|
assert.equal(answerMatches('pirate', ['Luffy']), false);
|
|
});
|
|
|
|
test('five ordered answers grant time, allow retries and reset on a new visit', () => {
|
|
let clock = Date.parse('2026-10-14T01:00:00Z'), saves = 0;
|
|
const grants = {}, bank = { moon: Array.from({ length: 5 }, (_, i) => ({ id: `q${i}`, prompt: `Question ${i}`, answers: [`answer${i}`], requires: ['v01/c0001'] })) };
|
|
let finished = false;
|
|
const gate = createReadingAccess({ schedule: parseSchedule('{"0":[],"1":[],"2":[],"3":[],"4":[],"5":[],"6":[]}', 'UTC'),
|
|
bank, grants, save: () => saves++, now: () => clock,
|
|
getProgress: () => ({ 'v01/c0001': { done: finished } }), getMangaTitle: () => 'Moon Garden' });
|
|
assert.equal(gate.status('reader', true).quizAvailable, false);
|
|
assert.throws(() => gate.start('reader'));
|
|
finished = true;
|
|
assert.equal(gate.status('reader', true).quizAvailable, true);
|
|
const first = gate.start('reader');
|
|
assert.equal(gate.answer('reader', first.challenge, 'wrong').correct, false);
|
|
assert.equal(gate.start('reader').number, 1);
|
|
assert.throws(() => gate.answer('reader', first.challenge, 'answer0'));
|
|
let current = gate.start('reader');
|
|
for (let i = 0; i < 5; i++) {
|
|
const answer = bank.moon.find(q => q.prompt === current.prompt).answers[0];
|
|
current = gate.answer('reader', current.challenge, answer);
|
|
}
|
|
assert.equal(current.complete, true);
|
|
assert.equal(grants.reader, clock + 3600000);
|
|
assert.equal(saves, 1);
|
|
assert.equal(gate.status('reader').allowed, true);
|
|
clock += 3600000;
|
|
assert.equal(gate.status('reader').allowed, false);
|
|
});
|