#!/usr/bin/env bash
# This file is in the public domain.
#
# Regression test for finding 5: '/setup' must survive a serialization
# failure instead of answering 500.
#
# The database session is SERIALIZABLE (see challengerdb/pg.c), so every
# single-statement query is its own serializable transaction.  The first
# statement of 'do_insert_validation' is
#   UPDATE clients SET validation_counter=validation_counter+1 ...
# which every /setup of a given client performs on the *same* row.  Any
# concurrent writer of that row therefore makes /setup fail with SQLSTATE
# 40001 -> GNUNET_DB_STATUS_SOFT_ERROR, which the handler must retry.
#
# We provoke exactly that, deterministically: a psql session holds an open
# transaction that has already updated the counter row, /setup is issued
# (and blocks on the row lock), and the holder then commits.  PostgreSQL
# then aborts the blocked statement with 40001.  With the retry loop the
# request succeeds on the next attempt; without it the user gets a 500.

set -eu

# Exit, with status code "skip" (no 'real' failure)
function exit_skip() {
    echo " SKIP: $1"
    exit 77
}

# Exit, with error message (hard failure)
function exit_fail() {
    echo " FAIL: $@"
    exit 1
}

# Cleanup to run whenever we exit
function cleanup()
{
    exec 3>&- 2> /dev/null || true
    for n in $(jobs -p)
    do
        kill $n 2> /dev/null || true
    done
    rm -f "$LAST_RESPONSE" "$BLOCKER_FIFO"
    wait
}

LAST_RESPONSE=$(mktemp responseXXXXXX.log)
BLOCKER_FIFO=$(mktemp -u blockerXXXXXX.fifo)

# Install cleanup handler (except for kill -9)
trap cleanup EXIT

export PATH="$PATH:."

echo -n "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
echo " FOUND"
echo -n "Testing for curl"
curl -h > /dev/null || exit_skip "curl required"
echo " FOUND"
echo -n "Testing for wget"
wget -h > /dev/null || exit_skip "wget required"
echo " FOUND"
echo -n "Testing for psql"
psql --version > /dev/null || exit_skip "psql required"
echo " FOUND"
echo -n "Testing for challenger-httpd ..."
challenger-httpd -h > /dev/null || exit_skip "challenger-httpd required"
echo " FOUND"

CONF="test-challenger.conf"
BURL="http://localhost:9967"
REDIRECT_URI="http://client.example.com/"

DB_URI=$(challenger-config -c "${CONF}" -s challengerdb-postgres -o CONFIG)

echo -n "Initialize challenger database ..."
challenger-dbinit -r -c "${CONF}" &> dbinit.log
echo " OK"

echo -n "Add challenger client ..."
CLIENT_SECRET="secret-token:secret"
challenger-admin -c "${CONF}" -a "${CLIENT_SECRET}" "${REDIRECT_URI}" &> admin.log
echo " OK"
# We just reset the DB, thus the client ID must be 1 here:
CLIENT_ID=1

echo -n "Start challenger-httpd ..."
challenger-httpd -L INFO -c "${CONF}" &> httpd.log &

# Wait for challenger to be available
for n in $(seq 1 50)
do
    echo -n "."
    sleep 0.2
    OK=0
    wget --tries=1 --timeout=1 "${BURL}/config" -o /dev/null -O /dev/null >/dev/null || continue
    OK=1
    break
done
if [ 1 != $OK ]
then
    exit_skip "Failed to launch challenger service"
fi
echo " OK"

echo -n "Locking the client's validation counter ..."
mkfifo "${BLOCKER_FIFO}"
psql "${DB_URI}" -q -f "${BLOCKER_FIFO}" &> blocker.log &
exec 3>"${BLOCKER_FIFO}"
echo "BEGIN; UPDATE challenger.clients SET validation_counter=validation_counter+1 WHERE client_serial_id=${CLIENT_ID};" >&3
sleep 1
echo " OK"

echo -n "Setup new validation process against the lock ..."
curl "${BURL}/setup/${CLIENT_ID}" \
    -H "Authorization: Bearer ${CLIENT_SECRET}" \
    -d '' \
    -w "%{http_code}" -s -o $LAST_RESPONSE > status.txt &
CURL_PID=$!
# Give the request time to reach the UPDATE and block on the row lock,
# then release it; the blocked statement now fails with 40001.
sleep 2
echo "COMMIT;" >&3
exec 3>&-
wait $CURL_PID
STATUS=$(cat status.txt)
rm -f status.txt

if [ "$STATUS" != "200" ]
then
    exit_fail "Expected 200 OK after retrying the serialization failure. Got: $STATUS" $(cat $LAST_RESPONSE)
fi
NONCE=$(jq -r .nonce < "$LAST_RESPONSE")
if [ "x$NONCE" = "xnull" ]
then
    exit_fail "Expected a nonce in the /setup response"
fi
echo " OK"

exit 0
