2026-10-30 12:28:48 +01:00
|
|
|
// Generated, do not edit. sha256:13b6b32cbbf6b8c4a6a6069af37a7be33560a8abfc2af81ae2b4009e9692d7c6
|
2026-10-29 11:57:29 +01:00
|
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
|
|
|
|
import { createNwcApiFake } from '../helpers/nwc-api.mjs';
|
|
|
|
|
|
|
|
|
|
const harness = createDbHarness({ seedPreferences: true });
|
|
|
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
|
|
|
|
createNwcApiFake().registreer();
|
|
|
|
|
|
|
|
|
|
let stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
const echt = await import('../../server/license.mjs');
|
|
|
|
|
mock.module('../../server/license.mjs', {
|
|
|
|
|
namedExports: {
|
|
|
|
|
...echt,
|
|
|
|
|
licenseState: () => stand,
|
|
|
|
|
capacityReached: (soort, huidig) => echt.capacityReached(soort, huidig, stand),
|
|
|
|
|
hasFeature: (naam) => echt.hasFeature(naam, stand),
|
|
|
|
|
allowedAssets: () => echt.allowedAssets(stand),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { createAsset, updateAsset, deleteAsset, listAssets } = await import('../../server/data/assets.js');
|
|
|
|
|
const { createAccount, listAccounts } = await import('../../server/data/accounts.js');
|
2026-10-30 12:28:48 +01:00
|
|
|
const { mergeAccounts } = await import('../../server/data/accounts-merge.js');
|
2026-10-29 11:57:29 +01:00
|
|
|
const { createValuation, listValuations } = await import('../../server/data/valuations.js');
|
|
|
|
|
const { saveConnection, listConnections } = await import('../../server/data/connections.js');
|
|
|
|
|
const { setPriceSourceRank } = await import('../../server/data/price-source.js');
|
|
|
|
|
const { createWalletSource, updateWalletSource, listWalletSources } = await import('../../server/data/wallet-sources.js');
|
|
|
|
|
|
|
|
|
|
const EEN = {
|
|
|
|
|
symbol: 'BTC', name: 'Bitcoin', kind: 'crypto', unit: 'BTC', subunit: 'sat',
|
|
|
|
|
decimals: 8, displayUnit: 'unit', taxCategory: null,
|
|
|
|
|
};
|
|
|
|
|
const TWEE = { ...EEN, symbol: 'XAU', name: 'Goud', kind: 'metal', unit: 'g', subunit: 'mg', decimals: 3 };
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
harness.recreate();
|
|
|
|
|
stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('de gratis stand op fiatrekeningen', () => {
|
|
|
|
|
const bank = (name) => ({ name, kind: 'bank', isOwnCustody: false, scanEnabled: false, taxCategory: null });
|
|
|
|
|
|
|
|
|
|
it('laat de EERSTE bankrekening toe en weigert de tweede', async () => {
|
|
|
|
|
await createAccount(bank('ING'));
|
|
|
|
|
await assert.rejects(() => createAccount(bank('Rabo')), (fout) => {
|
|
|
|
|
assert.equal(fout.key, 'accounts.maxFreeBank');
|
|
|
|
|
assert.equal(fout.params.max, 1);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
assert.equal(listAccounts().length, 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('TELT ALLEEN kind bank, dus een exchange of wallet valt erbuiten', async () => {
|
|
|
|
|
await createAccount(bank('ING'));
|
|
|
|
|
await createAccount({ ...bank('Kraken'), kind: 'exchange' });
|
|
|
|
|
await createAccount({ ...bank('Ledger'), kind: 'wallet' });
|
|
|
|
|
assert.equal(listAccounts().length, 3);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('laat de tweede bankrekening WEL toe in de betaalde stand', async () => {
|
|
|
|
|
await createAccount(bank('ING'));
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
await createAccount(bank('Rabo'));
|
|
|
|
|
assert.equal(listAccounts().length, 2);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('de gratis stand op assets', () => {
|
|
|
|
|
it('TELT DE BASISVALUTA NIET MEE, anders is de gratis stand onbruikbaar', async () => {
|
|
|
|
|
await createAsset({ ...EEN, symbol: 'EUR', name: 'Euro', kind: 'fiat', unit: 'EUR', subunit: 'cent', decimals: 2 });
|
|
|
|
|
const id = await createAsset(EEN);
|
|
|
|
|
assert.ok(id > 0);
|
|
|
|
|
assert.equal(listAssets().length, 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('telt een munt die er alleen voor de KOERS is niet mee, en een munt in gebruik wel', async () => {
|
|
|
|
|
const DOLLAR = { ...EEN, symbol: 'USD', name: 'Dollar', kind: 'fiat', unit: 'USD', subunit: 'cent', decimals: 2 };
|
|
|
|
|
await createAsset(DOLLAR);
|
|
|
|
|
await createAsset(EEN);
|
|
|
|
|
assert.equal(listAssets().length, 2, 'bitcoin mag naast een ongebruikte dollar');
|
|
|
|
|
|
|
|
|
|
harness.recreate();
|
|
|
|
|
const usd = await createAsset(DOLLAR);
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
await createValuation({ assetId: usd, valuedOn: '2026-01-01', accountId: null, balance: 100, fiatRate: 0.9,
|
|
|
|
|
rateAt: '2026-01-01T00:00:00Z', source: 'manual' });
|
|
|
|
|
stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
await assert.rejects(() => createAsset(EEN), (f) => f.key === 'assets.maxFree',
|
|
|
|
|
'met een balans in dollars is de dollar het ene asset van de gratis stand');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('laat de EERSTE asset gewoon toe', async () => {
|
|
|
|
|
const id = await createAsset(EEN);
|
|
|
|
|
assert.ok(id > 0);
|
|
|
|
|
assert.equal(listAssets().length, 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('weigert de TWEEDE, met een vertaalbare sleutel en niet met een SQL-fout', async () => {
|
|
|
|
|
await createAsset(EEN);
|
|
|
|
|
const POND = { ...EEN, symbol: 'GBP', name: 'Pond', kind: 'fiat', unit: 'GBP', subunit: 'pence', decimals: 2 };
|
|
|
|
|
await assert.rejects(() => createAsset(POND), (fout) => {
|
|
|
|
|
assert.equal(fout.key, 'assets.maxFree');
|
|
|
|
|
assert.equal(fout.params.max, 1);
|
|
|
|
|
assert.match(fout.message, /free version/i);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
assert.equal(listAssets().length, 1, 'er mag niets half zijn aangemaakt');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('ALLEEN BITCOIN: een ander asset wordt geweigerd, ook als het het eerste is', async () => {
|
|
|
|
|
await assert.rejects(() => createAsset(TWEE), (fout) => {
|
|
|
|
|
assert.equal(fout.key, 'assets.freeOnly');
|
|
|
|
|
assert.equal(fout.params.list, 'BTC');
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
assert.equal(listAssets().length, 0, 'er mag niets half zijn aangemaakt');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('laat een munt toe, want die valt onder de telling en niet onder de lijst', async () => {
|
|
|
|
|
const id = await createAsset({ ...EEN, symbol: 'GBP', name: 'Pond', kind: 'fiat', unit: 'GBP', subunit: 'pence', decimals: 2 });
|
|
|
|
|
assert.ok(id > 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('laat de tweede WEL toe in de betaalde stand', async () => {
|
|
|
|
|
await createAsset(EEN);
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
await createAsset(TWEE);
|
|
|
|
|
assert.equal(listAssets().length, 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('NEEMT NIETS AF: wijzigen en verwijderen blijven werken boven de grens', async () => {
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
const a = await createAsset(EEN);
|
|
|
|
|
const b = await createAsset(TWEE);
|
|
|
|
|
|
|
|
|
|
stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
await updateAsset(a, { ...EEN, name: 'Bitcoin hernoemd' });
|
|
|
|
|
assert.equal(listAssets().find((x) => x.id === a).name, 'Bitcoin hernoemd');
|
|
|
|
|
await deleteAsset(b);
|
|
|
|
|
assert.equal(listAssets().length, 1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('de gratis stand op balansen', () => {
|
|
|
|
|
const balans = (assetId, dag) => ({
|
|
|
|
|
assetId, valuedOn: dag, accountId: null, balance: 100, fiatRate: 50000,
|
|
|
|
|
rateAt: `${dag}T00:00:00Z`, source: 'manual',
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-30 12:28:48 +01:00
|
|
|
it('kent geen grens: ook de gratis stand legt balans na balans vast', async () => {
|
2026-10-29 11:57:29 +01:00
|
|
|
const asset = await createAsset(EEN);
|
2026-10-30 12:28:48 +01:00
|
|
|
for (const dag of ['2026-01-01', '2026-02-01', '2026-03-01']) await createValuation(balans(asset, dag));
|
|
|
|
|
assert.equal(listValuations().length, 3);
|
2026-10-29 11:57:29 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('de features van de gratis stand', () => {
|
|
|
|
|
it('weigert een lightning-verbinding en een eigen explorer', async () => {
|
|
|
|
|
await assert.rejects(() => saveConnection({ kind: 'lnd', name: 'Node', url: 'https://x' }),
|
|
|
|
|
(f) => f.key === 'connections.lightningPaid');
|
|
|
|
|
await assert.rejects(() => saveConnection({ kind: 'nwc', name: 'Wallet', url: 'nostr+walletconnect://x' }),
|
|
|
|
|
(f) => f.key === 'connections.lightningPaid');
|
|
|
|
|
await assert.rejects(() => saveConnection({ kind: 'btc_explorer', name: 'Eigen node', url: 'https://x' }),
|
|
|
|
|
(f) => f.key === 'connections.ownExplorerPaid');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('LEEST verbindingen gewoon, want de grens zit op opslaan en niet op kijken', async () => {
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
await saveConnection({ kind: 'btc_explorer', name: 'Eigen node', url: 'https://x' });
|
|
|
|
|
stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
assert.equal(listConnections('btc_explorer').length, 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('laat die verbindingen WEL toe in de betaalde stand', async () => {
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
const id = await saveConnection({ kind: 'lnd', name: 'Node', url: 'https://x' });
|
|
|
|
|
assert.ok(id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('weigert een NWC- of LND-bron bij een account, ook via wijzigen, maar laat een bestaande staan', async () => {
|
|
|
|
|
const accountId = await createAccount({ name: 'Wallet', kind: 'wallet', isOwnCustody: true, scanEnabled: false, taxCategory: null });
|
|
|
|
|
await assert.rejects(() => createWalletSource({ accountId, kind: 'nwc', value: 'ab'.repeat(32), name: 'W', secret: 'nostr+walletconnect://x' }),
|
|
|
|
|
(f) => f.key === 'connections.lightningPaid');
|
|
|
|
|
await assert.rejects(() => createWalletSource({ accountId, kind: 'lnd', value: '', name: 'N', connectionId: 1 }),
|
|
|
|
|
(f) => f.key === 'connections.lightningPaid');
|
|
|
|
|
const adres = await createWalletSource({ accountId, kind: 'address', value: 'bc1qtest', name: 'A' });
|
|
|
|
|
await assert.rejects(() => updateWalletSource(adres, { kind: 'nwc', value: 'ab'.repeat(32), name: 'A', secret: 'nostr+walletconnect://x' }),
|
|
|
|
|
(f) => f.key === 'connections.lightningPaid');
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
const bron = await createWalletSource({ accountId, kind: 'nwc', value: 'cd'.repeat(32), name: 'W', secret: 'nostr+walletconnect://y' });
|
|
|
|
|
stand = { level: 'free', enforced: true, why: 'none' };
|
|
|
|
|
await updateWalletSource(bron, { kind: 'nwc', value: 'cd'.repeat(32), name: 'Nieuwe naam' });
|
|
|
|
|
assert.equal(listWalletSources(accountId).find(s => s.id === bron).name, 'Nieuwe naam');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('weigert het wijzigen van de bronkeuze-matrix', async () => {
|
|
|
|
|
const asset = await createAsset(EEN);
|
|
|
|
|
await assert.rejects(() => setPriceSourceRank(asset, 'spot', 1),
|
|
|
|
|
(f) => f.key === 'integrations.matrixPaid');
|
|
|
|
|
});
|
2026-10-30 12:28:48 +01:00
|
|
|
|
|
|
|
|
it('weigert accounts samenvoegen en laat beide accounts staan; betaald gaat het door', async () => {
|
|
|
|
|
const wallet = (name) => ({ name, kind: 'wallet', isOwnCustody: true, scanEnabled: false, taxCategory: null });
|
|
|
|
|
const a = await createAccount(wallet('A'));
|
|
|
|
|
const b = await createAccount(wallet('B'));
|
|
|
|
|
await assert.rejects(() => mergeAccounts(a, b), (f) => f.key === 'accounts.mergePaid');
|
|
|
|
|
assert.equal(listAccounts().length, 2);
|
|
|
|
|
stand = { level: 'full', enforced: true, why: 'licensed' };
|
|
|
|
|
await mergeAccounts(a, b);
|
|
|
|
|
assert.deepEqual(listAccounts().map(r => r.id), [a]);
|
|
|
|
|
});
|
2026-10-29 11:57:29 +01:00
|
|
|
});
|