Files
postgrest/test/io-tests.sh
T
Wolfgang WaltherandWolfgang Walther ed58511de3 feat: renamed config options with prefixes; added aliases for old names
* secret-is-base64 -> jwt-secret-is-base64
* role-claim-key -> jwt-role-claim-key
* max-rows -> db-max-rows
* pre-request -> db-pre-request
* root-spec -> db-root-spec
* db-schema -> db-schemas

This is not a breaking change, because aliases are added as well.

refactor: sorted all config keys alphabetically where applicable
2020-12-06 21:59:03 +01:00

428 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Run unit tests for Input/Ouput of PostgREST seen as a black box
# with test output in Test Anything Protocol format.
#
# These tests expect that `postgrest` is on the PATH, as well as `curl`
#
# References:
# [1] Test Anything Protocol
# https://testanything.org/
#
# [2] TAP Specification
# https://testanything.org/tap-specification.html
#
# [3] List of TCP and UDP port numbers
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
#
set -eu
export POSTGREST_TEST_CONNECTION=${POSTGREST_TEST_CONNECTION:-"postgres:///postgrest_test"}
cd "$(dirname "$0")"
cd io-tests
cleanup() {
# clean up trap to avoid bash segmentation fault
trap - sigint sigterm exit
# kill without output
ps=$(pgrep -g0 | sed -e "1,/$$/d")
kill $ps 2> /dev/null
wait $ps 2> /dev/null
}
trap cleanup sigint sigterm exit
# Port for Test PostgREST Server (must match config)
pgrPort=49421 # in range 4915265535: for private or temporary use
# Colors
NC='\033[0m' # no color
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
# TAP utilities
currentTest=1
failedTests=0
bailOut(){ echo "Bail out! $1"; exit 1; }
result(){ echo -e "$1 $currentTest $2${NC}"; currentTest=$(( $currentTest + 1 )); }
todo(){ result "${YELLOW}ok" "# TODO: $*"; }
skip(){ result "${YELLOW}ok" "# SKIP: $*"; }
ok(){ result "${GREEN}ok" "- $1"; }
ko(){ result "${RED}not ok" "- $1"; failedTests=$(( $failedTests + 1 )); }
comment(){ echo "# $1"; }
########################
# SYNCHRONOUS IO TESTS #
########################
dumpedConfigMatchesExpectation(){
# This test compares the dumped config vs. the corresponding file in ./configs/expected.
# To be used to test default values, config aliases and environment variables.
dump="$(mktemp)"
tap(){
if test $1 -eq 0; then
ok "dump of config file $2 does match expectation"
else
ko "dump of config file $2 does not match expectation"
fi
rm -f "$dump"
}
trap 'tap $? $1; trap - RETURN; return 0' ERR RETURN
postgrest --dump-config "$1" > "$dump"
diff --color "$dump" "$2"
}
dumpedConfigIsValid(){
# This test compares the dumped config vs. the dumped-reread-redumped config.
# Re-reading the dumped config tests the validity of the config format.
# Re-dumping this config should yield no difference to the first dump, showing
# that the semantics have not changed by dumping.
# Note: only dump vs redump must be equal, the original config file can be different,
# because of default values, whitespace, and quoting
dump="$(mktemp)"
redump="$(mktemp)"
tap(){
if test $1 -eq 0; then
ok "dump of config file $2 is valid"
else
ko "dump of config file $2 is invalid"
fi
rm -f "$dump" "$redump"
}
trap 'tap $? $1; trap - RETURN; return 0' ERR RETURN
postgrest --dump-config "$1" > "$dump"
postgrest --dump-config "$dump" > "$redump"
diff --color "$dump" "$redump"
}
####################
# BACKGROUND TESTS #
####################
# Utilities to start/stop test PostgREST server running in the background
pgrStart(){
# stderr is not piped to /dev/null to catch errors on startup.
# to keep $! reference the correct pid, stderr is piped to a subshell and
# then filtered for FatalError. Those are part of the tests and expected.
postgrest $1 >/dev/null 2> >(grep -v 'FatalError' 1>&2) & pgrPID="$!";
}
pgrStartRead(){ postgrest $1 <$2 >/dev/null & pgrPID="$!"; }
pgrStartStdin(){ postgrest $1 >/dev/null <<< "$2" & pgrPID="$!"; }
pgrStarted(){ kill -0 "$pgrPID" 2>/dev/null; }
pgrStop(){ kill "$pgrPID" 2>/dev/null; pgrPID=""; sleep 0.1; }
# Utilities to send HTTP requests to the PostgREST server
rootStatus(){
curl -s -o /dev/null -w '%{http_code}' "http://localhost:$pgrPort/"
}
authorsStatus(){
curl -s -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer $1" \
"http://localhost:$pgrPort/authors_only"
}
v1SchemaParentsStatus(){
curl -s -o /dev/null -w '%{http_code}' \
-H "Accept-Profile: v1" \
"http://localhost:$pgrPort/parents"
}
# Unit Test Templates
readSecretFromFile(){
case "$1" in
*.b64)
pgrConfig="base64-secret-from-file.config";;
*)
pgrConfig="secret-from-file.config";;
esac
pgrStartRead "./configs/$pgrConfig" "./secrets/$1"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
if pgrStarted
then
authorsJwt="./secrets/${1%.*}.jwt"
httpStatus="$( authorsStatus $(cat "$authorsJwt") )"
if test "$httpStatus" -eq 200
then
ok "authentication with $2 secret read from a file"
else
ko "authentication with $2 secret read from a file: $httpStatus"
fi
else
ko "failed to read $2 secret from a file"
fi
pgrStop
}
readDbUriFromStdin(){
pgrConfig="dburi-from-file.config"
pgrStartStdin "./configs/$pgrConfig" "$1"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
if pgrStarted
then
ok "connection with $2 dburi read from stdin / a file"
else
ko "connection with $2 dburi read from stdin / a file"
fi
pgrStop
}
reqWithRoleClaimKey(){
export ROLE_CLAIM_KEY=$1
pgrStart "./configs/role-claim-key.config"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
authorsJwt=$(psql -qtAX "$POSTGREST_TEST_CONNECTION" -c "select jwt.sign('$2', 'reallyreallyreallyreallyverysafe');")
httpStatus="$( authorsStatus "$authorsJwt" )"
if test "$httpStatus" -eq $3
then
ok "request with \"$1\" role-claim-key for $2 jwt: $httpStatus"
else
ko "request with \"$1\" role-claim-key for $2 jwt: $httpStatus"
fi
pgrStop
}
invalidRoleClaimKey(){
export ROLE_CLAIM_KEY=$1
pgrStart "./configs/role-claim-key.config"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
if pgrStarted
then
ko "invalid jspath \"$1\": accepted"
pgrStop
else
ok "invalid jspath \"$1\": rejected"
fi
}
# ensure iat claim is successful in the presence of pgrst time cache, see https://github.com/PostgREST/postgrest/issues/1139
ensureIatClaimWorks(){
pgrStart "./configs/simple.config"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
for i in {1..10}; do \
iatJwt=$(psql -qtAX "$POSTGREST_TEST_CONNECTION" -c "select jwt.sign(row_to_json(r), 'reallyreallyreallyreallyverysafe') from ( select 'postgrest_test_author' as role, extract(epoch from now()) as iat) r")
httpStatus="$( authorsStatus $iatJwt )"
if test "$httpStatus" -ne 200
then
ko "iat claim rejected: $httpStatus"
return
fi
sleep .5;\
done
ok "iat claim accepted"
pgrStop
}
# ensure app settings don't reset on pool timeout, see https://github.com/PostgREST/postgrest/issues/1141
# pool timeout set to 1s to shorten runtime
ensureAppSettings(){
pgrStart "./configs/app-settings.config"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
sleep 2
response=$(curl -s "http://localhost:$pgrPort/rpc/get_guc_value?name=app.settings.external_api_secret")
if test "$response" = "\"0123456789abcdef\""
then
ok "GET /rpc/get_guc_value: $response"
else
ko "GET /rpc/get_guc_value: $response"
fi
pgrStop
}
checkAppSettingsReload(){
configFile=$(mktemp)
trap "rm -f $configFile" ERR RETURN
cat "./configs/sigusr2-settings.config" > "$configFile"
pgrStart "$configFile"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
# change setting
replaceConfigValue "app.settings.name_var" "Jane" "$configFile"
# reload
kill -s SIGUSR2 $pgrPID
response=$(curl -s "http://localhost:$pgrPort/rpc/get_guc_value?name=app.settings.name_var")
if test "$response" = "\"Jane\""
then
ok "app.settings.name_var config reloaded with SIGUSR2"
else
ko "app.settings.name_var config not reloaded with SIGUSR2. Got: $response"
fi
pgrStop
}
checkJwtSecretReload(){
configFile=$(mktemp)
trap "rm -f $configFile" ERR RETURN
cat "./configs/sigusr2-settings.config" > "$configFile"
pgrStart "$configFile"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
secret="reallyreallyreallyreallyverysafe"
# change setting
replaceConfigValue "jwt-secret" "$secret" "$configFile"
# reload
kill -s SIGUSR2 $pgrPID
payload='{"role":"postgrest_test_author"}'
authorsJwt=$(psql -qtAX "$POSTGREST_TEST_CONNECTION" -c "select jwt.sign('$payload', '$secret');")
httpStatus="$( authorsStatus "$authorsJwt" )"
if test "$httpStatus" -eq 200
then
ok "jwt-secret config reloaded with SIGUSR2"
else
ko "jwt-secret config not reloaded with SIGUSR2. Got: $httpStatus"
fi
pgrStop
}
checkDbSchemaReload(){
configFile=$(mktemp)
trap "rm -f $configFile" ERR RETURN
cat "./configs/sigusr2-settings.config" > "$configFile"
pgrStart "$configFile"
while pgrStarted && test "$( rootStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
# add v1 schema to db-schemas
replaceConfigValue "db-schemas" "test, v1" "$configFile"
# reload
kill -s SIGUSR2 $pgrPID
kill -s SIGUSR1 $pgrPID
httpStatus="$(v1SchemaParentsStatus)"
if test "$httpStatus" -eq 200
then
ok "db-schemas config reloaded with SIGUSR2"
else
ko "db-schemas config not reloaded with SIGUSR2. Got: $httpStatus"
fi
pgrStop
}
replaceConfigValue(){
sed -i "s/.*$1.*/$1 = \"$2\"/g" $3
}
getSocketStatus() {
curl -sL -w "%{http_code}\\n" -o /dev/null --unix-socket /tmp/postgrest.sock http://localhost/
}
socketConnection(){
pgrStart "./configs/unix-socket.config"
while pgrStarted && test "$( getSocketStatus )" -ne 200
do
# wait for the server to start
sleep 0.1
done
if test $( getSocketStatus ) -eq 200
then
ok "Succesfully connected through unix socket"
else
ko "Failed to connect through unix socket"
fi
pgrStop
}
# PRE: curl must be available
test -n "$(command -v curl)" || bailOut 'curl is not available'
# PRE: postgres must be running
psql -l "$POSTGREST_TEST_CONNECTION" 1>/dev/null 2>/dev/null || bailOut 'postgres is not running'
echo "Running IO tests.."
# run dumpConfigIsValid with as many inputs as possible
for cfg in configs/*.config
do
# ROLE_CLAIM_KEY is only used in one of the config files
# using a complex example here, to make sure the quoting works
ROLE_CLAIM_KEY='."https://www.example.com/roles"[0].value' \
dumpedConfigIsValid "$cfg" \
<<< "Y29ubmVjdGlvbl9zdHJpbmc=" # /dev/stdin is read by some config files, one of them expects Base64
done
# run dumpConfigMatchesExpectation with all expectations
for exp in configs/expected/*.config
do
cfg="$(sed -e 's|expected/||' <(echo $exp))"
dumpedConfigMatchesExpectation "$cfg" "$exp"
done
socketConnection
readSecretFromFile word.noeol 'simple (no EOL)'
readSecretFromFile word.txt 'simple'
readSecretFromFile ascii.noeol 'ASCII (no EOL)'
readSecretFromFile ascii.txt 'ASCII'
readSecretFromFile utf8.noeol 'UTF-8 (no EOL)'
readSecretFromFile utf8.txt 'UTF-8'
readSecretFromFile binary.noeol 'binary'
readSecretFromFile binary.eol 'binary (+EOL)'
readSecretFromFile word.b64 'Base64 (simple)'
readSecretFromFile ascii.b64 'Base64 (ASCII)'
readSecretFromFile utf8.b64 'Base64 (UTF-8)'
readSecretFromFile binary.b64 'Base64 (binary)'
eol=$'\x0a'
readDbUriFromStdin "$POSTGREST_TEST_CONNECTION" "(no EOL)"
readDbUriFromStdin "$POSTGREST_TEST_CONNECTION$eol" "(EOL)"
reqWithRoleClaimKey '.postgrest.a_role' '{"postgrest":{"a_role":"postgrest_test_author"}}' 200
reqWithRoleClaimKey '.customObject.manyRoles[1]' '{"customObject":{"manyRoles": ["other", "postgrest_test_author"]}}' 200
reqWithRoleClaimKey '."https://www.example.com/roles"[0].value' '{"https://www.example.com/roles":[{"value":"postgrest_test_author"}]}' 200
reqWithRoleClaimKey '.myDomain[3]' '{"myDomain":["other","postgrest_test_author"]}' 401
reqWithRoleClaimKey '.myRole' '{"role":"postgrest_test_author"}' 401
invalidRoleClaimKey 'role.other'
invalidRoleClaimKey '.role##'
invalidRoleClaimKey '.my_role;;domain'
invalidRoleClaimKey '.#$%&$%/'
invalidRoleClaimKey ''
invalidRoleClaimKey 1234
ensureIatClaimWorks
ensureAppSettings
checkAppSettingsReload
checkJwtSecretReload
checkDbSchemaReload
# TODO: SIGUSR2 tests for other config options
trap - sigint sigterm exit
exit $failedTests