// Generated, do not edit. sha256:7b65131858517607493b339c40ff58b8b81242aed88852e46fb2e02ffabfaa2b import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, rm, writeFile, mkdir, readFile, readdir } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; import { installDir, pointerFile, portfolioDir, assertValidName, normalizeName, sameName, readActiveName, writeActiveName, activeName, activeDir, migrateLooseLayout, prepareLayout, DEFAULT_NAME, MIGRATION_NAME, } from '../../server/portfolios.mjs'; import { dbFile } from '../../server/db.js'; import { secretsFile } from '../../server/keys.mjs'; let DIR; let vorige; beforeEach(async () => { vorige = process.env.ASSETAPP_DATA_DIR; DIR = await mkdtemp(join(tmpdir(), 'assetapp-portefeuilles-')); process.env.ASSETAPP_DATA_DIR = DIR; }); afterEach(async () => { if (vorige === undefined) delete process.env.ASSETAPP_DATA_DIR; else process.env.ASSETAPP_DATA_DIR = vorige; await rm(DIR, { recursive: true, force: true }); }); const bestandenIn = async (pad) => (await readdir(pad)).sort((a, b) => a.localeCompare(b, 'nl')); describe('portfolios: de naam als pad', () => { it('leest de installatiemap bij elke aanroep, niet bij het laden', () => { assert.equal(installDir(), DIR); process.env.ASSETAPP_DATA_DIR = join(DIR, 'ergens-anders'); assert.equal(installDir(), join(DIR, 'ergens-anders')); process.env.ASSETAPP_DATA_DIR = DIR; }); it('weigert een naam die buiten de datamap wijst', () => { for (const naam of ['..', '../buiten', '..\\buiten', '/etc/passwd', 'C:\\Windows']) { assert.throws(() => portfolioDir(naam), (e) => e.key.startsWith('portfolios.name'), `'${naam}' hoort geweigerd te worden`); } }); it('weigert de gereserveerde apparaatnamen van Windows, ook met een extensie', () => { for (const naam of ['CON', 'con', 'NUL', 'COM1', 'LPT9', 'CON.txt', 'aux.sqlite']) { assert.throws(() => assertValidName(naam), (e) => e.key === 'portfolios.nameReserved', `'${naam}' hoort geweigerd te worden`); } for (const naam of ['Console', 'Com', 'Auxiliair', 'Nulmeting']) assertValidName(naam); }); it('weigert een naam die op een spatie of punt eindigt, in plaats van hem stil af te knippen', () => { assert.throws(() => assertValidName('Demo '), (e) => e.key === 'portfolios.nameTrailing'); assert.throws(() => assertValidName('Demo.'), (e) => e.key === 'portfolios.nameTrailing'); assert.throws(() => assertValidName(' Demo'), (e) => e.key === 'portfolios.nameLeadingSpace'); }); it('weigert de verboden tekens en de stuurtekens', () => { for (const naam of ['ab', 'a:b', 'a"b', 'a|b', 'a?b', 'a*b']) { assert.throws(() => assertValidName(naam), (e) => e.key === 'portfolios.nameForbiddenChars', `'${naam}' hoort geweigerd te worden`); } assert.throws(() => assertValidName(`regel${String.fromCharCode(10)}twee`), (e) => e.key === 'portfolios.nameForbiddenChars'); assert.throws(() => assertValidName(`nul${String.fromCharCode(0)}byte`), (e) => e.key === 'portfolios.nameForbiddenChars'); }); it('weigert een lege naam en een naam die te lang is', () => { assert.throws(() => assertValidName(''), (e) => e.key === 'portfolios.nameRequired'); assert.throws(() => assertValidName(null), (e) => e.key === 'portfolios.nameRequired'); assert.throws(() => assertValidName('x'.repeat(65)), (e) => e.key === 'portfolios.nameTooLong'); assertValidName('x'.repeat(64)); }); it('laat gewone namen met spaties, koppeltekens en accenten door', () => { for (const naam of ['Echt', 'Demo van Jan', 'test-2026', 'Privé']) { assert.equal(portfolioDir(naam), join(DIR, naam)); } }); it('vergelijkt namen op hun genormaliseerde vorm, want niet elk bestandssysteem doet dat gelijk', () => { assert.ok(sameName('Echt', 'echt'), 'hoofdletters maken op Windows en macOS geen verschil'); const samengesteld = `Priv${String.fromCharCode(101, 0x0301)}`; assert.equal(normalizeName(samengesteld), normalizeName('Privé')); assert.ok(!sameName('Echt', 'Demo')); }); it('weigert alles wat geen tekst is, in plaats van er stil een tekst van te maken', () => { assert.throws(() => sameName({ naam: 'Echt' }, 'Echt'), TypeError); assert.throws(() => normalizeName(42), TypeError); }); it('maakt twee verschillende objecten niet aan elkaar gelijk', () => { assert.throws(() => sameName({ naam: 'Echt' }, { naam: 'Demo' }), TypeError, 'zonder deze guard gaven allebei "[object Object]" en was het antwoord true'); }); it('laat "nog geen wijzer" wél toe, want daar wordt rechtstreeks mee vergeleken', () => { assert.equal(sameName('Echt', null), false); assert.equal(sameName(null, null), false, 'niets is ook niet gelijk aan zichzelf'); assert.equal(sameName(undefined, 'Echt'), false); }); }); describe('portfolios: de wijzer', () => { it('geeft null als er nog geen wijzer is, en dan valt activeName op de standaard terug', () => { assert.equal(readActiveName(), null); assert.equal(activeName(), DEFAULT_NAME); assert.equal(readActiveName(), null); }); it('schrijft en leest dezelfde naam terug', () => { writeActiveName('Demo'); assert.equal(readActiveName(), 'Demo'); assert.equal(activeDir(), join(DIR, 'Demo')); }); it('weigert een wijzer die geen geldige JSON is', async () => { await writeFile(pointerFile(), '{ dit is geen json', 'utf-8'); assert.throws(() => readActiveName(), /not valid JSON/); }); it('weigert een wijzer zonder portefeuille, en een wijzer met een onmogelijke naam', async () => { await writeFile(pointerFile(), JSON.stringify({ iets: 'anders' }), 'utf-8'); assert.throws(() => readActiveName(), /names no portfolio/); await writeFile(pointerFile(), JSON.stringify({ portefeuille: '../buiten' }), 'utf-8'); assert.throws(() => readActiveName(), (e) => e.key === 'portfolios.nameForbiddenChars'); }); it('weigert een onmogelijke naam ook bij het schrijven', () => { assert.throws(() => writeActiveName('CON'), (e) => e.key === 'portfolios.nameReserved'); assert.throws(() => writeActiveName('../buiten'), (e) => e.key === 'portfolios.nameForbiddenChars'); }); }); describe('portfolios: de eenmalige verhuizing', () => { const legLosseInstallatieAan = async () => { await writeFile(join(DIR, 'db.sqlite'), 'database', 'utf-8'); await writeFile(join(DIR, 'db.sqlite-wal'), 'wal', 'utf-8'); await writeFile(join(DIR, 'db.sqlite-shm'), 'shm', 'utf-8'); await writeFile(join(DIR, 'secrets.json'), '{"assetapp_auth":{"user":"ik"}}', 'utf-8'); await mkdir(join(DIR, 'tls'), { recursive: true }); await writeFile(join(DIR, 'tls', 'cert.pem'), 'certificaat', 'utf-8'); }; it('verhuist de database en de geheimen naar data/Test/, en laat tls/ staan', async () => { await legLosseInstallatieAan(); assert.equal(migrateLooseLayout(), MIGRATION_NAME); assert.deepEqual(await bestandenIn(join(DIR, MIGRATION_NAME)), ['db.sqlite', 'db.sqlite-shm', 'db.sqlite-wal', 'secrets.json']); assert.deepEqual(await bestandenIn(DIR), [MIGRATION_NAME, 'tls']); assert.equal(await readFile(join(DIR, MIGRATION_NAME, 'secrets.json'), 'utf-8'), '{"assetapp_auth":{"user":"ik"}}'); }); it('doet niets als er niets los staat, en is dus veilig om elke start te draaien', async () => { await mkdir(join(DIR, 'Echt'), { recursive: true }); await writeFile(join(DIR, 'Echt', 'db.sqlite'), 'database', 'utf-8'); assert.equal(migrateLooseLayout(), null); assert.deepEqual(await bestandenIn(DIR), ['Echt']); }); it('WEIGERT als er zowel losse bestanden als een database op de doelplek staan', async () => { await legLosseInstallatieAan(); await mkdir(join(DIR, MIGRATION_NAME), { recursive: true }); await writeFile(join(DIR, MIGRATION_NAME, 'db.sqlite'), 'een andere database', 'utf-8'); assert.throws(() => migrateLooseLayout(), /does not guess/); assert.equal(await readFile(join(DIR, 'db.sqlite'), 'utf-8'), 'database'); assert.equal(await readFile(join(DIR, MIGRATION_NAME, 'db.sqlite'), 'utf-8'), 'een andere database'); }); }); describe('portfolios: database en geheimen volgen de wijzer', () => { it('wijst na een wissel naar de andere map, zonder dat er iets opnieuw geladen wordt', () => { writeActiveName('Echt'); assert.equal(dbFile(), join(DIR, 'Echt', 'db.sqlite')); assert.equal(secretsFile(), join(DIR, 'Echt', 'secrets.json')); writeActiveName('Demo'); assert.equal(dbFile(), join(DIR, 'Demo', 'db.sqlite')); assert.equal(secretsFile(), join(DIR, 'Demo', 'secrets.json')); }); it('houdt database en geheimen in dezelfde map, want geheimen zijn op rij-id gesleuteld', () => { writeActiveName('Demo'); assert.equal(dirname(dbFile()), dirname(secretsFile())); }); it('laat ASSETAPP_DB_FILE winnen, want daar leunen container en test op', () => { writeActiveName('Echt'); process.env.ASSETAPP_DB_FILE = join(DIR, 'ergens', 'anders.sqlite'); try { assert.equal(dbFile(), join(DIR, 'ergens', 'anders.sqlite')); } finally { delete process.env.ASSETAPP_DB_FILE; } }); }); describe('portfolios: de installatie klaarzetten', () => { it('maakt bij een verse installatie de standaardportefeuille en de wijzer', async () => { const uitkomst = prepareLayout(); assert.equal(uitkomst.name, DEFAULT_NAME); assert.equal(uitkomst.migrated, null); assert.equal(readActiveName(), DEFAULT_NAME); assert.deepEqual(await bestandenIn(DIR), ['active.json', DEFAULT_NAME]); assert.match(uitkomst.regel, new RegExp(`portefeuille: ${DEFAULT_NAME}`)); }); it('wijst na een verhuizing naar de verhuisde portefeuille en niet naar de standaard', async () => { await writeFile(join(DIR, 'db.sqlite'), 'database', 'utf-8'); const uitkomst = prepareLayout(); assert.equal(uitkomst.migrated, MIGRATION_NAME); assert.equal(uitkomst.name, MIGRATION_NAME); assert.equal(readActiveName(), MIGRATION_NAME); assert.match(uitkomst.regel, /verhuisd/); }); it('trekt de wijzer van de eerste dag recht, zonder de keuze te verliezen', async () => { await writeFile(join(DIR, 'actief.json'), JSON.stringify({ portefeuille: 'Test' }), 'utf-8'); const uitkomst = prepareLayout(); assert.equal(uitkomst.name, 'Test'); assert.equal(readActiveName(), 'Test'); assert.deepEqual(await bestandenIn(DIR), ['active.json', 'Test'], 'en het oude bestand is weg'); }); it('laat een bestaande wijzer met rust', async () => { writeActiveName('Demo'); const uitkomst = prepareLayout(); assert.equal(uitkomst.name, 'Demo'); assert.equal(readActiveName(), 'Demo'); }); it('is idempotent: twee keer draaien verandert niets aan de uitkomst', async () => { await writeFile(join(DIR, 'db.sqlite'), 'database', 'utf-8'); const eerste = prepareLayout(); const tweede = prepareLayout(); assert.equal(tweede.name, eerste.name); assert.equal(tweede.migrated, null, 'de tweede keer valt er niets meer te verhuizen'); assert.deepEqual(await bestandenIn(DIR), ['active.json', MIGRATION_NAME]); }); });