Files
bindery/tests/js/auth-flows.test.mjs
tim 3e29ab6bf0 Name downloaded books with a template
The new "Book Downloads" group in Settings holds a Calibre template for the file name of a
direct download. Without a template, the name stays the title. The name is also the title if
the template fails or gives no name for a book.
2026-10-03 08:09:41 +02:00

80 lines
4.0 KiB
JavaScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { install, server, nodes, find, field, emit, settle } from './flow-helpers.mjs';
const ui = install();
const { api, setUnauthorizedHandler } = await import('../../bindery/static/js/lib.js');
const { stopPolling } = await import('../../bindery/static/js/jobs.js');
test('login retry, navigation, session expiry and logout preserve the authentication lifecycle', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const backend = server(t);
let auth = { authenticated: false, mode: 'cwa', user: null };
backend.on('GET', 'auth/check', () => auth);
backend.on('GET', 'config', { metadata_fields: {}, mode_labels: {}, library_available: false, download_fields: {} });
backend.on('GET', 'jobs', []);
backend.on('GET', 'stories', []);
backend.on('GET', 'rules', { rules: [], paragraphs: false });
backend.on('GET', 'settings', { recent_chapters: 3, personal_ini: '' });
backend.on('GET', 'stories/1', { error: 'Story unavailable' }, 404);
backend.on('GET', 'stories/1/rules', { rules: [], paragraphs: null });
backend.on('GET', 'chapters/2/diff?', { error: 'Chapter unavailable' }, 404);
backend.on('GET', 'expired', { error: 'Session ended' }, 401);
backend.on('POST', 'auth/logout', () => { auth = { ...auth, authenticated: false }; return {}; });
t.after(() => { stopPolling(); setUnauthorizedHandler(() => {}); });
await import('../../bindery/static/js/main.js');
await settle();
assert.equal(document.activeElement, field(ui.app, 'username'));
assert.equal(backend.sent('GET', 'config').length, 0);
ui.windowEvents.hashchange();
assert.equal(backend.sent('GET', 'stories').length, 0);
const form = () => nodes(ui.app).find((el) => el.tagName === 'form');
field(ui.app, 'username').value = 'reader';
field(ui.app, 'password').value = 'wrong';
nodes(form()).find((el) => el.attributes.type === 'checkbox').checked = false;
backend.on('POST', 'auth/login', { error: 'Wrong password' }, 401);
await emit(form(), 'submit');
assert.match(ui.app.textContent, /Wrong password/);
assert.equal(find(ui.app, 'button', 'Log in').disabled, false);
assert.deepEqual(backend.sent('POST', 'auth/login')[0].body, { username: 'reader', password: 'wrong', remember: false });
field(ui.app, 'password').value = 'correct';
backend.on('POST', 'auth/login', () => { auth = { authenticated: true, mode: 'cwa', user: 'reader' }; return {}; });
await emit(form(), 'submit');
await settle();
assert.ok(find(ui.app, 'button', 'Log out'));
assert.equal(backend.sent('GET', 'config').length, 1);
for (const path of ['', '#/settings', '#/story/1', '#/story/1/chapter/2', '#/missing']) {
window.location.hash = path;
ui.windowEvents.hashchange();
await settle();
}
assert.match(ui.app.textContent, /Page not found/);
assert.equal(backend.sent('GET', 'stories/1/rules').length, 1);
assert.equal(backend.sent('GET', 'chapters/2/diff?').length, 1);
auth = { authenticated: false, mode: 'cwa' };
await assert.rejects(api('expired'), /Session ended/);
await settle();
assert.ok(find(ui.app, 'button', 'Log in'));
const polls = backend.sent('GET', 'jobs').length;
t.mock.timers.tick(60000);
await settle();
assert.equal(backend.sent('GET', 'jobs').length, polls);
const checks = backend.sent('GET', 'auth/check').length;
await assert.rejects(api('expired'), /Session ended/);
assert.equal(backend.sent('GET', 'auth/check').length, checks);
await emit(form(), 'submit');
await settle();
await emit(find(ui.app, 'button', 'Log out'), 'click');
await settle();
assert.ok(find(ui.app, 'button', 'Log in'));
assert.equal(backend.sent('POST', 'auth/logout').length, 1);
backend.on('POST', 'auth/login', () => {
auth = { authenticated: true, mode: 'proxy', user: 'reader', logout_url: 'https://login.example/logout' };
return {};
});
await emit(form(), 'submit');
await settle();
await emit(find(ui.app, 'button', 'Log out'), 'click');
assert.equal(window.location.href, 'https://login.example/logout');
});