const test = require('node:test'); const assert = require('node:assert/strict'); require('../src/common'); const registerSettingsRoutes = require('../src/web/routes/settings/routes'); const { ANNOUNCEMENT_ICON_CATALOG } = require('../src/data/announcement-icons'); test('settings route renders the overview', async () => { const handlers = {}; const queries = []; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; }, post(path, ...routeHandlers) { handlers['POST ' + path] = routeHandlers; } }; registerSettingsRoutes(app, { pages: { renderSettingsPage(_settings, _icons, currentUser) { return currentUser && currentUser.id === 7 ? 'settings-page' : 'unexpected'; } }, pool: { async query(sql, params) { queries.push({ sql, params }); return [[]]; } }, requirePermission(permissionKey) { assert.ok(['system-settings.read', 'system-settings.update'].includes(permissionKey)); return function (_req, _res, next) { next(); }; } }); const pageResponse = { send(value) { this.body = value; } }; await handlers['/settings/system'][1]({ currentUser: { id: 7 }, query: {} }, pageResponse, function (error) { throw error; }); assert.equal(pageResponse.body, 'settings-page'); assert.equal(Object.keys(handlers).length, 2); assert.ok(handlers['/settings/system']); assert.ok(handlers['POST /settings/system']); const saveResponse = { redirect(url) { this.redirectedTo = url; } }; await handlers['POST /settings/system'][1]({ body: { 'suggested_icons[]': ['bell-fill'], image_max_mb: '100', video_max_mb: '1024' }, body: { settings_section: 'icons', 'suggested_icons[]': ['bell-fill'] }, currentUser: { id: 7 } }, saveResponse, function (error) { throw error; }); assert.ok(queries.some(function (entry) { return entry.sql && /DELETE FROM o_app_settings/.test(entry.sql); })); assert.ok(queries.some(function (entry) { return entry.sql && /INSERT INTO o_app_settings/.test(entry.sql); })); queries.filter(function (entry) { return entry.sql && /DELETE FROM o_app_settings/.test(entry.sql); }).forEach(function (entry) { assert.equal((entry.sql.match(/\?/g) || []).length, entry.params.length); assert.ok(entry.params.includes('email.invitation_body')); }); assert.match(saveResponse.redirectedTo, /^\/settings\/system\?message=/); let tooManyError = null; await handlers['POST /settings/system'][1]({ body: { settings_section: 'icons', suggested_icons: ANNOUNCEMENT_ICON_CATALOG.slice(0, 49).map(function (option) { return option.value; }) }, currentUser: { id: 7 } }, {}, function (error) { tooManyError = error; }); assert.equal(tooManyError.statusCode, 400); assert.match(tooManyError.message, /48/); const mediaResponse = { redirect(url) { this.redirectedTo = url; } }; await handlers['POST /settings/system'][1]({ body: { settings_section: 'media', image_max_mb: '100', video_max_mb: '1024', wysiwyg_image_max_mb: '0.5', allowed_mime_types: ['image/png', 'video/mp4'] }, currentUser: { id: 7 } }, mediaResponse, function (error) { throw error; }); assert.match(mediaResponse.redirectedTo, /media-uploads/); const securityResponse = { redirect(url) { this.redirectedTo = url; } }; await handlers['POST /settings/system'][1]({ body: { settings_section: 'security', session_lifetime_days: '30', allow_user_session_revocation: '1', max_active_sessions: '5', login_max_attempts: '5', login_lockout_minutes: '15', login_rate_limit_scope: 'both', password_min_length: '12', password_min_categories: '3' }, currentUser: { id: 7 } }, securityResponse, function (error) { throw error; }); assert.match(securityResponse.redirectedTo, /security-sessions/); assert.ok(queries.some(function (entry) { return entry.sql && entry.sql.includes('INSERT INTO o_app_settings') && entry.params && entry.params[0] === 'security.session_lifetime_days'; })); assert.ok(queries.some(function (entry) { return entry.sql && entry.sql.includes('INSERT INTO o_app_settings') && entry.params && entry.params[0] === 'security.password_min_length'; })); const mediaInsertQueries = queries.filter(function (entry) { return entry.sql && entry.sql.includes('INSERT INTO o_app_settings'); }); const videoInsert = mediaInsertQueries.find(function (entry) { return entry.params && entry.params[0] === 'uploads.video_max_bytes'; }); assert.equal(Number(videoInsert.params[1]), 1024 * 1024 * 1024); }); test('email settings can send a test email to the logged-in user', async () => { const handlers = {}; const sentMessages = []; const app = { get(path, ...routeHandlers) { handlers[path] = routeHandlers; }, post(path, ...routeHandlers) { handlers['POST ' + path] = routeHandlers; } }; registerSettingsRoutes(app, { pages: {}, pool: { async query(sql) { if (/SELECT id, setting_key, setting_value FROM o_app_settings/.test(sql)) { return [[ { setting_key: 'email.smtp_enabled', setting_value: 'true' }, { setting_key: 'email.smtp_host', setting_value: 'smtp.example.test' }, { setting_key: 'email.from_address', setting_value: 'pulse@example.test' } ]]; } throw new Error('Unexpected settings write during test email action.'); } }, requirePermission() { return function (_req, _res, next) { next(); }; }, sendAccountEmail(settings, message) { sentMessages.push({ settings, message }); } }); const response = { redirect(url) { this.redirectedTo = url; } }; await handlers['POST /settings/system'][1]({ body: { settings_section: 'email', settings_action: 'test_email' }, currentUser: { id: 7, email: 'admin@example.test' } }, response, function (error) { throw error; }); assert.equal(sentMessages.length, 1); assert.equal(sentMessages[0].message.to, 'admin@example.test'); assert.equal(sentMessages[0].message.subject, 'Pulse Signage SMTP test email'); assert.match(response.redirectedTo, /Test%20email%20sent%20to%20admin%40example\.test/); });