Release 2.8.0
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m49s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
2026-08-16 17:15:31 +01:00
parent 1bdc122995
commit 203d0bfc01
105 changed files with 4185 additions and 677 deletions
+80
View File
@@ -0,0 +1,80 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const {
getDefaultAppSettings,
normalizeSettingValue,
normalizeAppSettings,
fetchAppSettings,
saveAppSettings
} = require('../src/data/app-settings');
test('application settings expose typed defaults', () => {
const settings = getDefaultAppSettings();
assert.equal(settings['app.name'], 'Pulse Signage');
assert.equal(settings['uploads.image_max_bytes'], 100 * 1024 * 1024);
assert.deepEqual(settings['uploads.allowed_mime_types'], ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/svg+xml', 'video/mp4', 'video/webm', 'video/ogg']);
assert.equal(settings['uploads.optimize_images'], true);
assert.equal(settings['security.session_lifetime_days'], 14);
assert.equal(settings['security.allow_user_session_revocation'], true);
assert.equal(settings['security.max_active_sessions'], 0);
assert.equal(settings['security.password_min_length'], 10);
assert.equal(settings['security.password_min_categories'], 3);
assert.equal(settings['security.require_password_change_for_new_users'], true);
assert.equal(settings['security.require_password_change_after_admin_reset'], true);
assert.equal(settings['security.login_max_attempts'], 5);
assert.equal(settings['security.login_lockout_minutes'], 15);
assert.equal(settings['security.login_rate_limit_scope'], 'both');
assert.equal(settings['data-sources.rss_default_interval_value'], 60);
assert.equal(settings['data-sources.rss_default_interval_unit'], 'minutes');
assert.equal(settings['data-sources.api_default_interval_value'], 60);
assert.equal(settings['data-sources.api_default_interval_unit'], 'minutes');
});
test('application settings normalize supported values and reject invalid values', () => {
assert.equal(normalizeSettingValue('player.default_fade_between_slides', 'false'), false);
assert.equal(normalizeSettingValue('uploads.optimize_images', 'false'), false);
assert.deepEqual(normalizeSettingValue('uploads.allowed_mime_types', ['image/png', ' image/png', 'video/mp4']), ['image/png', 'video/mp4']);
assert.throws(() => normalizeSettingValue('player.default_fade_between_slides', 'bounce'), /Invalid value/);
assert.throws(() => normalizeSettingValue('uploads.image_max_bytes', 0), /Invalid value/);
assert.throws(() => normalizeSettingValue('unknown.setting', 'value'), /Unknown application setting/);
});
test('fetch app settings merges stored values with defaults', async () => {
const pool = {
async query() {
return [[
{ setting_key: 'uploads.image_max_bytes', setting_value: '2097152' },
{ setting_key: 'uploads.optimize_images', setting_value: 'false' },
{ setting_key: 'unknown.setting', setting_value: 'ignored' }
]];
}
};
const settings = await fetchAppSettings(pool);
assert.equal(settings['uploads.image_max_bytes'], 2097152);
assert.equal(settings['uploads.optimize_images'], false);
assert.equal(settings['player.default_fade_between_slides'], true);
});
test('save app settings writes only supplied keys in one transaction', async () => {
const queries = [];
const connection = {
async beginTransaction() { queries.push('begin'); },
async query(sql, params) { queries.push({ sql, params }); },
async commit() { queries.push('commit'); },
async rollback() { queries.push('rollback'); },
release() { queries.push('release'); }
};
const pool = {
async getConnection() { return connection; }
};
const saved = await saveAppSettings(pool, { 'player.default_fade_between_slides': false }, 7);
assert.equal(saved['player.default_fade_between_slides'], false);
assert.equal(queries[0], 'begin');
assert.equal(queries[queries.length - 2], 'commit');
assert.equal(queries[queries.length - 1], 'release');
assert.equal(queries.filter(function (entry) { return entry && entry.sql; }).length, 1);
});