Show read-only server reading hours in settings

This commit is contained in:
Codex committed 2026-10-14 22:42:54 +02:00
1 parent de8e7ed988
commit ddd9c62c40
9 files changed
+89 -7

No files matched your search

+10 -1
View File
@@ -49,8 +49,10 @@ 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',
READER_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' } : {}),
READING_TIMEZONE: 'Europe/Berlin', READING_QUIZ_UNLOCK_MINUTES: '60', READING_QUIZ_CATALOG_PATH: '/data/reading-quiz.json' }
: { READING_WINDOWS_JSON: '', READING_QUIZ_CATALOG_PATH: '' }),
...(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) => {
@@ -81,6 +83,13 @@ let keep = false;
const signup = await fetch(url + '/api/signup', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'qa', password: 'disposable-local-test' }) });
assert.equal(signup.status, 200); const cookie = signup.headers.get('set-cookie').split(';')[0];
const api = (endpoint, body) => fetch(url + endpoint, { headers: { cookie, 'content-type': 'application/json' }, ...(body ? { method: 'POST', body: JSON.stringify(body) } : {}) });
assert.equal((await fetch(url + '/api/reading-schedule')).status, 401);
const schedule = await (await api('/api/reading-schedule')).json();
assert.equal(schedule.enabled, readingGateQa);
if (readingGateQa) {
assert.equal(schedule.timezone, 'Europe/Berlin');
assert.deepEqual(schedule.days, Array.from({ length: 7 }, () => []));
}
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;
+20 -1
View File
@@ -24,7 +24,22 @@ module.exports = async function testSettingsUI(url, cookie) {
await page.goto(url + '/settings.html');
await page.getByRole('heading', { name: 'Reading', exact: true, level: 1 }).waitFor();
assert.equal(await page.locator('.settings-menu-group').count(), 2);
assert.equal(requests.filter(p => p.startsWith('/api/admin')).length, 0, 'Personal settings fetched admin data');
assert.equal(requests.filter(p => p.startsWith('/api/admin') && p !== '/api/admin/peering').length, 0,
'Personal settings fetched admin data beyond the top navigation peer indicator');
await clickSection('user/reading-hours', 'Reading hours');
await page.getByRole('heading', { name: 'Server reading hours' }).waitFor();
const schedule = await page.evaluate(() => fetch('/api/reading-schedule').then(r => r.json()));
if (schedule.enabled) {
await page.getByText(`Server timezone: ${schedule.timezone}`).waitFor();
assert.equal(await page.locator('.reading-hours-day').count(), 7);
} else await page.getByText('Reading is available at any time. No schedule is configured.').waitFor();
assert.equal(await page.locator('[data-settings-section="user/reading-hours"] input').count(), 0);
await page.screenshot({ path: path.join(shots, 'desktop-reading-hours.png'), fullPage: true });
await page.setViewportSize({ width: 390, height: 844 });
await noOverflow();
await page.screenshot({ path: path.join(shots, 'mobile-reading-hours.png'), fullPage: true });
await page.setViewportSize({ width: 1280, height: 900 });
await clickSection('user/reading', 'Reading');
await page.getByLabel('Skip chapter cover pages', { exact: true }).check();
await page.waitForFunction(async () => (await (await fetch('/api/me')).json()).settings.skipCovers === true);
await page.screenshot({ path: path.join(shots, 'desktop-reading.png'), fullPage: true });
@@ -96,6 +111,10 @@ module.exports = async function testSettingsUI(url, cookie) {
await page.getByRole('heading', { name: 'Reading', exact: true, level: 1 }).waitFor();
assert.equal(await page.locator('.settings-menu-group').count(), 1);
assert.equal(await page.locator('.settings-menu a[href*="admin"]').count(), 0);
await clickSection('user/reading-hours', 'Reading hours');
await page.getByRole('heading', { name: 'Server reading hours' }).waitFor();
assert.equal(await page.evaluate(() => fetch('/api/reading-schedule').then(r => r.status)), 200);
await noOverflow();
assert.equal(requests.slice(readerRequestStart).filter(p => p.startsWith('/api/admin')).length, 0, 'Ordinary reader fetched admin data');
assert.equal(await page.evaluate(() => fetch('/api/admin').then(r => r.status)), 403);
await noOverflow();