diff options
Diffstat (limited to 'tools/nwc-test.js')
| -rw-r--r-- | tools/nwc-test.js | 66 |
1 files changed, 65 insertions, 1 deletions
diff --git a/tools/nwc-test.js b/tools/nwc-test.js index 848b2c6..5b0dd10 100644 --- a/tools/nwc-test.js +++ b/tools/nwc-test.js @@ -13,12 +13,13 @@ const store = {}; globalThis.localStorage = { getItem:k=>k in store?store[k]:null, setItem:(k,v)=>{store[k]=v;}, removeItem:k=>{delete store[k];} }; let subHandler = null; +const subFilters = []; // every filter subscribed, in order let priorReplies = []; let claimOk = true; const published = []; // fake relay transport: capture the subscription handler and any replies const nwcTransport = { - subscribe: (relays, filter, on) => { subHandler = on; return () => { subHandler = null; }; }, + subscribe: (relays, filter, on) => { subFilters.push(filter); subHandler = on; return () => { subHandler = null; }; }, publish: async (relays, evt) => { published.push(evt); return true; }, query: async () => priorReplies, // what other devices have answered (none, unless a test says so) errGraceMs: 20, // the sibling-first hold, shortened for the harness @@ -222,5 +223,68 @@ console.log('\n[device sync of connections]'); check('what we publish carries the connections', Array.isArray(ext.save().nwcConns)); } + +// --------------------------------------------------------------------------- +console.log('\n[period budgets + get_budget]'); +{ + const { budgetWindow, budgetRenewsAt } = await import('../src/nwc-budget.js'); + const st = JSON.parse(store['fs:nwc']); + const c = st.conns.find((x) => x.id === 'test1'); + Object.assign(c, { period: 'month', dailySat: 2000, spentDate: budgetWindow('month'), spentToday: 1990 }); + store['fs:nwc'] = JSON.stringify(st); + f.stop(); f.init(); priorReplies = []; claimOk = true; + const wasPaid = paid.length; + r = await request('pay_invoice', { invoice: 'lnbc210n1p4xuk2wpp506wkjr0xk3677nu7je9c55vq4lzlkyd0ztcq2mlvumap0zpe3alqhp5ppg7g8qwpdv34hgpymhw446y37duzwcn388yp3pw05n7tlulyn2scqzysxqrrssrzjqv3dpepm8kfdxrk3sl6wzqdf49s9c0h9ljtjrek6c08r6aejlwcnur0dwyqqvusqqqqqqqlgqqqq86qqjqsp5dc0jrq94ke2f4dzx8c2dwqsc6a65eu56dt2j599l7kxp7q2hs6zq9qxpqysgqdjeft8gkl0uga24e502pvcp5vgsfap3dxuutcpgfaj33fffuqs9psmnrklshp3fg3py7vlnzsea90vj9ahqq5t9xuy67u3pk0sfnheqpn95f2g' }); + check('21 sat over a monthly budget with 10 left → refused, naming the month', r?.error?.code === 'QUOTA_EXCEEDED' && /monthly budget \(10 sat\)/.test(r?.error?.message || ''), JSON.stringify(r?.error)); + check('nothing was paid', paid.length === wasPaid); + r = await request('get_budget', {}); + check('get_budget reports the month', r?.result?.used_budget === 1990000 && r?.result?.total_budget === 2000000 && r?.result?.renewal_period === 'monthly' && r?.result?.renews_at === budgetRenewsAt('month'), JSON.stringify(r)); + r = await request('get_info', {}); + check('get_info lists get_budget', r?.result?.methods?.includes('get_budget')); + // last month's counter is not this month's + const st2 = JSON.parse(store['fs:nwc']); + const lm = new Date(); lm.setUTCMonth(lm.getUTCMonth() - 1); + Object.assign(st2.conns.find((x) => x.id === 'test1'), { spentDate: budgetWindow('month', lm.getTime()), spentToday: 1990 }); + store['fs:nwc'] = JSON.stringify(st2); + f.stop(); f.init(); + r = await request('get_budget', {}); + check('a counter from last month reads as nothing spent', r?.result?.used_budget === 0, JSON.stringify(r?.result)); + r = await request('pay_invoice', { invoice: INV21 }); // the mock seam pays 25 + 1 fee + check('…and a payment goes through, stamped with this month', !!r?.result?.preimage && JSON.parse(store['fs:nwc']).conns.find((x) => x.id === 'test1').spentDate === budgetWindow('month') && JSON.parse(store['fs:nwc']).conns.find((x) => x.id === 'test1').spentToday === 26, JSON.stringify(r?.error || r)); +} + +console.log('\n[revoked: refused out loud, for a while]'); +{ + const st = JSON.parse(store['fs:nwc']); + const c = st.conns.find((x) => x.id === 'test1'); + c.revoked = true; c.revokedAt = Date.now(); + store['fs:nwc'] = JSON.stringify(st); + subFilters.length = 0; + f.stop(); f.init(); + check('a lately revoked connection is still listened for', subFilters.some((x) => x.kinds?.[0] === 23194 && x['#p']?.[0] === conn.servicePk)); + const wasPaid = paid.length; + r = await request('pay_invoice', { invoice: INV21 }); + check('a pay is answered UNAUTHORIZED', r?.error?.code === 'UNAUTHORIZED' && r?.result_type === 'pay_invoice', JSON.stringify(r)); + check('nothing was paid', paid.length === wasPaid); + r = await request('get_balance', {}, 'nip04'); + check('a balance ask too, in nip04', r?.error?.code === 'UNAUTHORIZED' && r?.result_type === 'get_balance', JSON.stringify(r)); + const n = published.filter((e) => e.kind === 13194 && e.pubkey === conn.servicePk).length; + f.stop(); f.init(); await new Promise((r) => setTimeout(r, 100)); + check('no capability event is published for it', published.filter((e) => e.kind === 13194 && e.pubkey === conn.servicePk).length === n); + check('the revocation date travels in the snapshot', wallet.__ext.save().nwcConns.find((x) => x.id === 'test1').revokedAt === c.revokedAt); + // 31 days on: forgotten + const st2 = JSON.parse(store['fs:nwc']); + st2.conns.find((x) => x.id === 'test1').revokedAt = Date.now() - 31 * 86_400_000; + store['fs:nwc'] = JSON.stringify(st2); + subFilters.length = 0; + f.stop(); f.init(); + check('after the grace period it is no longer listened for', !subFilters.some((x) => x.kinds?.[0] === 23194 && x['#p']?.[0] === conn.servicePk)); + // a revocation dated on two devices keeps the earlier date + const ext = wallet.__ext; + const stale = { ...JSON.parse(store['fs:nwc']).conns.find((x) => x.id === 'test1'), revokedAt: Date.now() - 40 * 86_400_000 }; + ext.load({ nwcConns: [stale] }); + check('merge keeps the earlier revocation date', JSON.parse(store['fs:nwc']).conns.find((x) => x.id === 'test1').revokedAt === stale.revokedAt); +} + console.log(ok ? '\n✅ NWC service behaves' : '\n❌ failed'); process.exit(ok?0:1); |
