150 lines
8.0 KiB
JavaScript
150 lines
8.0 KiB
JavaScript
// Generated, do not edit. sha256:52052b11142a17d483d54341c9f2ba38a354f77829e27fbdc3fddff2dd5437b6
|
|||
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
||
|
|
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 {
|
||
|
|
createEvent, eventAccountIdsByAsset, eventAssetIdsByAccount, eventDaysByAccount, eventsCountByAccount,
|
||
|
|
eventsNetByAccount, eventsWithoutImportRef, firstTaxEventDate, listEvents,
|
||
|
|
} = await import('../../server/data/events.js');
|
||
|
|
const {
|
||
|
|
bookReservation, createReservation, deleteReservation, listDueReservations, listReservations,
|
||
|
|
listReservationsForAccount, updateReservation,
|
||
|
|
} = await import('../../server/data/reservations.js');
|
||
|
|
|
||
|
|
let eur, bank, spaar;
|
||
|
|
beforeEach(() => {
|
||
|
|
harness.recreate();
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit) VALUES ('EUR','Euro','euro','fiat','EUR','cent',2,'unit')`);
|
||
|
|
eur = harness.exports.lastId();
|
||
|
|
harness.exports.run(`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES ('Bank','bank','bank',0,0,1,NULL)`);
|
||
|
|
bank = harness.exports.lastId();
|
||
|
|
harness.exports.run(`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES ('Spaar','spaar','bank',0,0,1,NULL)`);
|
||
|
|
spaar = harness.exports.lastId();
|
||
|
|
});
|
||
|
|
|
||
|
|
const rij = (id) => harness.exports.one(`SELECT * FROM Event WHERE id = ?`, [id]);
|
||
|
|
const reservering = (extra = {}) => createReservation({
|
||
|
|
accountId: bank, assetId: eur, direction: 'out', amount: 50_000,
|
||
|
|
occurredAt: '2026-12-01T00:00:00Z', note: 'aanslag', ...extra,
|
||
|
|
});
|
||
|
|
const isNietOpen = e => e.key === 'reservations.notOpen';
|
||
|
|
|
||
|
|
describe('vastleggen', () => {
|
||
|
|
it('zet een afschrijving op het van-account en een bijboeking op het naar-account', async () => {
|
||
|
|
const uit = await reservering();
|
||
|
|
const bij = await reservering({ direction: 'in', amount: 10_000 });
|
||
|
|
assert.deepEqual([rij(uit).type, rij(uit).fromAccountId, rij(uit).toAccountId], ['reservation', bank, null]);
|
||
|
|
assert.deepEqual([rij(bij).fromAccountId, rij(bij).toAccountId], [null, bank]);
|
||
|
|
assert.deepEqual(listReservationsForAccount(bank).map(r => r.id), [uit, bij]);
|
||
|
|
assert.deepEqual(listReservationsForAccount(spaar), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert een onbekende richting en een reservering zonder account', async () => {
|
||
|
|
await assert.rejects(() => reservering({ direction: 'opzij' }), /direction/);
|
||
|
|
await assert.rejects(() => reservering({ accountId: null }), /account/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert in de datalaag én in de DDL een reservering met twee accounts of met een prijs', async () => {
|
||
|
|
const basis = { assetId: eur, type: 'reservation', occurredAt: '2026-12-01T00:00:00Z', amount: 1, source: 'manual' };
|
||
|
|
await assert.rejects(() => createEvent({ ...basis, fromAccountId: bank, toAccountId: spaar }), /exactly one account/);
|
||
|
|
await assert.rejects(() => createEvent({ ...basis, fromAccountId: bank, fiatPrice: 1 }), /no price/);
|
||
|
|
assert.throws(() => harness.exports.run(
|
||
|
|
`INSERT INTO Event (assetId, fromAccountId, toAccountId, amount, type, source, occurredAt, createdAt)
|
||
|
|
VALUES (?, ?, ?, 1, 'reservation', 'manual', '2026-12-01T00:00:00Z', '2026-10-23T00:00:00Z')`,
|
||
|
|
[eur, bank, spaar]), /CHECK/);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('telt nergens mee zolang hij niet geboekt is', () => {
|
||
|
|
it('blijft buiten saldo, lijsten, importpool en eerste boeking', async () => {
|
||
|
|
await reservering({ occurredAt: '2025-01-01T00:00:00Z' });
|
||
|
|
assert.equal(eventsNetByAccount(bank, eur, null), 0);
|
||
|
|
assert.equal(eventsCountByAccount(bank, eur, null), 0);
|
||
|
|
assert.deepEqual(listEvents(), []);
|
||
|
|
assert.deepEqual(eventsWithoutImportRef(), []);
|
||
|
|
assert.deepEqual(eventAssetIdsByAccount(bank), []);
|
||
|
|
assert.deepEqual(eventAccountIdsByAsset(eur), []);
|
||
|
|
assert.deepEqual(eventDaysByAccount(bank, eur, '2024-01-01', '2026-01-01'), []);
|
||
|
|
assert.equal(firstTaxEventDate(), null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('vervallen', () => {
|
||
|
|
it('geeft alleen de reserveringen waarvan de datum bereikt is', async () => {
|
||
|
|
const vroeg = await reservering({ occurredAt: '2026-01-01T00:00:00Z' });
|
||
|
|
await reservering({ occurredAt: '2027-01-01T00:00:00Z' });
|
||
|
|
assert.deepEqual(listDueReservations('2026-06-01T00:00:00Z').map(r => r.id), [vroeg]);
|
||
|
|
assert.equal(listReservations().length, 2);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('boeken', () => {
|
||
|
|
it('maakt van dezelfde rij een verplaatsing met de echte datum en het echte bedrag', async () => {
|
||
|
|
const id = await reservering();
|
||
|
|
await bookReservation(id, { occurredAt: '2026-12-03T10:00:00Z', amount: 48_000, otherAccountId: null });
|
||
|
|
const e = rij(id);
|
||
|
|
assert.deepEqual([e.type, e.occurredAt, e.amount, e.fromAccountId, e.toAccountId, e.note],
|
||
|
|
['transfer', '2026-12-03T10:00:00Z', 48_000, bank, null, 'aanslag']);
|
||
|
|
assert.equal(eventsNetByAccount(bank, eur, null), -48_000, 'nu telt hij wel');
|
||
|
|
assert.deepEqual(listReservations(), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('zet een tegenaccount aan de andere kant, ook bij een bijboeking', async () => {
|
||
|
|
const uit = await reservering();
|
||
|
|
await bookReservation(uit, { occurredAt: '2026-12-01T00:00:00Z', amount: 50_000, otherAccountId: spaar });
|
||
|
|
assert.deepEqual([rij(uit).fromAccountId, rij(uit).toAccountId], [bank, spaar]);
|
||
|
|
const bij = await reservering({ direction: 'in' });
|
||
|
|
await bookReservation(bij, { occurredAt: '2026-12-01T00:00:00Z', amount: 50_000, otherAccountId: spaar });
|
||
|
|
assert.deepEqual([rij(bij).fromAccountId, rij(bij).toAccountId], [spaar, bank]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert een tweede keer boeken, en laat de eerste boeking staan', async () => {
|
||
|
|
const id = await reservering();
|
||
|
|
await bookReservation(id, { occurredAt: '2026-12-01T00:00:00Z', amount: 50_000 });
|
||
|
|
await assert.rejects(() => bookReservation(id, { occurredAt: '2026-12-09T00:00:00Z', amount: 1 }), isNietOpen);
|
||
|
|
assert.equal(rij(id).amount, 50_000);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert het eigen account als tegenkant, een bedrag van nul en een ongeldige datum', async () => {
|
||
|
|
const id = await reservering();
|
||
|
|
await assert.rejects(() => bookReservation(id, { occurredAt: '2026-12-01T00:00:00Z', amount: 50_000, otherAccountId: bank }), /another account/);
|
||
|
|
await assert.rejects(() => bookReservation(id, { occurredAt: '2026-12-01T00:00:00Z', amount: 0 }), /amount > 0/);
|
||
|
|
await assert.rejects(() => bookReservation(id, { occurredAt: 'morgen', amount: 1 }), /occurredAt/);
|
||
|
|
assert.equal(rij(id).type, 'reservation', 'en hij staat nog open');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('een gewone boeking is via deze module niet aan te raken', () => {
|
||
|
|
it('weigert boeken, bewerken en verwijderen van een verplaatsing', async () => {
|
||
|
|
const t = await createEvent({ assetId: eur, type: 'transfer', occurredAt: '2026-01-01T00:00:00Z', amount: 100,
|
||
|
|
fromAccountId: bank, toAccountId: spaar, source: 'manual' });
|
||
|
|
await assert.rejects(() => bookReservation(t, { occurredAt: '2026-01-02T00:00:00Z', amount: 1 }), isNietOpen);
|
||
|
|
await assert.rejects(() => updateReservation(t, { accountId: bank, assetId: eur, direction: 'out', amount: 1,
|
||
|
|
occurredAt: '2026-01-02T00:00:00Z' }), isNietOpen);
|
||
|
|
await assert.rejects(() => deleteReservation(t), isNietOpen);
|
||
|
|
assert.deepEqual([rij(t).type, rij(t).amount], ['transfer', 100]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('bewerken en verwijderen', () => {
|
||
|
|
it('bewerkt bedrag, datum, richting en omschrijving, en blijft een reservering', async () => {
|
||
|
|
const id = await reservering();
|
||
|
|
await updateReservation(id, { accountId: bank, assetId: eur, direction: 'in', amount: 7, occurredAt: '2027-02-01T00:00:00Z', note: 'teruggave' });
|
||
|
|
const e = rij(id);
|
||
|
|
assert.deepEqual([e.type, e.amount, e.occurredAt, e.fromAccountId, e.toAccountId, e.note],
|
||
|
|
['reservation', 7, '2027-02-01T00:00:00Z', null, bank, 'teruggave']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('verwijdert hem zonder boeking', async () => {
|
||
|
|
const id = await reservering();
|
||
|
|
await deleteReservation(id);
|
||
|
|
assert.ok(!rij(id));
|
||
|
|
await assert.rejects(() => deleteReservation(id), isNietOpen, 'een tweede keer is er niets meer');
|
||
|
|
});
|
||
|
|
});
|