lightning-agent 0.2.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);