release 1.26.0: --wallet-db harness flag, TESTPAYSAN quadlet, token-pay

This commit is contained in:
Hernâni Marques 2026-08-30 20:06:41 +02:00
parent f2ee435e6b
commit 39e5eccc25
No known key found for this signature in database
13 changed files with 235 additions and 39 deletions

View file

@ -8,8 +8,8 @@ source "$ROOT/lib.sh"
# shellcheck source=metrics.sh
source "$ROOT/metrics.sh"
: "${E2E_TIMEOUT:=180}" # Bank withdraw ladder + wirewatch lag needs room (was 55 — too tight)
: "${E2E_PAY_SECS:=22}" # hard reserve for handle-uri + settle (do not starve pay)
: "${E2E_TIMEOUT:=360}" # Bank withdraw + refresh/pay + shop sample + token-pay
: "${E2E_PAY_SECS:=60}" # handle-uri + coin refresh; macOS perl SIGALRM kills at this cap
: "${E2E_SETTLE_ROUNDS:=18}"
: "${E2E_SETTLE_SLEEP:=3}"
E2E_START=$(date +%s)
@ -307,7 +307,7 @@ PAY_AMT=$(printf '%s' "$PAY_LIST" | awk '{print $1}')
if [ "$E2E_VARIABLE" = "1" ]; then
n_w=$(printf '%s' "$WITHDRAW_LIST" | wc -w | tr -d ' ')
n_p=$(printf '%s' "$PAY_LIST" | wc -w | tr -d ' ')
need=$(( 50 + n_w * 40 + n_p * 20 + 60 )) # + room for paivana pay
need=$(( 80 + n_w * 50 + n_p * 50 + 180 )) # shop sample + token-pay + refresh room
if [ "${E2E_TIMEOUT}" -lt "$need" ]; then
E2E_TIMEOUT=$need
fi
@ -330,8 +330,9 @@ fi
BANK_HOST=$(python3 -c 'from urllib.parse import urlparse; print(urlparse("'"$BANK_PUBLIC"'").hostname or "bank")' 2>/dev/null || echo bank)
SCRATCH=$(mktemp -d)
WDB="$SCRATCH/wallet.sqlite3"
WSOCK="$SCRATCH/wallet.sock"
WDB=$(taler_mon_resolve_wallet_db "$SCRATCH/wallet.sqlite3") || exit 2
# macOS AF_UNIX path cap ~104 bytes. Do not use $TMPDIR (/var/folders/...).
WSOCK="/tmp/e2e-wcli-$$.sock"
WSERVE_PID=""
# Client/server wallet (advanced serve): long-lived shepherd without run-until-done.
# Default on — wallet-cli only completes withdraw/pay while a serve process is up.
@ -598,20 +599,26 @@ start_wallet_serve() {
[ -n "${WALLET_CLI:-}" ] && [ -f "${WALLET_CLI}" ] || return 1
stop_wallet_serve
rm -f "$WSOCK"
# helper shebang is /usr/bin/env python3 — macOS /usr/bin/python3 is 3.9 (FATAL).
export PATH="/opt/homebrew/bin:/usr/local/bin:${HOME}/.local/bin:${PATH}"
export PYTHON="${PYTHON:-$(command -v python3)}"
# serve holds the DB open; clients use --wallet-connection only
"$NODE_BIN" "$WALLET_CLI" --wallet-db="$WDB" --no-throttle --skip-defaults \
advanced serve --unix-path "$WSOCK" \
>"$SCRATCH/wallet-serve.log" 2>&1 &
WSERVE_PID=$!
local i
for i in $(seq 1 40); do
if [ -S "$WSOCK" ] && kill -0 "$WSERVE_PID" 2>/dev/null; then
for i in $(seq 1 80); do
if kill -0 "$WSERVE_PID" 2>/dev/null && { [ -S "$WSOCK" ] || grep -q "serving at" "$SCRATCH/wallet-serve.log" 2>/dev/null; }; then
ok "wallet serve" "pid=$WSERVE_PID sock=$WSOCK (no run-until-done)"
return 0
fi
if ! kill -0 "$WSERVE_PID" 2>/dev/null; then
break
fi
sleep 0.15
done
warn "wallet serve" "socket not ready — falling back to one-shot CLI (coins may stay pending)"
warn "wallet serve" "socket not ready — $(head -c 180 "$SCRATCH/wallet-serve.log" 2>/dev/null | tr '\n' ' ')"
stop_wallet_serve
return 1
}
@ -658,20 +665,10 @@ wcli() {
fi
}
# Pay must not be starved: use reserved seconds even if global budget is tight
# Pay must not be starved: always keep the reserved handle-uri cap
# (leftover e2e budget used to shrink this to 1214s → SIGALRM mid-pay).
wcli_pay() {
local cap=$E2E_PAY_SECS
local left
left=$(e2e_left)
left=$(printf '%s' "$left" | awk '/^[0-9]+$/{print; exit} {for(i=1;i<=NF;i++) if($i+0==$i){print $i; exit}}')
if [ -n "$left" ] && [ "$left" -gt "$cap" ] 2>/dev/null; then
cap=$E2E_PAY_SECS
elif [ -n "$left" ] && [ "$left" -ge 8 ] 2>/dev/null; then
cap=$left
else
# still try once with floor 12s so Alarm clock is not the blocker
cap=12
fi
info "pay wallet cap" "${cap}s"
if [ -n "${WSERVE_PID:-}" ] && [ -S "${WSOCK:-}" ]; then
_wcli_exec "$cap" --wallet-connection="$WSOCK" --no-throttle "$@"
@ -1717,12 +1714,135 @@ else
info "paivana summary" "${PAIVANA_REPORT:-?} · instance ${E2E_PAIVANA_INSTANCE}"
fi
# ---------------------------------------------------------------------------
# Loyalty-token pays (generic). TESTPAYSAN default = stage token-order API.
# Override: E2E_TOKEN_ORDER_URL E2E_TOKEN_PRODUCT E2E_TOKEN_PAID_N E2E_TOKEN_FREE_N
# ---------------------------------------------------------------------------
if [ "${CUR:-}" = "TESTPAYSAN" ] && [ -z "${E2E_TOKEN_ENABLE:-}" ]; then
E2E_TOKEN_ENABLE=1
fi
: "${E2E_TOKEN_ORDER_URL:=https://stage.monnaie.lefrancpaysan.ch/shops/api/kiosque-order}"
: "${E2E_TOKEN_PRODUCT:=prod_banana}"
: "${E2E_TOKEN_PAID_N:=2}"
: "${E2E_TOKEN_FREE_N:=1}"
TOKEN_OK=0
TOKEN_OK_N=0
TOKEN_FAIL_N=0
TOKEN_REPORT=""
e2e_one_token_order() {
local product="$1" choice="$2" tag="$3"
local body create_f payuri oid otok inst
body=$(python3 -c 'import json,sys; print(json.dumps({"product_id":sys.argv[1]}))' "$product")
create_f="$SCRATCH/token-$tag.json"
curl -skS -m 20 -o "$create_f" -X POST \
-H 'Content-Type: application/json' -d "$body" \
"$E2E_TOKEN_ORDER_URL" 2>"$SCRATCH/token-$tag.err" || true
oid=$(python3 -c 'import json,sys
try:
d=json.load(open(sys.argv[1])); print(d.get("order_id") or "")
except Exception: print("")' "$create_f" 2>/dev/null || true)
otok=$(python3 -c 'import json,sys
try:
d=json.load(open(sys.argv[1])); print(d.get("token") or "")
except Exception: print("")' "$create_f" 2>/dev/null || true)
payuri=$(python3 -c 'import json,sys
try:
d=json.load(open(sys.argv[1])); print((d.get("taler_pay_uri") or "").replace(":443",""))
except Exception: print("")' "$create_f" 2>/dev/null || true)
if [ -z "$oid" ] || [ -z "$payuri" ]; then
warn "token-pay $tag" "order POST failed — $(head -c 120 "$create_f" 2>/dev/null | tr '\n' ' ')"
return 1
fi
info "token-pay $tag" "order $oid choice=$choice product=$product"
if ! wcli_pay handle-uri --yes --non-interactive --choice-index="$choice" "$payuri" \
>"$SCRATCH/token-pay-$tag.out" 2>&1; then
warn "token-pay $tag" "handle-uri failed — $(tail -c 140 "$SCRATCH/token-pay-$tag.out" | tr '\n' ' ')"
return 1
fi
# wallet-cli can print Payment expired/failed and still exit 0
if grep -qiE 'Payment expired|Payment failed|Payment aborted|insufficient balance' \
"$SCRATCH/token-pay-$tag.out" 2>/dev/null; then
warn "token-pay $tag" "handle-uri not paid — $(tail -c 140 "$SCRATCH/token-pay-$tag.out" | tr '\n' ' ')"
return 1
fi
inst="${E2E_TOKEN_INSTANCE:-kiosque-banane}"
local r
for r in 1 2 3 4 5 6 8 10 12 15 18 20; do
if [ -n "$otok" ]; then
curl -skS -m 8 -o "$SCRATCH/token-st-$tag.json" \
"${MERCHANT_PUBLIC}/instances/${inst}/orders/$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$oid")?token=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$otok")" \
2>/dev/null || true
if python3 -c 'import json,sys
try:
d=json.load(open(sys.argv[1]))
st=str(d.get("order_status") or d.get("status") or "").lower()
if d.get("paid") is True or st=="paid":
raise SystemExit(0)
except FileNotFoundError:
pass
except SystemExit:
raise
except Exception:
pass
raise SystemExit(1)
' "$SCRATCH/token-st-$tag.json" 2>/dev/null \
|| grep -qE '"paid"[[:space:]]*:[[:space:]]*true|"order_status"[[:space:]]*:[[:space:]]*"paid"' \
"$SCRATCH/token-st-$tag.json" 2>/dev/null; then
ok "token-pay $tag" "paid choice=$choice oid=$oid"
return 0
fi
fi
sleep 2
done
warn "token-pay $tag" "not settled for order $oid$(head -c 80 "$SCRATCH/token-st-$tag.json" 2>/dev/null | tr '\n' ' ')"
return 1
}
if [ "${E2E_TOKEN_ENABLE}" = "1" ]; then
set_group token
section "e2e · token pays (public order API)"
info "token-pay" "POST $E2E_TOKEN_ORDER_URL product=$E2E_TOKEN_PRODUCT paid_n=$E2E_TOKEN_PAID_N free_n=$E2E_TOKEN_FREE_N"
_ti=1
while [ "$_ti" -le "$E2E_TOKEN_PAID_N" ]; do
if e2e_one_token_order "$E2E_TOKEN_PRODUCT" 0 "paid${_ti}"; then
TOKEN_OK_N=$((TOKEN_OK_N + 1))
TOKEN_REPORT="${TOKEN_REPORT:+$TOKEN_REPORT }paid${_ti}=OK"
else
TOKEN_FAIL_N=$((TOKEN_FAIL_N + 1))
TOKEN_REPORT="${TOKEN_REPORT:+$TOKEN_REPORT }paid${_ti}=FAIL"
fi
_ti=$((_ti + 1))
done
_ti=1
while [ "$_ti" -le "$E2E_TOKEN_FREE_N" ]; do
if e2e_one_token_order "$E2E_TOKEN_PRODUCT" 1 "free${_ti}"; then
TOKEN_OK_N=$((TOKEN_OK_N + 1))
TOKEN_REPORT="${TOKEN_REPORT:+$TOKEN_REPORT }free${_ti}=OK"
else
TOKEN_FAIL_N=$((TOKEN_FAIL_N + 1))
TOKEN_REPORT="${TOKEN_REPORT:+$TOKEN_REPORT }free${_ti}=FAIL"
fi
_ti=$((_ti + 1))
done
unset _ti
if [ "$TOKEN_FAIL_N" = "0" ] && [ "$TOKEN_OK_N" -gt 0 ]; then
TOKEN_OK=1
PAY_OK=1
PAY_OK_N=$((PAY_OK_N + TOKEN_OK_N))
ok "token-pay summary" "$TOKEN_REPORT"
else
warn "token-pay summary" "$TOKEN_REPORT"
fi
fi
set_group report
section "e2e · report"
info "user" "$USER"
info "Bank withdraws" "$WITHDRAW_REPORT"
info "payments" "$PAY_REPORT"
info "shop" "${SHOP_REPORT:-(n/a)}"
info "token-pay" "${TOKEN_REPORT:-(n/a)}"
info "paivana" "${PAIVANA_REPORT:-(n/a)}"
info "final balance" "$(fmt_bal "$SCRATCH/bal-live.out" 2>/dev/null || echo "(n/a)")"
info "scratch" "$SCRATCH"