diff --git a/integration/002_lnurl.jmx b/integration/002_lnurl.jmx
index 0f87247d..f439b253 100644
--- a/integration/002_lnurl.jmx
+++ b/integration/002_lnurl.jmx
@@ -68,6 +68,11 @@
${__property(mirrorPort,,8500)}
=
+
+ webhookCapturePort
+ ${__property(webhookCapturePort,,8501)}
+ =
+
configPath
${__BeanShell(import org.apache.jmeter.services.FileServer; FileServer.getFileServer().getBaseDir();)}
@@ -106,6 +111,14 @@
false
+
+ groovy
+
+ ${configPath}/scripts/lnurl_start_webhook_capture.groovy
+ true
+
+
+
true
false
@@ -406,7 +419,7 @@
false
- {"description":"receive payments ${lnurlpCounter}","min":11,"comment_chars":128,"webhook_url":"http://localhost:${mirrorPort}","webhook_headers":"{\"h1\": \"1\"}","webhook_body":"{\"b2\": 2}","success_text":"All goood!","success_url":"https://lnbits.com","max":${lnurlpCounter}1}
+ {"description":"receive payments ${lnurlpCounter}","min":11,"comment_chars":128,"webhook_url":"http://127.0.0.1:${webhookCapturePort}/","webhook_headers":"{\"h1\": \"1\"}","webhook_body":"{\"b2\": 2}","success_text":"All goood!","success_url":"https://lnbits.com","max":${lnurlpCounter}1}
=
@@ -972,11 +985,6 @@
-
- 100
- for the webhook to be called
-
-
false
@@ -1019,6 +1027,14 @@
8
+
+ groovy
+
+ ${configPath}/scripts/lnurl_wait_for_webhook.groovy
+ true
+
+
+
$.details.extra.wh_success
true
@@ -1030,17 +1046,17 @@
$.details.extra.wh_message
- OK
+ Webhook delivered.
true
false
false
false
-
+
javascript
- ${configPath}/scripts/lnurl_check_webhook_response.js
+ ${configPath}/scripts/lnurlp_check_webhook_request.js
true
@@ -1213,7 +1229,7 @@
Withdraw links
- curl -X GET http://127.0.0.1:5000/withdraw/api/v1/links -H
+ curl -X GET ${scheme}://${host}:${port}/withdraw/api/v1/links -H
Assertion.response_data
@@ -2062,6 +2078,14 @@
8
+
+ groovy
+
+ ${configPath}/scripts/lnurl_wait_for_webhook.groovy
+ true
+
+
+
$.details.extra.wh_success
true
@@ -2485,6 +2509,27 @@
+
+ continue
+
+ false
+ 1
+
+ 1
+ 1
+ false
+
+
+
+ groovy
+
+
+ true
+ props.remove('lnurlWebhookServer')?.stop(0)
+props.remove('lnurlWebhookRequests')
+
+
+
diff --git a/integration/fragments/init-server.jmx b/integration/fragments/init-server.jmx
index 547eba52..23a941eb 100644
--- a/integration/fragments/init-server.jmx
+++ b/integration/fragments/init-server.jmx
@@ -369,7 +369,8 @@
false
{
"lnbits_callback_url_rules": [],
- "lnbits_lnurl_allow_private_ips": true
+ "lnbits_lnurl_allow_private_ips": true,
+ "lnbits_callback_allow_private_ips": true
}
=
diff --git a/integration/scripts/lnurl_start_webhook_capture.groovy b/integration/scripts/lnurl_start_webhook_capture.groovy
new file mode 100644
index 00000000..f3b6df49
--- /dev/null
+++ b/integration/scripts/lnurl_start_webhook_capture.groovy
@@ -0,0 +1,27 @@
+import com.sun.net.httpserver.HttpHandler
+import com.sun.net.httpserver.HttpServer
+import groovy.json.JsonOutput
+import groovy.json.JsonSlurper
+import java.util.concurrent.ConcurrentHashMap
+
+def requests = new ConcurrentHashMap()
+def server = HttpServer.create(
+ new InetSocketAddress('127.0.0.1', vars.get('webhookCapturePort').toInteger()), 0)
+server.createContext('/', { exchange ->
+ try {
+ def body = new JsonSlurper().parseText(exchange.requestBody.getText('UTF-8'))
+ requests.put(body.payment_hash, JsonOutput.toJson([
+ method: exchange.requestMethod,
+ header: exchange.requestHeaders.getFirst('h1'),
+ body: body
+ ]))
+ byte[] response = 'Webhook response must not appear in payment metadata'.getBytes('UTF-8')
+ exchange.sendResponseHeaders(200, response.length)
+ exchange.responseBody.write(response)
+ } finally {
+ exchange.close()
+ }
+} as HttpHandler)
+server.start()
+props.put('lnurlWebhookRequests', requests)
+props.put('lnurlWebhookServer', server)
diff --git a/integration/scripts/lnurl_wait_for_webhook.groovy b/integration/scripts/lnurl_wait_for_webhook.groovy
new file mode 100644
index 00000000..8ab82b3b
--- /dev/null
+++ b/integration/scripts/lnurl_wait_for_webhook.groovy
@@ -0,0 +1,28 @@
+import groovy.json.JsonSlurper
+import java.util.concurrent.TimeUnit
+
+def deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30)
+def poll = sampler.clone()
+poll.setConnectTimeout('2000')
+poll.setResponseTimeout('2000')
+def response = prev
+
+// Stop on a completed delivery (including failure) or an HTTP error.
+while (response.successful && System.nanoTime() < deadline) {
+ def payment = new JsonSlurper().parseText(response.responseDataAsString)
+ if (payment.details?.extra?.containsKey('wh_success')) {
+ break
+ }
+ Thread.sleep(250)
+ response = poll.sample()
+ prev.addSubResult(response)
+}
+
+// The existing assertions must inspect the final response, including failures.
+if (!response.is(prev)) {
+ prev.setResponseData(response.responseData)
+ prev.setResponseCode(response.responseCode)
+ prev.setResponseMessage(response.responseMessage)
+ prev.setSuccessful(response.successful)
+ prev.setEndTime(System.currentTimeMillis())
+}
diff --git a/integration/scripts/lnurlp_check_webhook_request.js b/integration/scripts/lnurlp_check_webhook_request.js
new file mode 100644
index 00000000..0a50690f
--- /dev/null
+++ b/integration/scripts/lnurlp_check_webhook_request.js
@@ -0,0 +1,33 @@
+var payment = JSON.parse(prev.getResponseDataAsString());
+var paymentHash = vars.get("paymenHash");
+var captured = props.get("lnurlWebhookRequests").remove(paymentHash);
+var extra = payment.details.extra;
+
+if (extra.wh_status !== 200 || extra.wh_response !== "") {
+ AssertionResult.setFailureMessage(
+ "Expected HTTP 200 delivery with a redacted webhook response",
+ );
+ AssertionResult.setFailure(true);
+} else if (captured === null) {
+ AssertionResult.setFailureMessage(
+ "No webhook received for this payment hash",
+ );
+ AssertionResult.setFailure(true);
+} else {
+ var request = JSON.parse(String(captured));
+ if (
+ request.method !== "POST" ||
+ request.body.payment_hash !== paymentHash
+ ) {
+ AssertionResult.setFailureMessage(
+ "Incorrect webhook method or payment hash",
+ );
+ AssertionResult.setFailure(true);
+ } else if (request.header !== "1") {
+ AssertionResult.setFailureMessage("Expected webhook header h1: 1");
+ AssertionResult.setFailure(true);
+ } else if (!request.body.body || request.body.body.b2 !== 2) {
+ AssertionResult.setFailureMessage("Expected webhook body field b2: 2");
+ AssertionResult.setFailure(true);
+ }
+}