'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'psitransfer-settings-'));
process.env.PSITRANSFER_UPLOAD_DIR = tmpDir;
const { test, beforeEach, after } = require('node:test');
const assert = require('node:assert');
const config = require('../../config');
const settings = require('../../lib/settings');
const PNG = Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), Buffer.alloc(32)]);
const SVG = Buffer.from('');
beforeEach(() => {
fs.rmSync(config.settingsDir, { recursive: true, force: true });
settings.reset();
});
after(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
test('settings live inside the data directory', () => {
assert.strictEqual(config.settingsDir, path.join(tmpDir, '.settings'));
});
test('brand falls back to the configured defaults', () => {
const brand = settings.brand();
assert.strictEqual(brand.name, 'Dodo Transfer');
assert.strictEqual(brand.from, 2026);
assert.strictEqual(brand.customLogo, false);
assert.match(brand.logoUrl, /assets\/brand-logo\?v=[0-9a-f]{10}$/);
});
test('footer shows a range once the start year is in the past', () => {
assert.strictEqual(settings.footerText('ACME', 2026, 2026), '© 2026 ACME');
assert.strictEqual(settings.footerText('ACME', 2026, 2028), '© 2026–2028 ACME');
assert.strictEqual(settings.footerText('', 2026, 2026), '© 2026');
});
test('update validates and persists', () => {
settings.update({ name: ' My Transfer ', owner: 'Team', from: '2024' });
assert.deepStrictEqual(
JSON.parse(fs.readFileSync(path.join(config.settingsDir, 'settings.json'), 'utf8')),
{ name: 'My Transfer', owner: 'Team', from: 2024 }
);
settings.reset();
assert.strictEqual(settings.brand().name, 'My Transfer');
assert.throws(() => settings.update({ name: ' ' }), /name/);
assert.throws(() => settings.update({ name: 'x'.repeat(41) }), /name/);
assert.throws(() => settings.update({ from: 1999 }), /year/);
assert.throws(() => settings.update({ from: 'abc' }), /year/);
assert.throws(() => settings.update({ owner: 'x'.repeat(61) }), /owner/);
// failed updates change nothing
assert.strictEqual(settings.brand().name, 'My Transfer');
});
test('logo is detected by its content', () => {
assert.strictEqual(settings.detectLogo(PNG).mime, 'image/png');
assert.strictEqual(settings.detectLogo(SVG).mime, 'image/svg+xml');
assert.strictEqual(settings.detectLogo(Buffer.from('RIFF\0\0\0\0WEBPVP8 ')).mime, 'image/webp');
assert.strictEqual(settings.detectLogo(Buffer.from('')), null);
assert.strictEqual(settings.detectLogo(Buffer.from('GIF89a......')), null);
});
test('svg logos with scripts are rejected', () => {
const evil = Buffer.from('');
assert.throws(() => settings.detectLogo(evil), /script/i);
const handler = Buffer.from('');
assert.throws(() => settings.detectLogo(handler), /script/i);
});
test('custom logo replaces the default and can be restored', () => {
const before = settings.brand();
const after1 = settings.setLogo(PNG);
assert.strictEqual(after1.customLogo, true);
assert.notStrictEqual(after1.logoUrl, before.logoUrl, 'url changes with the image (cache busting)');
assert.ok(fs.existsSync(path.join(config.settingsDir, 'logo.png')));
// another type removes the old file
settings.setLogo(SVG);
assert.ok(fs.existsSync(path.join(config.settingsDir, 'logo.svg')));
assert.ok(!fs.existsSync(path.join(config.settingsDir, 'logo.png')));
assert.strictEqual(settings.logoInfo().mime, 'image/svg+xml');
const restored = settings.clearLogo();
assert.strictEqual(restored.customLogo, false);
assert.strictEqual(restored.logoUrl, before.logoUrl);
assert.ok(!fs.existsSync(path.join(config.settingsDir, 'logo.svg')));
});
test('invalid logos are refused', () => {
assert.throws(() => settings.setLogo(Buffer.alloc(0)), /no image/);
assert.throws(() => settings.setLogo(Buffer.from('not an image at all')), /PNG/);
assert.throws(() => settings.setLogo(Buffer.concat([PNG, Buffer.alloc(settings.MAX_LOGO_SIZE)])), /1MB/);
assert.strictEqual(settings.brand().customLogo, false);
});