Add deployment reading windows and progress-aware quiz unlock
This commit is contained in:
1 parent
edbc9c6056
commit
d8eba9169f
15 files changed
+471
-5
No files matched your search
+58
-1
@@ -19,6 +19,11 @@ if (peerGatewayQa) {
|
||||
new PeeringState(peerData).generateIdentity('ComposeQA');
|
||||
}
|
||||
const pausedConfirmQa = process.argv.includes('--paused-confirm');
|
||||
const readingGateQa = process.argv.includes('--reading-limit-ui');
|
||||
if (readingGateQa) fs.writeFileSync(path.join(data, 'reading-quiz.json'), JSON.stringify({ version: 1, manga: {
|
||||
'Moon Garden': [0, 1, 2].map(i => ({ id: `moon-${i}`, prompt: `Moon Garden question ${i}?`, answers: [`moonanswer${i}`], requires: ['1/1'], evidence: 'Disposable Compose fixture' })),
|
||||
'Before the Rain': [0, 1, 2].map(i => ({ id: `rain-${i}`, prompt: `Before the Rain question ${i}?`, answers: [`rainanswer${i}`], requires: ['1/1'], evidence: 'Disposable Compose fixture' })),
|
||||
} }));
|
||||
fs.writeFileSync(path.join(data, 'scraper', 'queue.json'), JSON.stringify({ paused: true,
|
||||
sourcePaused: pausedConfirmQa ? { weebcentral: true } : {},
|
||||
queue: pausedConfirmQa ? [{ id: 'ready-fixture', source: 'weebcentral',
|
||||
@@ -44,6 +49,8 @@ for (const [index, name] of ['Moon Garden', 'Before the Rain', 'Iron Harbor'].en
|
||||
}
|
||||
const env = { ...process.env, LIBRARY_PATH: library, DATA_PATH: data, SCRAPER_DATA_PATH: path.join(data, 'scraper'), SENTINEL_DATA_PATH: path.join(data, 'sentinel'),
|
||||
SCRAPER_TOKEN_PATH: tokenFile, READER_PORT: port, READER_BIND: '127.0.0.1', COOKIE_SECURE: '0', APP_IMAGE: process.env.APP_IMAGE || 'mangacircus:local',
|
||||
...(readingGateQa ? { READING_WINDOWS_JSON: '{"0":[],"1":[],"2":[],"3":[],"4":[],"5":[],"6":[]}',
|
||||
READING_TIMEZONE: 'Europe/Berlin', READING_QUIZ_UNLOCK_MINUTES: '60', READING_QUIZ_CATALOG_PATH: '/data/reading-quiz.json' } : {}),
|
||||
...(peerGatewayQa ? { PEER_DATA_PATH: path.join(data, 'peering'), PEER_USAGE_PATH: path.join(data, 'peering-usage'), PEER_PORT: process.env.QA_PEER_PORT || '8193' } : {}) };
|
||||
const args = ['compose', '-p', project, '-f', path.join(root, 'docker-compose.yml'), ...(peerGatewayQa ? ['--profile', 'peering'] : [])];
|
||||
const docker = (...command) => new Promise((resolve, reject) => {
|
||||
@@ -77,6 +84,54 @@ let keep = false;
|
||||
let inventory;
|
||||
await until(async () => { inventory = await (await api('/api/library')).json(); return inventory.mangas?.length === 3; }, 'Fixture inventory missing');
|
||||
const byTitle = title => inventory.mangas.find(m => m.title === title).id;
|
||||
if (readingGateQa) {
|
||||
const manga = byTitle('Moon Garden'), other = byTitle('Before the Rain');
|
||||
const chapter = (await (await api('/api/manga?m=' + encodeURIComponent(manga))).json()).chapters[0].id;
|
||||
const chapterUrl = '/api/chapter?m=' + encodeURIComponent(manga) + '&c=' + encodeURIComponent(chapter);
|
||||
const pageUrl = '/lib/' + encodeURIComponent(manga) + '/v01/c0001%20The%20Beginning/0001.png';
|
||||
assert.equal((await api(chapterUrl)).status, 423, 'chapter JSON must be gated');
|
||||
assert.equal((await api(pageUrl)).status, 423, 'chapter image must be gated');
|
||||
assert.equal((await api('/lib/' + encodeURIComponent(manga) + '/cover.png')).status, 200);
|
||||
assert.equal((await (await api('/api/reading-access?m=' + encodeURIComponent(manga))).json()).quizAvailable, false);
|
||||
for (const title of [manga, other]) {
|
||||
const id = (await (await api('/api/manga?m=' + encodeURIComponent(title))).json()).chapters[0].id;
|
||||
assert.equal((await api('/api/progress', { m: title, c: id, p: 0, n: 1, done: true })).status, 200);
|
||||
}
|
||||
assert.equal((await (await api('/api/reading-access?m=' + encodeURIComponent(manga))).json()).quizAvailable, true);
|
||||
const { chromium } = require('playwright');
|
||||
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
||||
try {
|
||||
const context = await browser.newContext();
|
||||
const [name, ...values] = cookie.split('=');
|
||||
await context.addCookies([{ name, value: values.join('='), url }]);
|
||||
const page = await context.newPage();
|
||||
await page.goto(url + '/read.html?m=' + encodeURIComponent(manga) + '&c=' + encodeURIComponent(chapter));
|
||||
await page.getByRole('heading', { name: 'Reading is closed right now' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Start five-question challenge' }).click();
|
||||
await page.getByText('Question 1 of 5').waitFor();
|
||||
await page.goto(url + '/manga.html?m=' + encodeURIComponent(manga));
|
||||
await page.goto(url + '/read.html?m=' + encodeURIComponent(manga) + '&c=' + encodeURIComponent(chapter));
|
||||
await page.getByRole('button', { name: 'Start five-question challenge' }).waitFor();
|
||||
} finally { await browser.close(); }
|
||||
if (!process.argv.includes('--keep')) {
|
||||
let question = await (await api('/api/reading-quiz/start', {})).json();
|
||||
const wrong = await api('/api/reading-quiz/answer', { challenge: question.challenge, answer: 'incorrect' });
|
||||
assert.equal((await wrong.json()).correct, false);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const match = /(?:Moon Garden|Before the Rain) question (\d)/.exec(question.prompt);
|
||||
const answer = (question.mangaTitle === 'Moon Garden' ? 'moonanswer' : 'rainanswer') + match[1];
|
||||
question = await (await api('/api/reading-quiz/answer', { challenge: question.challenge, answer })).json();
|
||||
}
|
||||
assert.equal(question.complete, true);
|
||||
assert.equal((await api(chapterUrl)).status, 200);
|
||||
const image = await api(pageUrl);
|
||||
assert.equal(image.status, 200);
|
||||
assert.equal(image.headers.get('cache-control'), 'private, no-store');
|
||||
console.log('PASS: schedule blocks chapter and page bytes, progress-gated mixed-manga quiz unlocks reading');
|
||||
} else {
|
||||
console.log('PASS: isolated playable gate stays locked with progress-qualified sample questions');
|
||||
}
|
||||
}
|
||||
const catalog = { revision: 0, collections: [{ id: 'moon-garden', title: 'Moon Garden Universe', coverMode: 'mosaic', members: [
|
||||
{ mangaId: byTitle('Moon Garden'), role: 'main', readingOrder: 1 }, { mangaId: byTitle('Before the Rain'), role: 'prequel', readingOrder: 2 }] }],
|
||||
relations: [{ from: byTitle('Moon Garden'), to: byTitle('Before the Rain'), type: 'prequel', state: 'confirmed' }] };
|
||||
@@ -159,7 +214,9 @@ let keep = false;
|
||||
keep = process.argv.includes('--keep');
|
||||
if (keep) {
|
||||
const envFile = path.join(dir, 'qa.env');
|
||||
fs.writeFileSync(envFile, ['LIBRARY_PATH', 'DATA_PATH', 'SCRAPER_DATA_PATH', 'SENTINEL_DATA_PATH', 'SCRAPER_TOKEN_PATH', 'READER_PORT', 'READER_BIND', 'COOKIE_SECURE', 'APP_IMAGE'].map(k => `${k}=${env[k].replaceAll('\\', '/')}`).join('\n') + '\n');
|
||||
fs.writeFileSync(envFile, ['LIBRARY_PATH', 'DATA_PATH', 'SCRAPER_DATA_PATH', 'SENTINEL_DATA_PATH', 'SCRAPER_TOKEN_PATH', 'READER_PORT', 'READER_BIND', 'COOKIE_SECURE', 'APP_IMAGE',
|
||||
...(readingGateQa ? ['READING_WINDOWS_JSON', 'READING_TIMEZONE', 'READING_QUIZ_UNLOCK_MINUTES', 'READING_QUIZ_CATALOG_PATH'] : [])]
|
||||
.map(k => `${k}=${k === 'READING_WINDOWS_JSON' ? `'${env[k]}'` : env[k].replaceAll('\\', '/')}`).join('\n') + '\n');
|
||||
console.log(JSON.stringify({ project, url, envFile, fixture: dir }));
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const { loadQuestionBank } = require('../reading-access');
|
||||
const file = process.argv[2];
|
||||
if (!file) { console.error('Usage: node tools/validate-reading-quiz.js <catalog.json>'); process.exit(2); }
|
||||
try {
|
||||
const bank = loadQuestionBank(path.resolve(file));
|
||||
console.log(JSON.stringify({ works: Object.keys(bank).length,
|
||||
questions: Object.values(bank).reduce((count, questions) => count + questions.length, 0) }));
|
||||
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
||||
Reference in new issue
Block a user