Files
bindery/tests/js/auth-flows.test.mjs
T
tim 2f4271314b Add the web interface
Plain HTML, CSS and JavaScript modules, with no build step. Hash routes
and relative URLs make it work under any path. The job list is polled:
every 2 seconds while a job runs, and less often otherwise.
2026-09-27 23:38:28 +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 });
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');
});