Files

151 lines
6.7 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:b1e7db08e72ca29ddd3b364141b2e4cd3fc040df65fe9c6f17ee2547ebc6452c
import { describe, it, before, after, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
import { mkdtemp, mkdir, rm, readFile, writeFile, access } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const DIR = await mkdtemp(join(tmpdir(), 'assetapp-restore-'));
process.env.ASSETAPP_DATA_DIR = DIR;
process.env.ASSETAPP_DB_FILE = join(DIR, 'db.sqlite');
const { initDatabase, dbFile } = await import('../../server/db.js');
const DB_FILE = dbFile();
const { closeDatabase, run, one } = await import('../../server/db/conn.mjs');
const { restoreDatabase } = await import('../../server/db-restore.mjs');
const { SCHEMA } = await import('../../server/db/schema.js');
const { CONFIG } = await import('../../shared/config.js');
const bestaat = async p => { try { await access(p); return true; } catch { return false; } };
async function maakDatabase(pad, { versie = CONFIG.SCHEMA_VERSION, merk = 'nieuw' } = {}) {
const db = new DatabaseSync(pad);
db.exec(SCHEMA);
db.prepare(`INSERT INTO Schema (version) VALUES (?)`).run(BigInt(versie));
db.prepare(`INSERT INTO Label (name, searchName) VALUES (?,?)`).run(merk, merk);
db.close();
return readFile(pad);
}
const huidigMerk = () => { const r = one(`SELECT name FROM Label LIMIT 1`); return r ? r.name : null; };
before(() => initDatabase());
after(async () => { closeDatabase(); await rm(DIR, { recursive: true, force: true }); });
beforeEach(async () => {
closeDatabase();
await rm(DB_FILE, { force: true });
for (const b of ['-wal', '-shm']) await rm(DB_FILE + b, { force: true });
await maakDatabase(DB_FILE, { merk: 'origineel' });
initDatabase();
});
describe('restoreDatabase: het gelukkige pad', () => {
it('vervangt de database en opent de nieuwe', async () => {
const bytes = await maakDatabase(join(DIR, 'bron.sqlite'), { merk: 'teruggezet' });
const r = await restoreDatabase(bytes);
assert.equal(r.versie, CONFIG.SCHEMA_VERSION);
assert.equal(huidigMerk(), 'teruggezet');
});
it('bewaart de vorige database als .previous', async () => {
const bytes = await maakDatabase(join(DIR, 'bron2.sqlite'), { merk: 'teruggezet' });
const r = await restoreDatabase(bytes);
assert.ok(await bestaat(r.previous));
const oud = new DatabaseSync(r.previous, { readOnly: true });
assert.equal(oud.prepare(`SELECT name FROM Label LIMIT 1`).get().name, 'origineel');
oud.close();
});
it('laat geen WAL-buren van de vorige database naast de nieuwe staan', async () => {
run(`INSERT INTO Label (name, searchName) VALUES ('los','los')`);
assert.ok(await bestaat(DB_FILE + '-wal'), 'voorwaarde: er staat een write-ahead log');
const bytes = await maakDatabase(join(DIR, 'bron3.sqlite'), { merk: 'teruggezet' });
await restoreDatabase(bytes);
assert.equal(huidigMerk(), 'teruggezet');
assert.equal(one(`SELECT COUNT(*) AS n FROM Label WHERE name = 'los'`).n, 0,
'de teruggezette database bevat niets uit het log van zijn voorganger');
});
it('meldt verweesde verwijzingen in plaats van ze te verzwijgen', async () => {
const pad = join(DIR, 'wees.sqlite');
const db = new DatabaseSync(pad, { enableForeignKeyConstraints: false });
db.exec(SCHEMA);
db.prepare(`INSERT INTO Schema (version) VALUES (?)`).run(BigInt(CONFIG.SCHEMA_VERSION));
db.prepare(`INSERT INTO WalletSource (accountId, kind, value, createdAt) VALUES (?,?,?,?)`)
.run(999n, 'address', 'bc1q', '2026-01-01T00:00:00Z');
db.close();
const r = await restoreDatabase(await readFile(pad));
assert.ok(r.wezen > 0, 'de wees hoort geteld te worden');
});
it('noemt de geheimen, want die reizen niet mee', async () => {
const bytes = await maakDatabase(join(DIR, 'bron4.sqlite'));
const r = await restoreDatabase(bytes);
assert.match(r.geheimenWaarschuwing, /secrets\.json/);
});
});
describe('restoreDatabase: een afgekeurd bestand raakt de live database niet aan', () => {
async function weigert(bytes, patroon) {
await assert.rejects(() => restoreDatabase(bytes), patroon);
assert.equal(huidigMerk(), 'origineel', 'de live database is niet aangeraakt');
assert.equal(await bestaat(DB_FILE + '.new'), false, 'en er blijft geen half bestand achter');
}
it('weigert een leeg bestand', async () => {
await weigert(Buffer.alloc(0), (e) => e.key === 'dbfile.emptyFile');
});
it('weigert iets dat geen SQLite-bestand is', async () => {
await weigert(Buffer.from('dit is gewoon tekst, geen database'), (e) => e.key === 'dbfile.notSqlite');
});
it('weigert een SQLite-bestand zonder Schema-tabel', async () => {
const pad = join(DIR, 'leeg.sqlite');
const db = new DatabaseSync(pad);
db.exec(`CREATE TABLE Iets (id INTEGER PRIMARY KEY) STRICT;`);
db.close();
await weigert(await readFile(pad), (e) => e.key === 'dbfile.noSchemaTable');
});
it('weigert een database van vóór de baseline, en verwijst naar het conversiescript', async () => {
const bytes = await maakDatabase(join(DIR, 'oud.sqlite'), { versie: 59 });
await weigert(bytes, /baseline-convert/);
});
it('weigert een database die nieuwer is dan de app', async () => {
const bytes = await maakDatabase(join(DIR, 'toekomst.sqlite'), { versie: CONFIG.SCHEMA_VERSION + 1 });
await weigert(bytes, (e) => e.key === 'dbfile.versionNewerThanApp');
});
it('laat de database ook na een afkeuring gewoon bruikbaar', async () => {
await assert.rejects(() => restoreDatabase(Buffer.from('rommel')));
run(`INSERT INTO Label (name, searchName) VALUES ('na','na')`);
assert.equal(one(`SELECT COUNT(*) AS n FROM Label WHERE name = 'na'`).n, 1);
});
});
describe('restoreDatabase: de wissel loopt stuk', () => {
it('laat de app niet zonder database achter, en verandert niets', async () => {
const bytes = await maakDatabase(join(DIR, 'nieuw-bij-stuk.sqlite'), { merk: 'nieuw' });
await rm(`${DB_FILE}.previous`, { recursive: true, force: true });
await mkdir(`${DB_FILE}.previous`, { recursive: true });
await writeFile(join(`${DB_FILE}.previous`, 'bezet.txt'), 'x');
try {
await assert.rejects(() => restoreDatabase(bytes), 'de wissel hoort te falen');
assert.equal(huidigMerk(), 'origineel', 'de oude database staat er nog en is open');
run(`INSERT INTO Label (name, searchName) VALUES ('na','na')`);
assert.equal(one(`SELECT COUNT(*) AS n FROM Label WHERE name = 'na'`).n, 1);
assert.equal(await bestaat(`${DB_FILE}.new`), false, 'het tijdelijke bestand is weg');
} finally {
await rm(`${DB_FILE}.previous`, { recursive: true, force: true });
}
});
});