Merge remote-tracking branch 'origin/main' into feature/stale-reading-session
This commit is contained in:
commit
dddf9bb4ac
9 files changed
+326
-27
No files matched your search
@@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
// Reader settings per login session: a session follows the latest set until it changes one, then keeps its own.
|
||||
const test = require('node:test'), assert = require('node:assert/strict');
|
||||
const fs = require('fs'), os = require('os'), path = require('path'), net = require('net');
|
||||
const { spawn } = require('child_process');
|
||||
const readerSessions = require('../reader-sessions');
|
||||
|
||||
async function freePort() {
|
||||
const listener = net.createServer();
|
||||
await new Promise(resolve => listener.listen(0, '127.0.0.1', resolve));
|
||||
const port = listener.address().port;
|
||||
await new Promise(resolve => listener.close(resolve));
|
||||
return port;
|
||||
}
|
||||
|
||||
test('device labels name browser and system', () => {
|
||||
assert.equal(readerSessions.deviceLabel('Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Version/17.0 Mobile/15E148 Safari/604.1'), 'Safari on iPhone');
|
||||
assert.equal(readerSessions.deviceLabel('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0 Safari/537.36 Edg/130.0'), 'Edge on Windows');
|
||||
assert.equal(readerSessions.deviceLabel(''), 'Unknown device');
|
||||
});
|
||||
|
||||
test('sessions lock their reader settings on the first change, new ones copy the last active one', { timeout: 20000 }, async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mc-reader-sessions-'));
|
||||
let child;
|
||||
try {
|
||||
const library = path.join(root, 'library'), data = path.join(root, 'data');
|
||||
fs.mkdirSync(library); fs.mkdirSync(data);
|
||||
const port = await freePort(), output = [];
|
||||
child = spawn(process.execPath, ['server.js'], { cwd: path.resolve(__dirname, '..'), windowsHide: true,
|
||||
env: { ...process.env, LIBRARY: library, DATA: data, PORT: String(port), SCAN_INTERVAL: '0', FULL_SCAN_INTERVAL: '0',
|
||||
SCRAPER_URL: 'http://127.0.0.1:9', SCRAPER_TOKEN: 'fixture-token', SCRAPER_TOKEN_FILE: '', BACKUP: '',
|
||||
GREATEST_COVER_WARM: '0' } });
|
||||
child.stdout.on('data', d => output.push(String(d)));
|
||||
child.stderr.on('data', d => output.push(String(d)));
|
||||
const base = `http://127.0.0.1:${port}`;
|
||||
let ready = false;
|
||||
for (let n = 0; n < 100 && !ready; n++) {
|
||||
ready = await fetch(base + '/health').then(r => r.ok).catch(() => false);
|
||||
if (!ready) await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
assert.ok(ready, output.join(''));
|
||||
|
||||
const json = async r => { const body = await r.json(); assert.equal(r.status, 200, JSON.stringify(body)); return body; };
|
||||
const post = (cookie, url, body) => fetch(base + url, { method: 'POST',
|
||||
headers: { 'content-type': 'application/json', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) Firefox/130.0', ...(cookie ? { cookie } : {}) },
|
||||
body: JSON.stringify(body) }).then(async r => (url.endsWith('login') || url.endsWith('signup') ? r : json(r)));
|
||||
const me = async cookie => (await json(await fetch(base + '/api/me', { headers: { cookie } }))).settings;
|
||||
const overview = cookie => fetch(base + '/api/reader-sessions', { headers: { cookie } }).then(json);
|
||||
const login = async () => (await post(null, '/api/login', { name: 'reader', password: 'reader-password' })).headers.get('set-cookie').split(';')[0];
|
||||
|
||||
const a = (await post(null, '/api/signup', { name: 'reader', password: 'reader-password' })).headers.get('set-cookie').split(';')[0];
|
||||
// First session: defaults, following the latest set.
|
||||
assert.equal((await me(a)).lastMode, 'page');
|
||||
assert.equal((await overview(a)).sessions[0].locked, false);
|
||||
|
||||
// Session B exists before A changes anything: it follows A's change.
|
||||
const b = await login();
|
||||
await post(a, '/api/settings', { lastMode: 'flow', zoom: { flow: 1.5 } });
|
||||
assert.equal((await me(b)).lastMode, 'flow');
|
||||
assert.equal((await me(b)).zoom.flow, 1.5);
|
||||
|
||||
// B changes its own: B locks, A keeps its own set.
|
||||
await post(b, '/api/settings', { pageTone: 'sepia' });
|
||||
assert.equal((await me(b)).pageTone, 'sepia');
|
||||
assert.equal((await me(b)).lastMode, 'flow');
|
||||
assert.equal((await me(a)).pageTone, 'original');
|
||||
|
||||
// A non-reader setting is shared and does not lock anything.
|
||||
const c = await login();
|
||||
await post(c, '/api/settings', { continueRow: false });
|
||||
assert.equal((await me(a)).continueRow, false);
|
||||
assert.equal((await overview(c)).sessions.find(s => s.current).locked, false);
|
||||
// C follows the last active session, which is A (it loaded /api/me last).
|
||||
assert.equal((await me(c)).pageTone, 'original');
|
||||
await me(b);
|
||||
assert.equal((await me(c)).pageTone, 'sepia');
|
||||
|
||||
// Overview: three sessions, labels, intent.
|
||||
const o = await overview(a);
|
||||
assert.equal(o.enabled, true);
|
||||
assert.equal(o.sessions.length, 3);
|
||||
assert.equal(o.sessions[0].current, true);
|
||||
assert.equal(o.sessions[0].device, 'Firefox on Linux');
|
||||
assert.deepEqual(o.sessions.map(s => s.locked).sort(), [false, true, true]);
|
||||
|
||||
// "Follow latest again" drops A's lock; "Use for new sessions" makes a set the latest.
|
||||
const idA = o.sessions[0].id, idB = o.sessions.find(s => !s.current && s.locked).id;
|
||||
await post(a, '/api/reader-sessions', { id: idA, action: 'follow' });
|
||||
assert.equal((await me(a)).pageTone, 'sepia');
|
||||
assert.equal((await fetch(base + '/api/reader-sessions', { method: 'POST', headers: { 'content-type': 'application/json', cookie: a },
|
||||
body: JSON.stringify({ id: 'nope', action: 'follow' }) })).status, 404);
|
||||
assert.equal((await post(a, '/api/reader-sessions', { id: idB, action: 'latest' })).latestFrom, idB);
|
||||
|
||||
// Off: one shared set; a change applies everywhere and locks nothing. On again: kept locks return.
|
||||
await post(b, '/api/settings', { readerPerSession: false });
|
||||
await post(a, '/api/settings', { pageTone: 'dim' });
|
||||
assert.equal((await me(b)).pageTone, 'dim');
|
||||
assert.equal((await me(c)).pageTone, 'dim');
|
||||
await post(a, '/api/settings', { readerPerSession: true });
|
||||
assert.equal((await me(b)).pageTone, 'sepia');
|
||||
assert.equal((await me(a)).pageTone, 'sepia');
|
||||
} finally {
|
||||
if (child && child.exitCode === null) { child.kill(); await new Promise(resolve => child.once('exit', resolve)); }
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user