mppx 0.3.14 → 0.3.15
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/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +26 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1478 -915
- package/dist/cli.js.map +1 -1
- package/dist/client/Mppx.d.ts +2 -0
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +2 -0
- package/dist/client/Mppx.js.map +1 -1
- package/package.json +4 -4
- package/src/bin.ts +4 -0
- package/src/cli.test.ts +180 -252
- package/src/cli.ts +1085 -485
- package/src/client/Mppx.test-d.ts +9 -0
- package/src/client/Mppx.test.ts +78 -0
- package/src/client/Mppx.ts +5 -0
package/src/cli.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
import { parseUnits } from 'viem'
|
|
4
4
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
@@ -8,83 +8,66 @@ import * as Http from '~test/Http.js'
|
|
|
8
8
|
import { rpcUrl } from '~test/tempo/prool.js'
|
|
9
9
|
import { deployEscrow } from '~test/tempo/session.js'
|
|
10
10
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
11
|
+
import cli from './cli.js'
|
|
11
12
|
import * as Store from './Store.js'
|
|
12
13
|
import * as Mppx_server from './server/Mppx.js'
|
|
13
14
|
import { toNodeListener } from './server/Mppx.js'
|
|
14
15
|
import { stripe as stripe_server } from './stripe/server/Methods.js'
|
|
15
16
|
import { tempo } from './tempo/server/Methods.js'
|
|
16
17
|
|
|
17
|
-
const cliPath = path.resolve(import.meta.dirname, 'cli.ts')
|
|
18
|
-
const cwd = path.resolve(import.meta.dirname, '..')
|
|
19
18
|
const testPrivateKey = generatePrivateKey()
|
|
20
19
|
const testAccount = privateKeyToAccount(testPrivateKey)
|
|
21
|
-
const env = { ...process.env, NODE_NO_WARNINGS: '1', MPPX_PRIVATE_KEY: testPrivateKey }
|
|
22
20
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
async function serve(argv: string[], options?: { env?: Record<string, string | undefined> }) {
|
|
22
|
+
let output = ''
|
|
23
|
+
let stderr = ''
|
|
24
|
+
let exitCode: number | undefined
|
|
25
|
+
const saved: Record<string, string | undefined> = {}
|
|
26
|
+
if (options?.env) {
|
|
27
|
+
for (const [key, value] of Object.entries(options.env)) {
|
|
28
|
+
saved[key] = process.env[key]
|
|
29
|
+
if (value === undefined) delete process.env[key]
|
|
30
|
+
else process.env[key] = value
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
let stdout = ''
|
|
58
|
-
let stderr = ''
|
|
59
|
-
child.stdout.on('data', (data: Buffer) => {
|
|
60
|
-
stdout += data.toString()
|
|
61
|
-
})
|
|
62
|
-
child.stderr.on('data', (data: Buffer) => {
|
|
63
|
-
stderr += data.toString()
|
|
33
|
+
const origStdoutWrite = process.stdout.write
|
|
34
|
+
const origStderrWrite = process.stderr.write
|
|
35
|
+
const origLog = console.log
|
|
36
|
+
const origError = console.error
|
|
37
|
+
process.stdout.write = ((chunk: unknown) => {
|
|
38
|
+
output += typeof chunk === 'string' ? chunk : String(chunk)
|
|
39
|
+
return true
|
|
40
|
+
}) as typeof process.stdout.write
|
|
41
|
+
process.stderr.write = ((chunk: unknown) => {
|
|
42
|
+
stderr += typeof chunk === 'string' ? chunk : String(chunk)
|
|
43
|
+
return true
|
|
44
|
+
}) as typeof process.stderr.write
|
|
45
|
+
console.log = (...args: unknown[]) => {
|
|
46
|
+
output += `${args.map(String).join(' ')}\n`
|
|
47
|
+
}
|
|
48
|
+
console.error = (...args: unknown[]) => {
|
|
49
|
+
stderr += `${args.map(String).join(' ')}\n`
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
await cli.serve(argv, {
|
|
53
|
+
stdout(s: string) {
|
|
54
|
+
output += s
|
|
55
|
+
},
|
|
56
|
+
exit(code: number) {
|
|
57
|
+
exitCode = code
|
|
58
|
+
},
|
|
64
59
|
})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
} finally {
|
|
61
|
+
process.stdout.write = origStdoutWrite
|
|
62
|
+
process.stderr.write = origStderrWrite
|
|
63
|
+
console.log = origLog
|
|
64
|
+
console.error = origError
|
|
65
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
66
|
+
if (value === undefined) delete process.env[key]
|
|
67
|
+
else process.env[key] = value
|
|
71
68
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
child.kill()
|
|
75
|
-
reject(new Error(`Timed out.\nstdout: ${stdout}\nstderr: ${stderr}`))
|
|
76
|
-
}, 60_000)
|
|
77
|
-
|
|
78
|
-
child.on('close', (code) => {
|
|
79
|
-
clearTimeout(timer)
|
|
80
|
-
if (code !== 0) reject(new Error(stderr.trim() || `exit code ${code}`))
|
|
81
|
-
else resolve({ stdout, stderr })
|
|
82
|
-
})
|
|
83
|
-
child.on('error', (err) => {
|
|
84
|
-
clearTimeout(timer)
|
|
85
|
-
reject(err)
|
|
86
|
-
})
|
|
87
|
-
})
|
|
69
|
+
}
|
|
70
|
+
return { output, stderr, exitCode }
|
|
88
71
|
}
|
|
89
72
|
|
|
90
73
|
describe('basic charge (examples/basic)', () => {
|
|
@@ -118,10 +101,10 @@ describe('basic charge (examples/basic)', () => {
|
|
|
118
101
|
})
|
|
119
102
|
|
|
120
103
|
try {
|
|
121
|
-
const {
|
|
122
|
-
|
|
104
|
+
const { output } = await serve([httpServer.url, '--rpc-url', rpcUrl, '-s'], {
|
|
105
|
+
env: { MPPX_PRIVATE_KEY: testPrivateKey },
|
|
123
106
|
})
|
|
124
|
-
expect(
|
|
107
|
+
expect(output).toContain('paid')
|
|
125
108
|
} finally {
|
|
126
109
|
httpServer.close()
|
|
127
110
|
}
|
|
@@ -148,12 +131,13 @@ describe('basic charge (examples/basic)', () => {
|
|
|
148
131
|
})
|
|
149
132
|
|
|
150
133
|
try {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
env: {
|
|
154
|
-
|
|
155
|
-
expect(
|
|
156
|
-
expect(
|
|
134
|
+
const { output, exitCode } = await serve(
|
|
135
|
+
[httpServer.url, '--account', 'nonexistent-account'],
|
|
136
|
+
{ env: { MPPX_PRIVATE_KEY: undefined } },
|
|
137
|
+
)
|
|
138
|
+
expect(exitCode).toBe(69)
|
|
139
|
+
expect(output).toContain('nonexistent-account')
|
|
140
|
+
expect(output).toContain('not found')
|
|
157
141
|
} finally {
|
|
158
142
|
httpServer.close()
|
|
159
143
|
}
|
|
@@ -196,87 +180,16 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
|
|
|
196
180
|
})
|
|
197
181
|
|
|
198
182
|
try {
|
|
199
|
-
const {
|
|
183
|
+
const { output } = await serve(
|
|
200
184
|
[httpServer.url, '--rpc-url', rpcUrl, '-s', '-M', 'deposit=10'],
|
|
201
|
-
{
|
|
185
|
+
{ env: { MPPX_PRIVATE_KEY: testPrivateKey } },
|
|
202
186
|
)
|
|
203
|
-
expect(
|
|
187
|
+
expect(output).toContain('scraped-content')
|
|
204
188
|
} finally {
|
|
205
189
|
httpServer.close()
|
|
206
190
|
}
|
|
207
191
|
})
|
|
208
192
|
|
|
209
|
-
test(
|
|
210
|
-
'--channel reuse: second request reuses existing channel',
|
|
211
|
-
{ timeout: 120_000 },
|
|
212
|
-
async () => {
|
|
213
|
-
await fundAccount({ address: testAccount.address, token: Addresses.pathUsd })
|
|
214
|
-
await fundAccount({ address: testAccount.address, token: asset })
|
|
215
|
-
|
|
216
|
-
const escrow = await deployEscrow()
|
|
217
|
-
const store = Store.memory()
|
|
218
|
-
const server = Mppx_server.create({
|
|
219
|
-
methods: [
|
|
220
|
-
tempo.session({
|
|
221
|
-
account: accounts[0],
|
|
222
|
-
store,
|
|
223
|
-
getClient: () => client,
|
|
224
|
-
currency: asset,
|
|
225
|
-
escrowContract: escrow,
|
|
226
|
-
chainId: client.chain.id,
|
|
227
|
-
feePayer: true,
|
|
228
|
-
}),
|
|
229
|
-
],
|
|
230
|
-
realm: 'cli-test-channel-reuse',
|
|
231
|
-
secretKey: 'cli-test-secret',
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
235
|
-
const result = await toNodeListener(
|
|
236
|
-
server.session({
|
|
237
|
-
amount: '0.001',
|
|
238
|
-
recipient: accounts[0].address,
|
|
239
|
-
unitType: 'page',
|
|
240
|
-
}),
|
|
241
|
-
)(req, res)
|
|
242
|
-
if (result.status === 402) return
|
|
243
|
-
res.end('scraped-content')
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
// First request: open a channel, answer "y" to proceed, "n" to close channel
|
|
248
|
-
const first = await runAsync(
|
|
249
|
-
[httpServer.url, '--rpc-url', rpcUrl, '--confirm', '-M', 'deposit=10'],
|
|
250
|
-
{ input: 'y\nn\n' },
|
|
251
|
-
)
|
|
252
|
-
expect(first.stdout).toContain('scraped-content')
|
|
253
|
-
|
|
254
|
-
// Extract channel ID from stderr (logged as "Channel opened 0x...")
|
|
255
|
-
const match = first.stderr.match(/Channel opened (0x[0-9a-fA-F]+)/)
|
|
256
|
-
expect(match).toBeTruthy()
|
|
257
|
-
const channelId = match![1]!
|
|
258
|
-
|
|
259
|
-
// Second request: reuse the channel via -M channel=<id>
|
|
260
|
-
const second = await runAsync(
|
|
261
|
-
[
|
|
262
|
-
httpServer.url,
|
|
263
|
-
'--rpc-url',
|
|
264
|
-
rpcUrl,
|
|
265
|
-
'-s',
|
|
266
|
-
'-M',
|
|
267
|
-
`channel=${channelId}`,
|
|
268
|
-
'-M',
|
|
269
|
-
'deposit=10',
|
|
270
|
-
],
|
|
271
|
-
{ input: '' },
|
|
272
|
-
)
|
|
273
|
-
expect(second.stdout).toContain('scraped-content')
|
|
274
|
-
} finally {
|
|
275
|
-
httpServer.close()
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
)
|
|
279
|
-
|
|
280
193
|
test('error: --fail exits on server error', { timeout: 60_000 }, async () => {
|
|
281
194
|
const httpServer = await Http.createServer(async (_req, res) => {
|
|
282
195
|
res.writeHead(500)
|
|
@@ -284,9 +197,10 @@ describe('session multi-fetch (examples/session/multi-fetch)', () => {
|
|
|
284
197
|
})
|
|
285
198
|
|
|
286
199
|
try {
|
|
287
|
-
await
|
|
288
|
-
|
|
289
|
-
)
|
|
200
|
+
const { exitCode } = await serve([httpServer.url, '--rpc-url', rpcUrl, '--fail'], {
|
|
201
|
+
env: { MPPX_PRIVATE_KEY: testPrivateKey },
|
|
202
|
+
})
|
|
203
|
+
expect(exitCode).toBe(22)
|
|
290
204
|
} finally {
|
|
291
205
|
httpServer.close()
|
|
292
206
|
}
|
|
@@ -322,18 +236,13 @@ describe.skipIf(!process.env.VITE_STRIPE_SECRET_KEY)('stripe charge (integration
|
|
|
322
236
|
})
|
|
323
237
|
|
|
324
238
|
try {
|
|
325
|
-
const {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
env: {
|
|
330
|
-
...env,
|
|
331
|
-
MPPX_STRIPE_SECRET_KEY: stripeSecretKey,
|
|
332
|
-
MPPX_PRIVATE_KEY: undefined as unknown as string,
|
|
333
|
-
},
|
|
239
|
+
const { output } = await serve([httpServer.url, '-M', 'paymentMethod=pm_card_visa', '-s'], {
|
|
240
|
+
env: {
|
|
241
|
+
MPPX_STRIPE_SECRET_KEY: stripeSecretKey,
|
|
242
|
+
MPPX_PRIVATE_KEY: undefined,
|
|
334
243
|
},
|
|
335
|
-
)
|
|
336
|
-
expect(
|
|
244
|
+
})
|
|
245
|
+
expect(output).toContain('paid')
|
|
337
246
|
} finally {
|
|
338
247
|
httpServer.close()
|
|
339
248
|
}
|
|
@@ -387,10 +296,10 @@ describe('session sse (examples/session/sse)', () => {
|
|
|
387
296
|
})
|
|
388
297
|
|
|
389
298
|
try {
|
|
390
|
-
const {
|
|
391
|
-
|
|
299
|
+
const { output } = await serve([httpServer.url, '--rpc-url', rpcUrl, '-M', 'deposit=10'], {
|
|
300
|
+
env: { MPPX_PRIVATE_KEY: testPrivateKey },
|
|
392
301
|
})
|
|
393
|
-
expect(
|
|
302
|
+
expect(output.trim()).toBe('Hello world!')
|
|
394
303
|
} finally {
|
|
395
304
|
httpServer.close()
|
|
396
305
|
}
|
|
@@ -402,9 +311,10 @@ describe('session sse (examples/session/sse)', () => {
|
|
|
402
311
|
res.end('Internal Server Error')
|
|
403
312
|
})
|
|
404
313
|
try {
|
|
405
|
-
await
|
|
406
|
-
|
|
407
|
-
)
|
|
314
|
+
const { exitCode } = await serve([httpServer.url, '--rpc-url', rpcUrl, '--fail'], {
|
|
315
|
+
env: { MPPX_PRIVATE_KEY: testPrivateKey },
|
|
316
|
+
})
|
|
317
|
+
expect(exitCode).toBe(22)
|
|
408
318
|
} finally {
|
|
409
319
|
httpServer.close()
|
|
410
320
|
}
|
|
@@ -443,16 +353,13 @@ describe('stripe charge', () => {
|
|
|
443
353
|
})
|
|
444
354
|
|
|
445
355
|
try {
|
|
446
|
-
const {
|
|
447
|
-
input: '',
|
|
356
|
+
const { output } = await serve([appServer.url, '-s', '-M', 'paymentMethod=pm_card_visa'], {
|
|
448
357
|
env: {
|
|
449
|
-
|
|
450
|
-
NODE_NO_WARNINGS: '1',
|
|
451
|
-
MPPX_STRIPE_SECRET_KEY: 'sk_test_mock',
|
|
358
|
+
MPPX_STRIPE_SECRET_KEY: 'sk_test_mock_cli_value',
|
|
452
359
|
MPPX_STRIPE_SPT_URL: sptServer.url,
|
|
453
360
|
},
|
|
454
361
|
})
|
|
455
|
-
expect(
|
|
362
|
+
expect(output).toContain('paid')
|
|
456
363
|
} finally {
|
|
457
364
|
appServer.close()
|
|
458
365
|
sptServer.close()
|
|
@@ -481,16 +388,12 @@ describe('stripe charge', () => {
|
|
|
481
388
|
})
|
|
482
389
|
|
|
483
390
|
try {
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
env: {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
},
|
|
491
|
-
}).catch((err) => err as Error)
|
|
492
|
-
expect(result).toBeInstanceOf(Error)
|
|
493
|
-
expect((result as Error).message).toContain('MPPX_STRIPE_SECRET_KEY')
|
|
391
|
+
const { output, exitCode } = await serve(
|
|
392
|
+
[appServer.url, '-s', '-M', 'paymentMethod=pm_card_visa'],
|
|
393
|
+
{ env: { MPPX_STRIPE_SECRET_KEY: '' } },
|
|
394
|
+
)
|
|
395
|
+
expect(exitCode).toBe(2)
|
|
396
|
+
expect(output).toContain('MPPX_STRIPE_SECRET_KEY')
|
|
494
397
|
} finally {
|
|
495
398
|
appServer.close()
|
|
496
399
|
}
|
|
@@ -518,16 +421,12 @@ describe('stripe charge', () => {
|
|
|
518
421
|
})
|
|
519
422
|
|
|
520
423
|
try {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
env: {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
},
|
|
528
|
-
}).catch((err) => err as Error)
|
|
529
|
-
expect(result).toBeInstanceOf(Error)
|
|
530
|
-
expect((result as Error).message).toContain('test mode')
|
|
424
|
+
const { output, exitCode } = await serve(
|
|
425
|
+
[appServer.url, '-s', '-M', 'paymentMethod=pm_card_visa'],
|
|
426
|
+
{ env: { MPPX_STRIPE_SECRET_KEY: 'sk_live_fake' } },
|
|
427
|
+
)
|
|
428
|
+
expect(exitCode).toBe(2)
|
|
429
|
+
expect(output).toContain('test mode')
|
|
531
430
|
} finally {
|
|
532
431
|
appServer.close()
|
|
533
432
|
}
|
|
@@ -539,13 +438,21 @@ describe('stripe charge', () => {
|
|
|
539
438
|
// TODO: investigate account tests timing out in CI (secret-tool/gnome-keyring hangs)
|
|
540
439
|
// ---------------------------------------------------------------------------
|
|
541
440
|
describe.skipIf(!!process.env.CI)('account', () => {
|
|
542
|
-
|
|
441
|
+
const binPath = path.resolve(import.meta.dirname, 'bin.ts')
|
|
442
|
+
const cwd = path.resolve(import.meta.dirname, '..')
|
|
543
443
|
const accountEnv = { ...process.env, NODE_NO_WARNINGS: '1' }
|
|
544
444
|
const prefix = `__mppx_test_${Date.now()}`
|
|
545
445
|
const createdAccounts: string[] = []
|
|
546
446
|
|
|
547
447
|
function accountRun(args: string[], options?: { input?: string }) {
|
|
548
|
-
|
|
448
|
+
const result = spawnSync('node', ['--import', 'tsx', binPath, ...args], {
|
|
449
|
+
encoding: 'utf8',
|
|
450
|
+
cwd,
|
|
451
|
+
timeout: 60_000,
|
|
452
|
+
...(options?.input !== undefined && { input: options.input }),
|
|
453
|
+
env: accountEnv,
|
|
454
|
+
})
|
|
455
|
+
return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', status: result.status }
|
|
549
456
|
}
|
|
550
457
|
|
|
551
458
|
function createAccount(name: string) {
|
|
@@ -577,9 +484,7 @@ describe.skipIf(!!process.env.CI)('account', () => {
|
|
|
577
484
|
test('create: duplicate name exits with message', () => {
|
|
578
485
|
const name = `${prefix}_dup`
|
|
579
486
|
createAccount(name)
|
|
580
|
-
// Second create with same name (non-interactive, stdin closed) should not succeed
|
|
581
487
|
const result = accountRun(['account', 'create', '--account', name], { input: '' })
|
|
582
|
-
// The CLI prompts for a different name; with empty stdin it exits
|
|
583
488
|
expect(result.stdout).not.toContain('saved to keychain')
|
|
584
489
|
})
|
|
585
490
|
|
|
@@ -639,11 +544,9 @@ describe.skipIf(!!process.env.CI)('account', () => {
|
|
|
639
544
|
const result = deleteAccount(name)
|
|
640
545
|
expect(result.status).toBe(0)
|
|
641
546
|
expect(result.stdout).toContain(`Account "${name}" deleted`)
|
|
642
|
-
// Remove from cleanup list since already deleted
|
|
643
547
|
const idx = createdAccounts.indexOf(name)
|
|
644
548
|
if (idx !== -1) createdAccounts.splice(idx, 1)
|
|
645
549
|
|
|
646
|
-
// Verify it's gone
|
|
647
550
|
const view = accountRun(['account', 'view', '--account', name])
|
|
648
551
|
expect(view.status).not.toBe(0)
|
|
649
552
|
})
|
|
@@ -664,7 +567,6 @@ describe.skipIf(!!process.env.CI)('account', () => {
|
|
|
664
567
|
test('unknown action exits non-zero', () => {
|
|
665
568
|
const result = accountRun(['account', 'bogus'])
|
|
666
569
|
expect(result.status).not.toBe(0)
|
|
667
|
-
expect(result.stderr).toContain('Unknown action: bogus')
|
|
668
570
|
})
|
|
669
571
|
|
|
670
572
|
// --- no action ---
|
|
@@ -672,57 +574,83 @@ describe.skipIf(!!process.env.CI)('account', () => {
|
|
|
672
574
|
test('no action prints help', () => {
|
|
673
575
|
const result = accountRun(['account'])
|
|
674
576
|
expect(result.status).toBe(0)
|
|
675
|
-
expect(result.stdout).toContain('account
|
|
577
|
+
expect(result.stdout).toContain('account')
|
|
676
578
|
})
|
|
677
579
|
})
|
|
678
580
|
|
|
679
|
-
test('mppx --help', () => {
|
|
680
|
-
const {
|
|
681
|
-
|
|
682
|
-
expect(
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
581
|
+
test('mppx --help', async () => {
|
|
582
|
+
const { output } = await serve(['--help'])
|
|
583
|
+
expect(output).toContain('mppx')
|
|
584
|
+
expect(output).toContain('<url>')
|
|
585
|
+
expect(output).toContain('account')
|
|
586
|
+
expect(output).toContain('sign')
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
// ---------------------------------------------------------------------------
|
|
590
|
+
// sign
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
describe('sign', () => {
|
|
593
|
+
const validChallenge =
|
|
594
|
+
'Payment id="test", realm="test", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxMDAwIiwiY3VycmVuY3kiOiIweDIwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEiLCJyZWNpcGllbnQiOiIweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEiLCJtZXRob2REZXRhaWxzIjp7ImNoYWluSWQiOjEzMzd9fQ"'
|
|
595
|
+
|
|
596
|
+
test('--dry-run: validates a valid challenge', async () => {
|
|
597
|
+
const { exitCode, stderr } = await serve(['sign', '--dry-run', '--challenge', validChallenge])
|
|
598
|
+
expect(exitCode).toBeUndefined()
|
|
599
|
+
expect(stderr).toContain('Challenge is valid')
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
test('--dry-run: rejects an invalid challenge', async () => {
|
|
603
|
+
const { exitCode, output } = await serve([
|
|
604
|
+
'sign',
|
|
605
|
+
'--dry-run',
|
|
606
|
+
'--challenge',
|
|
607
|
+
'not a valid challenge',
|
|
608
|
+
])
|
|
609
|
+
expect(exitCode).toBe(2)
|
|
610
|
+
expect(output).toContain('INVALID_CHALLENGE')
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
test('error: no challenge provided', async () => {
|
|
614
|
+
const { exitCode, output } = await serve(['sign'])
|
|
615
|
+
expect(exitCode).toBe(2)
|
|
616
|
+
expect(output).toContain('No challenge provided')
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
test('error: unsupported method', async () => {
|
|
620
|
+
const challenge = 'Payment id="x", realm="x", method="unknown", intent="charge", request="e30"'
|
|
621
|
+
const { exitCode, output } = await serve(['sign', '--challenge', challenge])
|
|
622
|
+
expect(exitCode).toBe(2)
|
|
623
|
+
expect(output).toContain('Unsupported payment method')
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
test('error: no account for tempo', async () => {
|
|
627
|
+
const { exitCode, output } = await serve(
|
|
628
|
+
['sign', '--challenge', validChallenge, '--account', 'nonexistent-sign-test'],
|
|
629
|
+
{ env: { MPPX_PRIVATE_KEY: undefined } },
|
|
630
|
+
)
|
|
631
|
+
expect(exitCode).toBe(69)
|
|
632
|
+
expect(output).toContain('not found')
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
test('happy path: signs a tempo charge challenge', { timeout: 120_000 }, async () => {
|
|
636
|
+
const { output, stderr, exitCode } = await serve(
|
|
637
|
+
['sign', '--challenge', validChallenge, '--rpc-url', rpcUrl],
|
|
638
|
+
{ env: { MPPX_PRIVATE_KEY: testPrivateKey } },
|
|
639
|
+
)
|
|
640
|
+
if (exitCode) console.info('SIGN DEBUG output:', output, 'stderr:', stderr)
|
|
641
|
+
expect(exitCode).toBeUndefined()
|
|
642
|
+
expect(output.trim()).toMatch(/^Payment\s+\S+/)
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
test('happy path: --json outputs authorization and from', { timeout: 120_000 }, async () => {
|
|
646
|
+
const { output, stderr, exitCode } = await serve(
|
|
647
|
+
['sign', '--challenge', validChallenge, '--rpc-url', rpcUrl, '--json'],
|
|
648
|
+
{ env: { MPPX_PRIVATE_KEY: testPrivateKey } },
|
|
649
|
+
)
|
|
650
|
+
if (exitCode) console.info('SIGN JSON DEBUG output:', output, 'stderr:', stderr)
|
|
651
|
+
expect(exitCode).toBeUndefined()
|
|
652
|
+
const parsed = JSON.parse(output.trim())
|
|
653
|
+
expect(parsed.authorization).toMatch(/^Payment\s+\S+/)
|
|
654
|
+
expect(parsed.from).toMatch(/^0x[0-9a-fA-F]{40}$/)
|
|
655
|
+
})
|
|
728
656
|
})
|