import assert from 'node:assert/strict'; import { test } from 'node:test'; import { Element, change, click, field, find, install, nodes, section, server, settle } from './flow-helpers.mjs'; install(); const { settingsPage } = await import('../../bindery/static/js/settings.js'); async function open(t, settings, authMode = 'proxy') { install(); t.mock.timers.enable({ apis: ['setTimeout'] }); const backend = server(t); const stored = { download_template: '{title}', download_date_format: '%Y', personal_ini: '', ...settings }; backend.on('GET', 'settings', stored); backend.on('GET', 'rules', { rules: [] }); backend.on('GET', 'stories', []); backend.on('PUT', 'settings', ({ body }) => ({ ...stored, ...body })); backend.on('PUT', 'settings/opds-password', { opds_user: 'alice', opds_password_set: true }); const main = new Element('main'); settingsPage(main, {}, { cwa_enabled: false, download_fields: {}, auth_mode: authMode }); await settle(); return { backend, main, card: section(main, 'Book Downloads') }; } for (const enabled of [false, true]) { test(`OPDS loads ${enabled}, appears first in Book Downloads, and saves both states`, async (t) => { const { backend, main, card } = await open(t, { opds_enabled: enabled }); const toggle = field(card, 'opds-enabled'); assert.equal(toggle.checked, enabled); assert.equal(card.children[1].tagName, 'label'); assert.equal(card.children[1].children[0], toggle); assert.equal(card.children[1].textContent, ' Enable OPDS'); assert.equal(toggle.getAttribute('aria-describedby'), 'opds-note'); assert.match(field(card, 'opds-note').textContent, /off by default/); assert.equal(find(field(card, 'opds-note'), 'a', 'the OPDS catalog').getAttribute('href'), 'opds'); assert.equal(nodes(main).filter((node) => node.getAttribute('id') === 'opds-enabled').length, 1); for (const value of [!enabled, enabled]) { change(toggle, value); await click(card, 'Save'); assert.deepEqual(backend.sent('PUT', 'settings').at(-1).body, { opds_enabled: value, download_template: '{title}', download_date_format: '%Y', }); assert.equal(toggle.checked, value); } backend.on('PUT', 'settings', { opds_enabled: false, download_template: '{title}', download_date_format: '%Y' }); change(toggle, true); await click(card, 'Save'); assert.equal(toggle.checked, false, 'The saved response sets the checkbox state'); }); } const LOGIN = { none: /Bindery asks for no login, so the catalog needs none/, local: /sends your Bindery user name and password/, cwa: /sends your CWA user name and password/, proxy: /The proxy skips its login for the catalog.*Each user sets an OPDS password.*user name from the proxy/, }; for (const [mode, text] of Object.entries(LOGIN)) { test(`the note names the login of the ${mode} mode, and only the proxy mode shows the password`, async (t) => { const { card } = await open(t, { opds_enabled: true, opds_password_set: false, opds_user: 'alice' }, mode); assert.match(field(card, 'opds-note').textContent, text); assert.doesNotMatch(field(card, 'opds-note').textContent, /BINDERY_API_KEY|Bearer|X-Api-Key/); for (const id of ['opds-user', 'opds-password', 'opds-password-status']) { assert.equal(field(card, id) !== undefined, mode === 'proxy', id); } assert.equal(find(card, 'button', 'Set password') !== undefined, mode === 'proxy'); }); } test('the password field is grey until the box is checked, and follows the box at once', async (t) => { const { card } = await open(t, { opds_enabled: false, opds_password_set: true, opds_user: 'alice' }); const password = field(card, 'opds-password'); const button = find(card, 'button', 'Reset password'); assert.equal(password.disabled, true); assert.equal(button.disabled, true); change(field(card, 'opds-enabled'), true); assert.equal(password.disabled, false); assert.equal(button.disabled, false); change(field(card, 'opds-enabled'), false); assert.equal(password.disabled, true); assert.equal(button.disabled, true); }); for (const isSet of [false, true]) { test(`the OPDS password ${isSet ? 'is set' : 'is not set'}, and the button sets or resets it`, async (t) => { const { backend, card } = await open(t, { opds_enabled: true, opds_password_set: isSet, opds_user: 'alice' }); const password = field(card, 'opds-password'); const status = field(card, 'opds-password-status'); const user = field(card, 'opds-user'); assert.equal(user.value, 'alice'); assert.equal(user.getAttribute('readonly'), ''); assert.equal(user.disabled, false, 'The user name stays selectable'); assert.equal(user.getAttribute('aria-describedby'), 'opds-password-status'); assert.equal(find(card, 'label', 'OPDS user name').getAttribute('for'), 'opds-user'); assert.equal(find(card, 'label', 'OPDS password').getAttribute('for'), 'opds-password'); assert.equal(password.disabled, false); assert.equal(password.getAttribute('type'), 'password'); assert.equal(password.getAttribute('autocomplete'), 'new-password'); assert.equal(password.getAttribute('minlength'), '8'); assert.equal(password.value, ''); assert.match(status.textContent, /enter the user name and the password in your reader/i); assert.match(status.textContent, isSet ? /You have an OPDS password/ : /You have no OPDS password yet/); assert.ok(find(card, 'button', isSet ? 'Reset password' : 'Set password')); password.value = 'long enough'; await click(card, isSet ? 'Reset password' : 'Set password'); assert.deepEqual(backend.sent('PUT', 'settings/opds-password')[0].body, { password: 'long enough' }); assert.equal(password.value, '', 'The field is empty after the save'); assert.match(status.textContent, /You have an OPDS password/); assert.equal(password.disabled, false); assert.ok(find(card, 'button', 'Reset password')); // The Save button of the card never sends the password. password.value = 'not sent'; await click(card, 'Save'); assert.equal(backend.sent('PUT', 'settings/opds-password').length, 1); assert.equal('password' in backend.sent('PUT', 'settings')[0].body, false); assert.equal(password.disabled, false, 'A save keeps the field in step with the box'); }); } test('a rejected OPDS password keeps the field and the status', async (t) => { const { backend, card } = await open(t, { opds_enabled: true, opds_password_set: false }); backend.on('PUT', 'settings/opds-password', { error: 'The OPDS password must be text of 8 to 256 characters' }, 400); field(card, 'opds-password').value = 'short'; await click(card, 'Set password'); assert.equal(field(card, 'opds-password').value, 'short'); assert.match(field(card, 'opds-password-status').textContent, /You have no OPDS password yet/); assert.ok(find(card, 'button', 'Set password')); });