Skip to content

Commit a6f7ade

Browse files
author
fiatjaf
committed
lnurlpay tests and some random fixes.
1 parent a5411e6 commit a6f7ade

File tree

9 files changed

+223
-25
lines changed

9 files changed

+223
-25
lines changed

.mocharc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
require:
22
- '@babel/polyfill'
33
- '@babel/register'
4-
timeout: 5000
4+
timeout: 250000

migrations/20201101170804_lnurlpay.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
exports.up = async db => {
22
await db.schema.createTable('lnurlpay_endpoint', t => {
3-
t.string('id').primary()
4-
t.string('metadata').notNullable().defaultTo('{}')
5-
t.integer('min').notNullable()
6-
t.integer('max').notNullable()
7-
t.string('currency').nullable()
8-
t.string('text').notNullable()
9-
t.string('image').nullable()
10-
t.string('other_metadata').nullable()
11-
t.string('success_text').nullable()
12-
t.string('success_url').nullable()
3+
t.string ('id').primary()
4+
t.string ('metadata').notNullable().defaultTo('{}')
5+
t.string ('min').notNullable()
6+
t.string ('max').notNullable()
7+
t.string ('currency').nullable()
8+
t.string ('text').notNullable()
9+
t.string ('image').nullable()
10+
t.string ('other_metadata').nullable()
11+
t.string ('success_text').nullable()
12+
t.string ('success_url').nullable()
1313
t.integer('comment_length').notNullable().defaultTo(0)
14-
t.string('webhook').nullable()
14+
t.string ('webhook').nullable()
1515
})
1616
await db.schema.table('invoice', t => {
1717
t.string('lnurlpay_endpoint').nullable()

src/lnurl.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { toMsat } from './lib/exchange-rate'
44

55
const debug = require('debug')('lightning-charge')
66

7-
module.exports = (app, payListen, model, auth, ln) => async {
7+
module.exports = async (app, payListen, model, auth, ln) => {
88
// check if method invoicewithdescriptionhash exists
99
let help = await ln.help()
1010
let foundCommand
@@ -70,13 +70,16 @@ module.exports = (app, payListen, model, auth, ln) => async {
7070
const min = currency ? await toMsat(currency, endpoint.min) : endpoint.min
7171
const max = currency ? await toMsat(currency, endpoint.max) : endpoint.max
7272

73+
let qs = new URLSearchParams(req.query).toString()
74+
if (qs.length) qs = '?' + qs
75+
7376
res.status(200).send({
7477
tag: 'payRequest'
7578
, minSendable: min
7679
, maxSendable: max
7780
, metadata: makeMetadata(endpoint)
7881
, commentAllowed: endpoint.comment_length
79-
, callback: `https://${req.hostname}/lnurl/${lnurlpay.id}/callback`
82+
, callback: `https://${req.hostname}/lnurl/${lnurlpay.id}/callback${qs}`
8083
})
8184
}))
8285

src/model.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,15 @@ module.exports = (db, ln) => {
8383
}
8484

8585
if (props.amount) {
86-
lnurlpay.min = props.amount
87-
lnurlpay.max = props.amount
86+
lnurlpay.min = ''+props.amount
87+
lnurlpay.max = ''+props.amount
8888
} else if (props.min <= props.max) {
89-
lnurlpay.min = props.min
90-
lnurlpay.max = props.max
89+
lnurlpay.min = ''+props.min
90+
lnurlpay.max = ''+props.max
9191
} else if (props.min > props.max) {
9292
// silently correct a user error
93-
lnurlpay.min = props.max
94-
lnurlpay.max = props.min
93+
lnurlpay.min = ''+props.max
94+
lnurlpay.max = ''+props.min
9595
}
9696

9797
if (lnurlpay.min && !lnurlpay.max)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#! /bin/sh
2+
3+
# Eg. {"jsonrpc":"2.0","id":2,"method":"getmanifest","params":{}}\n\n
4+
read -r JSON
5+
read -r _
6+
id=$(echo "$JSON" | jq -r '.id')
7+
8+
echo '{"jsonrpc":"2.0","id":'"$id"',"result":{"dynamic":true,"options":[],"rpcmethods":[{"name":"invoicewithdescriptionhash","usage":"<sat> <label><description_hash>","description":"this is just a worthless broken method for the tests!"}]}}'
9+
10+
# Eg. {"jsonrpc":"2.0","id":5,"method":"init","params":{"options":{},"configuration":{"lightning-dir":"/home/rusty/.lightning","rpc-file":"lightning-rpc","startup":false}}}\n\n
11+
read -r JSON
12+
read -r _
13+
id=$(echo "$JSON" | jq -r '.id')
14+
rpc=$(echo "$JSON" | jq -r '.params.configuration | .["lightning-dir"] + "/" + .["rpc-file"]')
15+
16+
echo '{"jsonrpc":"2.0","id":'"$id"',"result":{}}'
17+
18+
# eg. { "jsonrpc" : "2.0", "method" : "invoicewithdescriptionhash 10sat label descriptionhash", "id" : 6, "params" :[ "hello"] }
19+
while read -r JSON; do
20+
read -r _
21+
echo "$JSON" | sed 's/invoicewithdescriptionhash/invoice/' | nc -U $rpc -W 1
22+
done

test/lnurl.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { toMsat } from '../src/lib/exchange-rate'
2+
3+
const { ok, equal: eq } = require('assert')
4+
5+
describe('Lnurlpay API', function() {
6+
let charge, lnBob
7+
before(() => {
8+
charge = require('supertest')(process.env.CHARGE_URL)
9+
lnBob = require('clightning-client')(process.env.LN_BOB_PATH)
10+
})
11+
12+
const mkLnurlPay = (other = {}) =>
13+
charge.post('/lnurlpay')
14+
.send({
15+
id: 'test'
16+
, min: 100000
17+
, max: 200000
18+
, metadata: {a: 1}
19+
, text: 'test lnurlpay'
20+
, other_metadata: [['nothing', 'nothing']]
21+
, success_text: 'thank you'
22+
, success_url: 'https://charge.example.com/success'
23+
, comment_length: 100
24+
, ...other
25+
})
26+
27+
describe('basic lnurlpay endpoint management', () => {
28+
it('can create an lnurlpay endpoint', () =>
29+
mkLnurlPay()
30+
.expect(201)
31+
.expect('Content-Type', /json/)
32+
.expect(({ body }) => {
33+
ok(body.id && body.comment_length && body.text)
34+
eq(body.min, '100000')
35+
eq(body.max, '200000')
36+
})
37+
)
38+
39+
it('can fetch endpoint', async () => {
40+
await mkLnurlPay({ webhook: 'https://x.example.com/wh' })
41+
charge.get('/lnurlpay/test')
42+
.expect(200)
43+
.expect('Content-Type', /json/)
44+
.expect(({ body }) => {
45+
eq(body.webhook, 'https://x.example.com/wh')
46+
eq(body.metadata, {a: 1})
47+
})
48+
})
49+
50+
it('can list endpoints', async () => {
51+
await mkLnurlPay({ id: 'test1', amount: 30000 })
52+
await mkLnurlPay({ id: 'test2', text: 'abc' })
53+
await mkLnurlPay({ id: 'test2', text: 'xyz' })
54+
charge.get('/lnurlpays')
55+
.expect(200)
56+
.expect('Content-Type', /json/)
57+
.expect(({ body }) => {
58+
eq(body.length, 2)
59+
eq(body.find(({ id }) => id === 'test1').min, '30000')
60+
eq(body.find(({ id }) => id === 'test1').max, '30000')
61+
eq(body.find(({ id }) => id === 'test2').text, 'xyz')
62+
})
63+
})
64+
65+
it('can update endpoints', async () => {
66+
await mkLnurlPay({ id: 'test1' })
67+
await mkLnurlPay({ id: 'test2', text: 'abc', metadata: {a: 3} })
68+
await charge.put('/lnurlpay/test2', { text: 'xyz' })
69+
charge.get('/lnurlpays')
70+
.expect(200)
71+
.expect('Content-Type', /json/)
72+
.expect(({ body }) => {
73+
eq(body.length, 2)
74+
eq(body.find(({ id }) => id === 'test2').text, 'xyz')
75+
eq(body.find(({ id }) => id === 'test2').metadata, {a: 3})
76+
})
77+
})
78+
79+
it('can delete endpoints', async () => {
80+
await mkLnurlPay({ id: 'test1' })
81+
await mkLnurlPay({ id: 'test2' })
82+
await charge.del('/lnurlpay/test1')
83+
.expect(204)
84+
charge.get('/lnurlpays')
85+
.expect(200)
86+
.expect(({ body }) => {
87+
eq(body.length, 0)
88+
})
89+
})
90+
})
91+
92+
describe('invoice generation through the lnurl-pay protocol', () => {
93+
it('can return lnurlpay params', async () => {
94+
await mkLnurlPay({ id: 'one', min: 100000, max: 200000, comment_length: 100 })
95+
await mkLnurlPay({ id: 'other', amount: 123123, comment_length: undefined })
96+
await charge.get('/lnurl/one')
97+
.expect(({ body }) => {
98+
eq(body.tag, 'payRequest')
99+
eq(body.metadata, `[["text/plain", "test lnurlpay"]]`)
100+
eq(body.maxSendable, 200000)
101+
eq(body.minSendable, 100000)
102+
eq(body.commentAllowed, 100)
103+
eq(body.callback, 'https://charge.example.com/lnurl/one/cb')
104+
})
105+
await charge.get('/lnurl/other')
106+
.expect(({ body }) => {
107+
eq(body.tag, 'payRequest')
108+
eq(body.metadata, `[["text/plain", "test lnurlpay"]]`)
109+
eq(body.maxSendable, 123123)
110+
eq(body.minSendable, 123123)
111+
eq(body.commentAllowed, 0)
112+
eq(body.callback, 'https://charge.example.com/lnurl/other/cb')
113+
})
114+
})
115+
116+
it('can return invoice', async () => {
117+
await mkLnurlPay()
118+
charge.get('/lnurl/test/cb?amount=112233')
119+
.expect(({ body }) => {
120+
console.log(body)
121+
ok(body.pr && body.routes)
122+
eq(body.pr.slice(0, 12), 'lnbcrt112233')
123+
eq(body.pr.successAction, { tag: 'url', description: 'thank you', url: 'https://charge.example.com/success' })
124+
})
125+
})
126+
127+
it('uses the "currency" parameter correctly', async () => {
128+
await mkLnurlPay({ currency: 'USD', min: 1, max: 2 })
129+
charge.get(`/lnurl/test/cb?amount=${toMsat('USD', 1.5)}`)
130+
.expect(({ body }) => {
131+
console.log(body)
132+
ok(body.pr && body.routes)
133+
eq(body.pr.slice(0, 12), 'lnbcrt112233')
134+
eq(body.pr.successAction, { tag: 'url', description: 'thank you', url: 'https://charge.example.com/success' })
135+
})
136+
})
137+
})
138+
139+
describe('invoice from lnurlpay endpoints management', () => {
140+
it('can list invoices', async () => {
141+
await mkLnurlPay()
142+
await charge.get('/lnurl/test/cb?amount=150000')
143+
await charge.get('/lnurl/test/cb?amount=200000')
144+
charge.get('/lnurlpay/test/invoices')
145+
.expect(200)
146+
.expect(({ body }) => {
147+
eq(body.length, 2)
148+
ok(body.find(({ amount }) => amount === 150000))
149+
ok(body.find(({ amount }) => amount === 200000))
150+
})
151+
})
152+
153+
it('can include arbitrary metadata on an invoice', async () => {
154+
await mkLnurlPay()
155+
await charge.get('/lnurl/test?v=1')
156+
.expect(({ body }) => {
157+
eq(body.callback, 'https://charge.example.com/lnurl/test?v=1')
158+
})
159+
await charge.get('/lnurl/test/cb?amount=100000&v=1')
160+
await charge.get('/lnurl/test/cb?amount=200000&v=2')
161+
charge.get('/lnurlpay/test/invoices')
162+
.expect(200)
163+
.expect(({ body }) => {
164+
eq(body.length, 2)
165+
eq(body.find(({ amount }) => amount === 150000).metadata, {v: 1, a: 1})
166+
eq(body.find(({ amount }) => amount === 200000).metadata, {v: 2, a: 1})
167+
})
168+
})
169+
})
170+
})

test/prelude.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ btc generatetoaddress 101 $addr > /dev/null
6767

6868
echo Setting up lightningd >&2
6969

70+
mkdir -p $LN_ALICE_PATH/plugins/
71+
cp test/invoicewithdescriptionhash-mock.sh $LN_ALICE_PATH/plugins/
72+
7073
LN_OPTS="$LN_OPTS --network=regtest --bitcoin-datadir=$BTC_DIR --log-level=debug --log-file=debug.log
7174
--fee-base 0 --fee-per-satoshi 0
7275
--allow-deprecated-apis="$([ -n "$ALLOW_DEPRECATED" ] && echo true || echo false)
@@ -104,7 +107,7 @@ sed $sedq '/State changed from CHANNELD_AWAITING_LOCKIN to CHANNELD_NORMAL/ q' <
104107
echo Setting up charged >&2
105108

106109
DEBUG=$DEBUG,lightning-*,knex:query,knex:bindings \
107-
bin/charged -l $LN_ALICE_PATH -d $CHARGE_DB -t $CHARGE_TOKEN -p $CHARGE_PORT -e ${NODE_ENV:-test} &> $DIR/charge.log &
110+
bin/charged --url https://charge.example.com -l $LN_ALICE_PATH -d $CHARGE_DB -t $CHARGE_TOKEN -p $CHARGE_PORT -e ${NODE_ENV:-test} &> $DIR/charge.log &
108111

109112
CHARGE_PID=$!
110113
sed $sedq '/HTTP server running/ q' <(tail -F -n+0 $DIR/charge.log 2> /dev/null)

test/sse.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ describe('Server-Sent-Events', function() {
1717

1818
before(() =>
1919
setTimeout(async () => {
20-
lnBob.pay(await mkInvoice({ msatoshi: '87' }).then(inv => inv.payreq))
21-
lnBob.pay(await mkInvoice({ msatoshi: '78' }).then(inv => inv.payreq))
20+
await lnBob.pay(await mkInvoice({ msatoshi: '87' }).then(inv => inv.payreq))
21+
await lnBob.pay(await mkInvoice({ msatoshi: '78' }).then(inv => inv.payreq))
2222
}, 250)
2323
)
2424

test/websocket.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ describe('WebSocket API', function() {
1717

1818
before(() =>
1919
setTimeout(async () => {
20-
lnBob.pay(await mkInvoice({ msatoshi: '89' }).then(inv => inv.payreq))
21-
lnBob.pay(await mkInvoice({ msatoshi: '98' }).then(inv => inv.payreq))
20+
await lnBob.pay(await mkInvoice({ msatoshi: '89' }).then(inv => inv.payreq))
21+
await lnBob.pay(await mkInvoice({ msatoshi: '98' }).then(inv => inv.payreq))
2222
}, 250)
2323
)
2424

0 commit comments

Comments
 (0)