152 lines
6.0 KiB
JavaScript
152 lines
6.0 KiB
JavaScript
// 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"']);
|
||
|
|
});
|
||
|
|
});
|