Files
MangaCircus/test/reading-access.test.js

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);
});