lightning-agent 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/test-v030.js ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Tests for lightning-agent v0.3.0 new features.
6
+ * Tests auth, escrow, and stream modules.
7
+ */
8
+
9
+ const {
10
+ createAuthServer, signAuth, authenticate,
11
+ createEscrowManager, EscrowState,
12
+ createStreamProvider, createStreamClient
13
+ } = require('./lib/index');
14
+
15
+ let passed = 0;
16
+ let failed = 0;
17
+
18
+ function assert(condition, msg) {
19
+ if (condition) {
20
+ passed++;
21
+ console.log(` ✅ ${msg}`);
22
+ } else {
23
+ failed++;
24
+ console.log(` ❌ ${msg}`);
25
+ }
26
+ }
27
+
28
+ // ─── Auth Tests ───
29
+
30
+ console.log('\n🔐 Auth Module');
31
+
32
+ (function testAuthServer() {
33
+ console.log('\n createAuthServer:');
34
+
35
+ const auth = createAuthServer({ challengeTtlMs: 5000 });
36
+
37
+ // Create challenge
38
+ const challenge = auth.createChallenge();
39
+ assert(challenge.k1 && challenge.k1.length === 64, 'Challenge has 64-char hex k1');
40
+ assert(challenge.expiresAt > Date.now(), 'Challenge has future expiry');
41
+ assert(auth.activeChallenges === 1, 'Active challenges count is 1');
42
+
43
+ // Verify with missing params
44
+ const bad = auth.verify(null, null, null);
45
+ assert(!bad.valid, 'Rejects null params');
46
+ assert(bad.error === 'Missing k1, sig, or key', 'Correct error message');
47
+
48
+ // Verify with wrong k1
49
+ const bad2 = auth.verify('deadbeef'.repeat(8), 'aabb', 'ccdd');
50
+ assert(!bad2.valid, 'Rejects unknown k1');
51
+
52
+ console.log(' Active challenges:', auth.activeChallenges);
53
+ })();
54
+
55
+ (function testSignAuth() {
56
+ console.log('\n signAuth + verify roundtrip:');
57
+
58
+ const crypto = require('crypto');
59
+ const privKey = crypto.randomBytes(32).toString('hex');
60
+
61
+ const auth = createAuthServer();
62
+ const challenge = auth.createChallenge();
63
+
64
+ try {
65
+ const { sig, key } = signAuth(challenge.k1, privKey);
66
+ assert(sig && sig.length > 0, 'Signature generated');
67
+ assert(key && key.length === 66, 'Compressed pubkey (33 bytes hex)');
68
+
69
+ const result = auth.verify(challenge.k1, sig, key);
70
+ assert(result.valid === true, 'Signature verified ✓');
71
+ assert(result.pubkey === key, 'Returns correct pubkey');
72
+
73
+ // Replay protection
74
+ const replay = auth.verify(challenge.k1, sig, key);
75
+ assert(!replay.valid, 'Rejects replay (challenge already used)');
76
+ } catch (err) {
77
+ assert(false, 'Sign/verify failed: ' + err.message);
78
+ }
79
+ })();
80
+
81
+ (function testAuthWithLnurl() {
82
+ console.log('\n LNURL generation:');
83
+
84
+ const auth = createAuthServer({ callbackUrl: 'https://example.com/auth' });
85
+ const challenge = auth.createChallenge();
86
+
87
+ assert(challenge.lnurl && challenge.lnurl.startsWith('lnurl'), 'LNURL generated');
88
+ assert(challenge.callbackUrl.includes('k1='), 'Callback URL has k1');
89
+ assert(challenge.callbackUrl.includes('tag=login'), 'Callback URL has tag=login');
90
+ })();
91
+
92
+ (function testAuthMiddleware() {
93
+ console.log('\n Middleware:');
94
+
95
+ const auth = createAuthServer();
96
+ const handler = auth.middleware((pubkey, req, res) => {
97
+ // onAuth callback
98
+ });
99
+ assert(typeof handler === 'function', 'Middleware returns function');
100
+ })();
101
+
102
+ // ─── Escrow Tests ───
103
+
104
+ console.log('\n\n💰 Escrow Module');
105
+
106
+ (function testEscrowStates() {
107
+ console.log('\n EscrowState:');
108
+ assert(EscrowState.CREATED === 'created', 'CREATED state');
109
+ assert(EscrowState.FUNDED === 'funded', 'FUNDED state');
110
+ assert(EscrowState.RELEASED === 'released', 'RELEASED state');
111
+ assert(EscrowState.REFUNDED === 'refunded', 'REFUNDED state');
112
+ assert(EscrowState.EXPIRED === 'expired', 'EXPIRED state');
113
+ assert(EscrowState.DISPUTED === 'disputed', 'DISPUTED state');
114
+ })();
115
+
116
+ (async function testEscrowCreate() {
117
+ console.log('\n createEscrowManager:');
118
+
119
+ // Mock wallet
120
+ const mockWallet = {
121
+ createInvoice: async (opts) => ({
122
+ invoice: 'lnbc' + opts.amountSats + 'n1mock',
123
+ paymentHash: require('crypto').randomBytes(32).toString('hex'),
124
+ amountSats: opts.amountSats
125
+ }),
126
+ waitForPayment: async () => ({ paid: true, preimage: 'abc123' }),
127
+ payInvoice: async () => ({ preimage: 'def456', paymentHash: 'hash456' }),
128
+ payAddress: async (addr, opts) => ({
129
+ preimage: 'ghi789',
130
+ paymentHash: 'hash789',
131
+ invoice: 'lnbcmock',
132
+ amountSats: opts.amountSats
133
+ })
134
+ };
135
+
136
+ const stateChanges = [];
137
+ const mgr = createEscrowManager(mockWallet, {
138
+ onStateChange: (id, from, to) => stateChanges.push({ id, from, to })
139
+ });
140
+
141
+ // Create escrow
142
+ const escrow = await mgr.create({
143
+ amountSats: 500,
144
+ workerAddress: 'worker@getalby.com',
145
+ description: 'Test work',
146
+ deadlineMs: 60000
147
+ });
148
+
149
+ assert(escrow.id && escrow.id.length === 32, 'Escrow ID generated');
150
+ assert(escrow.state === 'created', 'Initial state is created');
151
+ assert(escrow.amountSats === 500, 'Amount correct');
152
+ assert(escrow.invoice.startsWith('lnbc'), 'Invoice generated');
153
+ assert(escrow.deadline > Date.now(), 'Deadline set');
154
+
155
+ // Fund
156
+ const funded = await mgr.fund(escrow.id);
157
+ assert(funded.state === 'funded', 'State transitions to funded');
158
+ assert(funded.fundedAt > 0, 'Funded timestamp set');
159
+
160
+ // Deliver
161
+ const delivered = mgr.deliver(escrow.id, { hash: 'sha256ofwork' });
162
+ assert(delivered.state === 'delivered', 'State transitions to delivered');
163
+ assert(delivered.deliveryProof.hash === 'sha256ofwork', 'Proof stored');
164
+
165
+ // Release
166
+ const released = await mgr.release(escrow.id);
167
+ assert(released.state === 'released', 'State transitions to released');
168
+ assert(released.releasePreimage === 'ghi789', 'Release preimage stored');
169
+
170
+ // History
171
+ assert(released.history.length === 4, 'Full history (4 transitions)');
172
+ assert(stateChanges.length === 3, 'State change callbacks fired (3)');
173
+
174
+ // List
175
+ const all = mgr.list();
176
+ assert(all.length === 1, 'List returns 1 escrow');
177
+ const releasedList = mgr.list('released');
178
+ assert(releasedList.length === 1, 'Filtered list works');
179
+
180
+ mgr.close();
181
+ })().catch(e => { failed++; console.log(' ❌ Escrow test error:', e.message); });
182
+
183
+ (async function testEscrowRefund() {
184
+ console.log('\n Escrow refund flow:');
185
+
186
+ const mockWallet = {
187
+ createInvoice: async (opts) => ({
188
+ invoice: 'lnbcmock',
189
+ paymentHash: require('crypto').randomBytes(32).toString('hex')
190
+ }),
191
+ waitForPayment: async () => ({ paid: true }),
192
+ payAddress: async () => ({ preimage: 'refund123' })
193
+ };
194
+
195
+ const mgr = createEscrowManager(mockWallet);
196
+ const escrow = await mgr.create({
197
+ amountSats: 200,
198
+ workerAddress: 'worker@example.com',
199
+ deadlineMs: 60000
200
+ });
201
+
202
+ await mgr.fund(escrow.id);
203
+ const refunded = await mgr.refund(escrow.id, 'client@getalby.com', 'Worker no-show');
204
+
205
+ assert(refunded.state === 'refunded', 'Refund succeeds');
206
+ assert(refunded.metadata.refundReason === 'Worker no-show', 'Refund reason stored');
207
+
208
+ mgr.close();
209
+ })().catch(e => { failed++; console.log(' ❌ Escrow refund error:', e.message); });
210
+
211
+ (async function testEscrowDispute() {
212
+ console.log('\n Escrow dispute:');
213
+
214
+ const mockWallet = {
215
+ createInvoice: async () => ({
216
+ invoice: 'lnbcmock',
217
+ paymentHash: require('crypto').randomBytes(32).toString('hex')
218
+ }),
219
+ waitForPayment: async () => ({ paid: true })
220
+ };
221
+
222
+ const mgr = createEscrowManager(mockWallet);
223
+ const escrow = await mgr.create({
224
+ amountSats: 1000,
225
+ workerAddress: 'worker@example.com'
226
+ });
227
+
228
+ await mgr.fund(escrow.id);
229
+ const disputed = mgr.dispute(escrow.id, 'Work quality insufficient', 'client');
230
+
231
+ assert(disputed.state === 'disputed', 'Dispute state set');
232
+ assert(disputed.metadata.dispute.reason === 'Work quality insufficient', 'Dispute reason stored');
233
+ assert(disputed.metadata.dispute.raisedBy === 'client', 'Dispute raiser stored');
234
+
235
+ mgr.close();
236
+ })().catch(e => { failed++; console.log(' ❌ Escrow dispute error:', e.message); });
237
+
238
+ // ─── Stream Tests ───
239
+
240
+ console.log('\n\n⚡ Stream Module');
241
+
242
+ (function testStreamProvider() {
243
+ console.log('\n createStreamProvider:');
244
+
245
+ const mockWallet = {
246
+ createInvoice: async (opts) => ({
247
+ invoice: 'lnbc1n1mock',
248
+ paymentHash: require('crypto').randomBytes(32).toString('hex')
249
+ }),
250
+ waitForPayment: async () => ({ paid: true })
251
+ };
252
+
253
+ const provider = createStreamProvider(mockWallet, {
254
+ satsPerBatch: 2,
255
+ tokensPerBatch: 50,
256
+ maxBatches: 10
257
+ });
258
+
259
+ assert(typeof provider.handleRequest === 'function', 'Has handleRequest method');
260
+ assert(provider.activeSessions === 0, 'No active sessions initially');
261
+ })();
262
+
263
+ (function testStreamClient() {
264
+ console.log('\n createStreamClient:');
265
+
266
+ const mockWallet = {
267
+ payInvoice: async () => ({ preimage: 'mock_preimage' })
268
+ };
269
+
270
+ const client = createStreamClient(mockWallet, { maxSats: 500 });
271
+
272
+ assert(typeof client.stream === 'function', 'Has stream method');
273
+ assert(client.budget.maxSats === 500, 'Budget set correctly');
274
+ })();
275
+
276
+ // ─── Summary ───
277
+
278
+ setTimeout(() => {
279
+ console.log(`\n${'─'.repeat(40)}`);
280
+ console.log(`Results: ${passed} passed, ${failed} failed`);
281
+ if (failed > 0) process.exit(1);
282
+ }, 500);
package/test.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { createWallet, parseNwcUrl, decodeBolt11, NWCWallet } = require('./lib');
3
+ const { createWallet, parseNwcUrl, decodeBolt11, resolveLightningAddress, NWCWallet } = require('./lib');
4
4
 
5
5
  let passed = 0;
6
6
  let failed = 0;
@@ -118,6 +118,32 @@ const decoded = wallet.decodeInvoice('lnbc50u1ptest');
118
118
  assert(decoded.amountSats === 5000, 'wallet.decodeInvoice works');
119
119
  wallet.close();
120
120
 
121
+ // ─── Lightning Address tests ───
122
+ console.log('\n⚡ Lightning Address');
123
+
124
+ assert(typeof resolveLightningAddress === 'function', 'resolveLightningAddress is exported');
125
+
126
+ // Wallet has payAddress method
127
+ const walletForAddr = createWallet(testNwcUrl);
128
+ assert(typeof walletForAddr.payAddress === 'function', 'wallet has payAddress()');
129
+ walletForAddr.close();
130
+
131
+ // payAddress validation
132
+ const walletForAddrTest = createWallet(testNwcUrl);
133
+
134
+ // Test via promise catches
135
+ const addrTests = Promise.all([
136
+ walletForAddrTest.payAddress('invalid', { amountSats: 10 })
137
+ .then(() => assert(false, 'payAddress rejects invalid address'))
138
+ .catch(e => assert(e.message.includes('Invalid Lightning address'), 'payAddress rejects invalid address')),
139
+ walletForAddrTest.payAddress('user@domain.com', {})
140
+ .then(() => assert(false, 'payAddress requires amountSats'))
141
+ .catch(e => assert(e.message.includes('amountSats'), 'payAddress requires amountSats')),
142
+ walletForAddrTest.payAddress('user@domain.com', { amountSats: -5 })
143
+ .then(() => assert(false, 'payAddress rejects negative amount'))
144
+ .catch(e => assert(e.message.includes('amountSats'), 'payAddress rejects negative amount')),
145
+ ]).then(() => walletForAddrTest.close());
146
+
121
147
  // createWallet from env
122
148
  console.log('\n🌍 createWallet from env');
123
149
  process.env.NWC_URL = testNwcUrl;
@@ -128,11 +154,13 @@ delete process.env.NWC_URL;
128
154
 
129
155
  assertThrows(() => createWallet(), 'createWallet() throws without URL or env');
130
156
 
131
- // ─── Summary ───
132
- console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━`);
133
- console.log(`Results: ${passed} passed, ${failed} failed`);
134
- if (failed > 0) {
135
- process.exit(1);
136
- } else {
137
- console.log('All tests passed! ✅');
138
- }
157
+ // ─── Summary (wait for async tests) ───
158
+ addrTests.then(() => {
159
+ console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━`);
160
+ console.log(`Results: ${passed} passed, ${failed} failed`);
161
+ if (failed > 0) {
162
+ process.exit(1);
163
+ } else {
164
+ console.log('All tests passed! ✅');
165
+ }
166
+ });