Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
// Generated, do not edit. sha256:d777a6778dbae1d1860839c9462d7305d69ddab9f3ae6a43a005933e2e4c226c
import { describe, it, beforeEach, mock } from 'node:test';
import { toSearchName } from '../../shared/search-name.js';
import assert from 'node:assert/strict';
import { createDbHarness } from '../helpers/db-node.mjs';
const harness = createDbHarness();
mock.module('../../server/db.js', { namedExports: harness.exports });
const { createLabel, deleteLabel, listLabels, labelUsage } = await import('../../server/data/labels.js');
beforeEach(() => { harness.recreate(); });
function account(naam = 'Ledger') {
harness.exports.run(
`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES (?,?,'wallet',1,1,1,NULL)`, [naam, toSearchName(naam)]);
return harness.exports.lastId();
}
function beweging({ labelId = null, txid = 'tx1' } = {}) {
harness.exports.run(
`INSERT INTO WalletTransaction (accountId, layer, txid, direction, valueSats, labelId, createdAt)
VALUES (?, 'onchain', ?, 'in', 100000, ?, '2024-01-01T00:00:00')`,
[account(`acc-${txid}`), txid, labelId]);
return harness.exports.lastId();
}
describe('labels: CRUD', () => {
it('createLabel geeft het nieuwe id terug', async () => {
const id = await createLabel({ name: 'Sparen' });
assert.equal(typeof id, 'number');
assert.deepEqual(listLabels().map(l => l.name), ['Sparen']);
});
it('listLabels sorteert op naam, niet op invoervolgorde', async () => {
await createLabel({ name: 'Zakgeld' });
await createLabel({ name: 'Aflossing' });
await createLabel({ name: 'Middelen' });
assert.deepEqual(listLabels().map(l => l.name), ['Aflossing', 'Middelen', 'Zakgeld']);
});
it('deleteLabel verwijdert een ongebruikt label', async () => {
const id = await createLabel({ name: 'Tijdelijk' });
await deleteLabel(id);
assert.deepEqual(listLabels(), []);
});
});
describe('labels: deleteLabel blokkeert bij gebruik', () => {
it('weigert een label dat een beweging draagt, en noemt het aantal', async () => {
const id = await createLabel({ name: 'In gebruik' });
beweging({ labelId: id, txid: 'tx-a' });
beweging({ labelId: id, txid: 'tx-b' });
await assert.rejects(() => deleteLabel(id), (e) => e.key === 'labels.inUse' && e.params.n === 2);
});
it('laat het label én de koppeling intact na een geweigerde delete', async () => {
const id = await createLabel({ name: 'Blijft' });
const txId = beweging({ labelId: id, txid: 'tx-c' });
await assert.rejects(() => deleteLabel(id));
assert.deepEqual(listLabels().map(l => l.name), ['Blijft']);
const tx = harness.exports.one(`SELECT labelId FROM WalletTransaction WHERE id = ?`, [txId]);
assert.equal(tx.labelId, id);
});
it('laat een label wél weg als een ánder label in gebruik is', async () => {
const bezet = await createLabel({ name: 'Bezet' });
const vrij = await createLabel({ name: 'Vrij' });
beweging({ labelId: bezet, txid: 'tx-d' });
await deleteLabel(vrij);
assert.deepEqual(listLabels().map(l => l.name), ['Bezet']);
});
it('negeert bewegingen zonder label', async () => {
const id = await createLabel({ name: 'Los' });
beweging({ labelId: null, txid: 'tx-e' });
await deleteLabel(id);
assert.deepEqual(listLabels(), []);
});
});
describe('labelUsage: de telling die een handle betekenis geeft', () => {
it('geeft een rij per label, ook voor een label dat nergens gebruikt wordt', async () => {
const gebruikt = await createLabel({ name: 'Werk' });
const ongebruikt = await createLabel({ name: 'Ligt stil' });
beweging({ labelId: gebruikt, txid: 'tx-a' });
const rijen = labelUsage();
assert.equal(rijen.length, 2, 'een label zonder bewegingen hoort er ook in te staan');
const perId = new Map(rijen.map(r => [r.labelId, r.walletTransactions]));
assert.equal(perId.get(gebruikt), 1);
assert.equal(perId.get(ongebruikt), 0);
});
it('telt alleen de bewegingen van dat label', async () => {
const a = await createLabel({ name: 'Aaa' });
const b = await createLabel({ name: 'Bbb' });
beweging({ labelId: a, txid: 'tx-1' });
beweging({ labelId: a, txid: 'tx-2' });
beweging({ labelId: b, txid: 'tx-3' });
beweging({ labelId: null, txid: 'tx-4' });
const perId = new Map(labelUsage().map(r => [r.labelId, r.walletTransactions]));
assert.equal(perId.get(a), 2);
assert.equal(perId.get(b), 1);
});
it('geeft een lege lijst als er geen labels zijn', () => {
assert.deepEqual(labelUsage(), []);
});
it('levert gewone getallen en geen Map, want dit gaat ook over de data-API', () => {
assert.ok(Array.isArray(labelUsage()));
});
});
describe('randwaardes: namen die leeg of vreemd zijn', () => {
it('weigert een lege en een witruimte-naam met een leesbare fout', async () => {
await assert.rejects(() => createLabel({ name: '' }), /Labelnaam may not be empty/);
await assert.rejects(() => createLabel({ name: ' ' }), /Labelnaam may not be empty/);
await assert.rejects(() => createLabel({ name: null }), /Labelnaam may not be empty/);
assert.deepEqual(listLabels(), [], 'en er is niets aangemaakt');
});
it('noemt searchName niet meer, de kolom die de gebruiker niet kent', async () => {
await assert.rejects(() => createLabel({ name: ' ' }),
(e) => !/searchName/.test(e.message) && /Labelnaam/.test(e.message));
});
it('slaat de naam getrimd op, want een spatie erachter is dezelfde valkuil', async () => {
await createLabel({ name: ' Werk ' });
assert.deepEqual(listLabels().map(l => l.name), ['Werk']);
});
it('laat een naam van duizend tekens door', async () => {
const lang = 'y'.repeat(1000);
await createLabel({ name: lang });
assert.equal(listLabels()[0].name.length, 1000);
});
it('laat een emoji en een aanhalingsteken door, en dat hóórt', async () => {
await createLabel({ name: 'Werk 🏦' });
await createLabel({ name: 'Zogenaamd "spaargeld"' });
assert.deepEqual(listLabels().map(l => l.name).sort(), ['Werk 🏦', 'Zogenaamd "spaargeld"']);
});
});