// 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"']); }); });