commit 0f7de2887b1acb3d6cce8332c95f5fbf2ed39074
parent 813f441dc777206c613d0c03ec2c835649e554e4
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Wed, 28 Jan 2026 23:01:36 +0900
add test for # 10612
Diffstat:
3 files changed, 426 insertions(+), 4 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -859,7 +859,7 @@ phase_check_paid (struct GetOrderRequestContext *gorc)
/**
* Check if the @a reply satisfies the long-poll not_etag
- * constraint. If so, return it as a reponse for @a gorc,
+ * constraint. If so, return it as a response for @a gorc,
* otherwise suspend and wait for a change.
*
* @param[in,out] gorc request to handle
diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh
@@ -254,9 +254,10 @@ VALID_BEFORE="{\"t_s\": $(date +%s -d "+30 days")}" # 30 days from now
DURATION="{\"d_us\": $(expr 30 \* 24 \* 60 \* 60 \* 1000000)}" # 30 days
STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
-d "{\"kind\": \"discount\", \"slug\":\"test-discount\", \"name\": \"Test discount\", \"description\": \"Less money $$\", \"description_i18n\": {\"en\": \"Less money $$\", \"es\": \"Menos dinero $$\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $DURATION}" \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
+ cat "$LAST_RESPONSE" >&2
exit_fail "Expected '204 OK' response. Got instead $STATUS"
fi
echo "Ok"
@@ -270,10 +271,11 @@ VALID_BEFORE="{\"t_s\": $(date +%s -d "+30 days")}" # 30 days from now
DURATION="{\"d_us\": $(expr 30 \* 24 \* 60 \* 60 \* 1000000)}" # 30 days
STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
-d "{\"kind\": \"subscription\", \"slug\":\"test-subscription\", \"name\": \"Test subscription\", \"description\": \"Money per month\", \"description_i18n\": {\"en\": \"Money $$$ per month\", \"es\": \"Dinero $$$ al mes\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $DURATION}" \
- -w "%{http_code}" -s -o /dev/null)
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
then
- exit_fail "Expected '204 OK' response. Got instead $STATUS"
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected '204 OK' response. Got instead $STATUS"
fi
echo "Ok"
diff --git a/src/testing/test_merchant_tokenfamilies.sh b/src/testing/test_merchant_tokenfamilies.sh
@@ -0,0 +1,420 @@
+#!/bin/bash
+# This file is part of TALER
+# Copyright (C) 2026 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3, or
+# (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with TALER; see the file COPYING. If not, see
+# <http://www.gnu.org/licenses/>
+#
+
+# Cleanup to run whenever we exit
+function my_cleanup()
+{
+ for n in $(jobs -p)
+ do
+ kill "$n" 2> /dev/null || true
+ done
+ wait
+ if [ -n "${LAST_RESPONSE+x}" ]
+ then
+ rm -f "${LAST_RESPONSE}"
+ fi
+}
+
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ BANK_FLAGS="-f -d x-taler-bank -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ ACCOUNT="exchange-account-1"
+ BANK_FLAGS="-ns -d iban -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+fi
+
+. setup.sh
+
+setup -c test_template.conf -m
+CONF="test_template.conf.edited"
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
+WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_wallet.json-XXXXXX)
+EXCHANGE_URL="http://localhost:8081"/
+
+echo -n "Configuring 'admin' instance ..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ http://localhost:9966/management/instances \
+ -d '{"auth":{"method":"token","password":"new_pw"},"id":"admin","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \
+ -w "%{http_code}" \
+ -s \
+ -o /dev/null)
+
+if [ "$STATUS" != "204" ]
+then
+ exit_fail "Expected 204, instance created. got: $STATUS" >&2
+fi
+
+BASIC_AUTH=$(echo -n admin:new_pw | base64)
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H "Authorization: Basic $BASIC_AUTH" \
+ http://localhost:9966/private/token \
+ -d '{"scope":"spa"}' \
+ -w "%{http_code}" -s -o $LAST_RESPONSE)
+
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+BEARER_TOKEN=$(jq -e -r .access_token < "$LAST_RESPONSE")
+
+echo " OK" >&2
+
+echo -n "Setting up bank account..." >&2
+
+STATUS=$(curl -H "Content-Type: application/json" \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+
+# CREATE A DISCOUNT TOKEN FAMILY
+#
+echo -n "Creating discount token family..."
+VALID_AFTER="{\"t_s\": $(date +%s)}" # now
+VALID_BEFORE="{\"t_s\": $(date +%s -d "+300 days")}" # 300 days from now
+DURATION="{\"d_us\": $(expr 3 \* 60 \* 1000000)}" # 3 minutes
+GRANULARITY="{\"d_us\": $(expr 60 \* 1000000)}" # 1 minute
+STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -d "{\"kind\": \"discount\", \"slug\":\"test-discount\", \"name\": \"Test discount\", \"description\": \"Less money $$\", \"description_i18n\": {\"en\": \"Less money $$\", \"es\": \"Menos dinero $$\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $GRANULARITY}" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+if [ "$STATUS" != "204" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected '204 OK' response. Got instead $STATUS"
+fi
+echo "Ok"
+
+#
+# CREATE A SUBSCRIPTION TOKEN FAMILY
+#
+echo -n "Creating subscription token family..."
+VALID_AFTER="{\"t_s\": $(date +%s)}" # now
+VALID_BEFORE="{\"t_s\": $(date +%s -d "+30 days")}" # 300 days from now
+DURATION="{\"d_us\": $(expr 3 \* 60 \* 1000000)}" # 3 minutes
+GRANULARITY="{\"d_us\": $(expr 60 \* 1000000)}" # 1 minute
+STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -d "{\"kind\": \"subscription\", \"slug\":\"test-subscription\", \"name\": \"Test subscription\", \"description\": \"Money per month\", \"description_i18n\": {\"en\": \"Money $$$ per month\", \"es\": \"Dinero $$$ al mes\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $GRANULARITY}" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+if [ "$STATUS" != "204" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected '204 OK' response. Got instead $STATUS"
+fi
+echo "Ok"
+
+echo "Time traveling merchant 10 minutes into the future ..."
+
+# Kill merchant
+kill -TERM "$SETUP_PID"
+wait
+unset SETUP_PID
+
+setup -c test_template.conf \
+ -ef \
+ -u "exchange-account-2" \
+ -r "merchant-exchange-default"
+
+taler-merchant-exchangekeyupdate \
+ -c "${CONF}" \
+ -L DEBUG \
+ -t \
+ 2> taler-merchant-exchangekeyupdate2.log
+# 600000000 = 10 minutes
+taler-merchant-httpd \
+ -c "${CONF}" \
+ -L DEBUG \
+ --timetravel=600000000 \
+ 2> taler-merchant-httpd2.log &
+# Install cleanup handler (except for kill -9)
+trap my_cleanup EXIT
+
+echo -n "Waiting for the merchant..." >&2
+# Wait for merchant to be available (usually the slowest)
+for n in $(seq 1 50)
+do
+ echo -n "." >&2
+ sleep 0.1
+ OK=0
+ # merchant
+ wget --waitretry=0 \
+ --timeout=1 \
+ http://localhost:9966/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ "x$OK" != "x1" ]
+then
+ exit_fail "Failed to (re)start merchant backend"
+fi
+
+echo " OK" >&2
+
+echo -n "Preparing wallet with coins ..."
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:99",
+ corebankApiBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL
+ }' \
+ --arg BANK_URL "${BANK_URL}" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL"
+ )" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
+echo -n "."
+# FIXME-MS: add logic to have nexus check immediately here.
+# sleep 10
+echo -n "."
+# NOTE: once libeufin can do long-polling, we should
+# be able to reduce the delay here and run wirewatch
+# always in the background via setup
+taler-exchange-wirewatch \
+ -a "$ACCOUNT" \
+ -L "INFO" \
+ -c "$CONF" \
+ -t &> taler-exchange-wirewatch.out
+echo -n "."
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ run-until-done \
+ 2>wallet-withdraw-finish-1.err \
+ >wallet-withdraw-finish-1.out
+echo " OK"
+
+CURRENCY_COUNT=$(taler-wallet-cli --wallet-db="$WALLET_DB" balance | jq '.balances|length')
+if [ "$CURRENCY_COUNT" = "0" ]
+then
+ exit_fail "Expected least one currency, withdrawal failed. check log."
+fi
+
+
+
+RANDOM_IMG='data:image/png;base64,abcdefg'
+
+echo -n "Creating subscribeable order..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -d '{"create_token":true,"refund_delay":{"d_us":0},"order":{"version":1,"summary":"Expensive purchase","products":[{"description":"Expensive subscription","quantity":1,"unit":"pieces","price":"TESTKUDOS:10"}],"choices":[{"amount":"TESTKUDOS:10","inputs":[],"outputs":[{"type":"token","token_family_slug":"test-subscription","count":1}]},{"amount":"TESTKUDOS:0","inputs":[{"type":"token","token_family_slug":"test-subscription","count":1}],"outputs":[{"type":"token","token_family_slug":"test-subscription","count":1}]}]}}' \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+echo "OK"
+
+echo -n "Fetching payment URL "
+
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, getting order info. got: $STATUS"
+fi
+
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+
+echo "OK"
+
+
+NOW=$(date +%s)
+
+echo -n "Pay for subscription (choice 1) at ${PAY_URL} ..."
+echo "1" \
+ | taler-wallet-cli \
+ --no-throttle \
+ --timetravel=600000000 \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y 2> wallet-pay1.err > wallet-pay1.log
+taler-wallet-cli \
+ --no-throttle \
+ --timetravel=600000000 \
+ --wallet-db="$WALLET_DB" \
+ run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs )"
+
+echo -n "Checking order was paid ..."
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, after pay. got: $STATUS"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Order status should be 'paid'. got: $ORDER_STATUS"
+fi
+echo " OK"
+
+# FIXME: test also paying with the subscription token...
+
+
+
+echo -n "Creating discountable order..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -d '{"create_token":true,"refund_delay":{"d_us":0},"order":{"version":1,"summary":"Expensive subscription","products":[{"description":"Simple purchase","quantity":1,"unit":"pieces","price":"TESTKUDOS:10"}],"choices":[{"amount":"TESTKUDOS:10","inputs":[],"outputs":[{"type":"token","token_family_slug":"test-discount","count":1}]},{"amount":"TESTKUDOS:9","inputs":[{"type":"token","token_family_slug":"test-discount","count":1}],"outputs":[]}]}}' \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+echo "OK"
+
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
+
+echo -n "Fetching payment URL "
+
+ORDER_ID=$(jq -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -r .token < "$LAST_RESPONSE")
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, getting order info. got: $STATUS"
+fi
+
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+
+echo "OK"
+
+
+NOW=$(date +%s)
+
+echo -n "Pay without discount (choice 0) at ${PAY_URL} ..."
+echo "0" \
+ | taler-wallet-cli \
+ --no-throttle \
+ --timetravel=600000000 \
+ --wallet-db="$WALLET_DB" \
+ handle-uri "${PAY_URL}" \
+ -y 2> wallet-pay1.err > wallet-pay1.log
+taler-wallet-cli \
+ --no-throttle \
+ --timetravel=600000000 \
+ --wallet-db="$WALLET_DB" \
+ run-until-done 2> wallet-finish-pay1.err > wallet-finish-pay1.log
+NOW2=$(date +%s)
+echo " OK (took $(( NOW2 - NOW )) secs )"
+
+echo -n "Checking order was paid ..."
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -X POST \
+ -H "Authorization: Bearer $BEARER_TOKEN" \
+ -w "%{http_code}" \
+ -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Expected 200, after pay. got: $STATUS"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ cat "$LAST_RESPONSE" >&2
+ exit_fail "Order status should be 'paid'. got: $ORDER_STATUS"
+fi
+echo " OK"
+
+# FIXME: test also paying with the discount token...
+
+echo "Test PASSED"
+
+exit 0