From 49048e29cbf176a19c00e1ff53141b5e99760d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 12 Apr 2024 18:27:59 +0200 Subject: [PATCH 1/3] Clean up and improve structure of integration tests --- tests/integration.bash | 586 ++++++++++++++++++++++++++++++++--------- 1 file changed, 467 insertions(+), 119 deletions(-) diff --git a/tests/integration.bash b/tests/integration.bash index ca8b1d9..7b8b6a8 100755 --- a/tests/integration.bash +++ b/tests/integration.bash @@ -4,140 +4,488 @@ base=${1:-http://localhost:8080} baseWithPort=$(php -r 'echo parse_url($argv[1],PHP_URL_PORT) ? $argv[1] : $argv[1] . ":80";' "$base") n=0 +skipping=false +curl() { + skipping=false + out=$($(which curl) "$@" 2>&1); +} match() { + [[ $skipping == true ]] && return 0 n=$[$n+1] echo "$out" | grep "$@" >/dev/null && echo -n . || \ (echo ""; echo "Error in test $n: Unable to \"grep $@\" this output:"; echo "$out"; exit 1) || exit 1 } notmatch() { + [[ $skipping == true ]] && return 0 n=$[$n+1] echo "$out" | grep "$@" >/dev/null && \ (echo ""; echo "Error in test $n: Expected to NOT \"grep $@\" this output:"; echo "$out") && exit 1 || echo -n . } skipif() { - echo "$out" | grep "$@" >/dev/null && echo -n S && return 1 || return 0 + echo "$out" | grep "$@" >/dev/null && echo -n S && skipping=true || return 0 } skipifnot() { - echo "$out" | grep "$@" >/dev/null && return 0 || echo -n S && return 1 + echo "$out" | grep "$@" >/dev/null && return 0 || echo -n S && skipping=true } -out=$(curl -v $base/ 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" -out=$(curl -v $base/ 2>&1 -X POST); match "HTTP/.* 405" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" - -out=$(curl -v $base/unknown 2>&1); match "HTTP/.* 404" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base/index.php 2>&1); match "HTTP/.* 404" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base/.htaccess 2>&1); match "HTTP/.* 404" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base// 2>&1); match "HTTP/.* 404" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" - -out=$(curl -v $base/error 2>&1); match "HTTP/.* 500" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" && match "Unable to load error" -out=$(curl -v $base/error/null 2>&1); match "HTTP/.* 500" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" - -out=$(curl -v $base/sleep/fiber 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" -out=$(curl -v $base/sleep/coroutine 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" -out=$(curl -v $base/sleep/promise 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" - -out=$(curl -v $base/uri 2>&1); match "HTTP/.* 200" && match "$base/uri" -out=$(curl -v $base/uri/ 2>&1); match "HTTP/.* 200" && match "$base/uri/" -out=$(curl -v $base/uri/foo 2>&1); match "HTTP/.* 200" && match "$base/uri/foo" -out=$(curl -v $base/uri/foo/bar 2>&1); match "HTTP/.* 200" && match "$base/uri/foo/bar" -out=$(curl -v $base/uri/foo//bar 2>&1); match "HTTP/.* 200" && match "$base/uri/foo//bar" -out=$(curl -v $base/uri/Wham! 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham!" -out=$(curl -v $base/uri/Wham%21 2>&1); match "HTTP/.* 200" && match "$base/uri/Wham%21" -out=$(curl -v $base/uri/AC%2FDC 2>&1); skipif "HTTP/.* 404" && match "HTTP/.* 200" && match "$base/uri/AC%2FDC" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) -out=$(curl -v $base/uri/bin%00ary 2>&1); skipif "HTTP/.* 40[04]" && match "HTTP/.* 200" && match "$base/uri/bin%00ary" # skip nginx (400) and Apache (404) -out=$(curl -v $base/uri/AC/DC 2>&1); match "HTTP/.* 200" && match "$base/uri/AC/DC" -out=$(curl -v $base/uri/http://example.com:8080/ 2>&1); match "HTTP/.* 200" && match "$base/uri/http://example.com:8080/" -out=$(curl -v $base/uri? 2>&1); match "HTTP/.* 200" && match "$base/uri" # trailing "?" not reported for empty query string -out=$(curl -v $base/uri?query 2>&1); match "HTTP/.* 200" && match "$base/uri?query" -out=$(curl -v $base/uri?q=a 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a" -out=$(curl -v $base/uri?q=a! 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a!" -out=$(curl -v $base/uri?q=a%21 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a%21" -out=$(curl -v $base/uri?q=w%C3%B6rd 2>&1); match "HTTP/.* 200" && match "$base/uri?q=w%C3%B6rd" -out=$(curl -v $base/uri?q=+ 2>&1); match "HTTP/.* 200" && match "$base/uri?q=+" -out=$(curl -v $base/uri?q=%20 2>&1); match "HTTP/.* 200" && match "$base/uri?q=%20" -out=$(curl -v $base/uri?q=a%2Fb 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a%2Fb" -out=$(curl -v $base/uri?q=a%00b 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a%00b" -out=$(curl -v $base/uri?q=a\&q=b 2>&1); match "HTTP/.* 200" && match "$base/uri?q=a&q=b" -out=$(curl -v $base/uri?q%5B%5D=a\&q%5B%5D=b 2>&1); match "HTTP/.* 200" && match "$base/uri?q%5B%5D=a\&q%5B%5D=b" - -out=$(curl -v $base/query 2>&1); match "HTTP/.* 200" && match "{}" -out=$(curl -v $base/query? 2>&1); match "HTTP/.* 200" && match "{}" -out=$(curl -v $base/query?query 2>&1); match "HTTP/.* 200" && match "{\"query\":\"\"}" -out=$(curl -v $base/query?q=a 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a\"\}" -out=$(curl -v $base/query?q=a! 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a!\"\}" -out=$(curl -v $base/query?q=a%21 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a!\"\}" -out=$(curl -v $base/query?q=w%C3%B6rd 2>&1); match "HTTP/.* 200" && match "{\"q\":\"wörd\"\}" -out=$(curl -v $base/query?q=a+b 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a b\"\}" -out=$(curl -v $base/query?q=a%20b 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a b\"\}" -out=$(curl -v $base/query?q=a%2Fb 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a/b\"\}" -out=$(curl -v $base/query?q=a%00b 2>&1); match "HTTP/.* 200" && match "{\"q\":\"a\\\\u0000b\"\}" -out=$(curl -v $base/query?q=a\&q=b 2>&1); match "HTTP/.* 200" && match "{\"q\":\"b\"}" -out=$(curl -v $base/query?q%5B%5D=a\&q%5B%5D=b 2>&1); match "HTTP/.* 200" && match "{\"q\":[[]\"a\",\"b\"[]]}" - -out=$(curl -v $base/users/foo 2>&1); match "HTTP/.* 200" && match "Hello foo!" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" -out=$(curl -v $base/users/w%C3%B6rld 2>&1); match "HTTP/.* 200" && match "Hello wörld!" -out=$(curl -v $base/users/w%F6rld 2>&1); match "HTTP/.* 200" && match "Hello w�rld!" # demo expects UTF-8 instead of ISO-8859-1 -out=$(curl -v $base/users/a+b 2>&1); match "HTTP/.* 200" && match "Hello a+b!" -out=$(curl -v $base/users/Wham! 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" -out=$(curl -v $base/users/Wham%21 2>&1); match "HTTP/.* 200" && match "Hello Wham!!" -out=$(curl -v $base/users/AC%2FDC 2>&1); skipif "HTTP/.* 404" && match "HTTP/.* 200" && match "Hello AC/DC!" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) -out=$(curl -v $base/users/bi%00n 2>&1); skipif "HTTP/.* 40[04]" && match "HTTP/.* 200" && match "Hello bi�n!" # skip nginx (400) and Apache (404) - -out=$(curl -v $base/users 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/users/ 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/users/a/b 2>&1); match "HTTP/.* 404" - -out=$(curl -v $base/robots.txt 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" -out=$(curl -v $base/source 2>&1); match -i "Location: /source/" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base/source/ 2>&1); match "HTTP/.* 200" -out=$(curl -v $base/source/composer.json 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: application/json[\r\n]" -out=$(curl -v $base/source/public/robots.txt 2>&1); match "HTTP/.* 200" && match -iP "Content-Type: text/plain[\r\n]" -out=$(curl -v $base/source/public/robots.txt/ 2>&1); match -i "Location: ../robots.txt" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base/source/public/robots.txt// 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source//public/robots.txt 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source/public 2>&1); match -i "Location: public/" && match -iP "Content-Type: text/html; charset=utf-8[\r\n]" -out=$(curl -v $base/source/invalid 2>&1); match "HTTP/.* 404" -out=$(curl -v $base/source/bin%00ary 2>&1); match "HTTP/.* 40[40]" # expects 404, but not processed with nginx (400) and Apache (404) - -out=$(curl -v $base/method 2>&1); match "HTTP/.* 200" && match "GET" -out=$(curl -v $base/method -I 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 5[\r\n]" # HEAD has no response body -out=$(curl -v $base/method -X POST 2>&1); match "HTTP/.* 200" && match "POST" -out=$(curl -v $base/method -X PUT 2>&1); match "HTTP/.* 200" && match "PUT" -out=$(curl -v $base/method -X PATCH 2>&1); match "HTTP/.* 200" && match "PATCH" -out=$(curl -v $base/method -X DELETE 2>&1); match "HTTP/.* 200" && match "DELETE" -out=$(curl -v $base/method -X OPTIONS 2>&1); match "HTTP/.* 200" && match "OPTIONS" -out=$(curl -v $base -X OPTIONS --request-target "*" 2>&1); skipif "Server: nginx" && match "HTTP/.* 200" # skip nginx (400) - -out=$(curl -v $base/method/get 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 4[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: false[\r\n]" && match -P "GET$" -out=$(curl -v $base/method/get -I 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 4[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: true[\r\n]" -out=$(curl -v $base/method/head 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 5[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: false[\r\n]" && match -P "HEAD$" -out=$(curl -v $base/method/head -I 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 5[\r\n]" && match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" && match -iP "X-Is-Head: true[\r\n]" - -out=$(curl -v $base/etag/ 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 0[\r\n]" && match -iP "Etag: \"_\"" -out=$(curl -v $base/etag/ -H 'If-None-Match: "_"' 2>&1); match "HTTP/.* 304" && notmatch -i "Content-Length" && match -iP "Etag: \"_\"" -out=$(curl -v $base/etag/a 2>&1); match "HTTP/.* 200" && match -iP "Content-Length: 2[\r\n]" && match -iP "Etag: \"a\"" -out=$(curl -v $base/etag/a -H 'If-None-Match: "a"' 2>&1); skipif "Server: Apache" && match "HTTP/.* 304" && match -iP "Content-Length: 2[\r\n]" && match -iP "Etag: \"a\"" # skip Apache (no Content-Length) - -out=$(curl -v $base/headers -H 'Accept: text/html' 2>&1); match "HTTP/.* 200" && match "\"Accept\": \"text/html\"" -out=$(curl -v $base/headers -d 'name=Alice' 2>&1); match "HTTP/.* 200" && match "\"Content-Type\": \"application/x-www-form-urlencoded\"" && match "\"Content-Length\": \"10\"" -out=$(curl -v $base/headers -u user:pass 2>&1); match "HTTP/.* 200" && match "\"Authorization\": \"Basic dXNlcjpwYXNz\"" -out=$(curl -v $base/headers 2>&1); match "HTTP/.* 200" && notmatch -i "\"Content-Type\"" && notmatch -i "\"Content-Length\"" -out=$(curl -v $base/headers -H User-Agent: -H Accept: -H Host: -10 2>&1); match "HTTP/.* 200" && match "{}" -out=$(curl -v $base/headers -H 'Content-Length: 0' 2>&1); match "HTTP/.* 200" && match "\"Content-Length\": \"0\"" -out=$(curl -v $base/headers -H 'Empty;' 2>&1); match "HTTP/.* 200" && match "\"Empty\": \"\"" -out=$(curl -v $base/headers -H 'Content-Type;' 2>&1); skipif "Server: Apache" && match "HTTP/.* 200" && match "\"Content-Type\": \"\"" # skip Apache (discards empty Content-Type) -out=$(curl -v $base/headers -H 'DNT: 1' 2>&1); skipif "Server: nginx" && match "HTTP/.* 200" && match "\"DNT\"" && notmatch "\"Dnt\"" # skip nginx which doesn't report original case (DNT->Dnt) -out=$(curl -v $base/headers -H 'V: a' -H 'V: b' 2>&1); skipif "Server: nginx" && skipifnot "Server:" && match "HTTP/.* 200" && match "\"V\": \"a, b\"" # skip nginx (last only) and PHP webserver (first only) - -out=$(curl -v $base/set-cookie 2>&1); match "HTTP/.* 200" && match "Set-Cookie: 1=1" && match "Set-Cookie: 2=2" - -out=$(curl -v --proxy $baseWithPort $base/debug 2>&1); skipif "Server: nginx" && match "HTTP/.* 400" # skip nginx (continues like direct request) -out=$(curl -v --proxy $baseWithPort -p $base/debug 2>&1); skipif "CONNECT aborted" && match "HTTP/.* 400" # skip PHP development server (rejects as "Malformed HTTP request") - -out=$(curl -v $base/location/201 2>&1); match "HTTP/.* 201" && match "Location: /foobar" -out=$(curl -v $base/location/202 2>&1); match "HTTP/.* 202" && match "Location: /foobar" -out=$(curl -v $base/location/301 2>&1); match "HTTP/.* 301" && match "Location: /foobar" -out=$(curl -v $base/location/302 2>&1); match "HTTP/.* 302" && match "Location: /foobar" +# check index endpoint + +curl -v $base/ +match "HTTP/.* 200" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" + +curl -v $base/ -X POST +match "HTTP/.* 405" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +# check unknown endpoints return `404 Not Found` + +curl -v $base/unknown +match "HTTP/.* 404" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base/index.php +match "HTTP/.* 404" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base/.htaccess +match "HTTP/.* 404" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base// +match "HTTP/.* 404" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +# check endpoints that intentionally return an error + +curl -v $base/error +match "HTTP/.* 500" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" +match "Unable to load error" + +curl -v $base/error/null +match "HTTP/.* 500" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +# check async fibers + coroutines + promises + +curl -v $base/sleep/fiber +match "HTTP/.* 200" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" + +curl -v $base/sleep/coroutine +match "HTTP/.* 200" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" + +curl -v $base/sleep/promise +match "HTTP/.* 200" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" + +# check URIs with special characters and encoding + +curl -v $base/uri +match "HTTP/.* 200" +match "$base/uri" + +curl -v $base/uri/ +match "HTTP/.* 200" +match "$base/uri/" + +curl -v $base/uri/foo +match "HTTP/.* 200" +match "$base/uri/foo" + +curl -v $base/uri/foo/bar +match "HTTP/.* 200" +match "$base/uri/foo/bar" + +curl -v $base/uri/foo//bar +match "HTTP/.* 200" +match "$base/uri/foo//bar" + +curl -v $base/uri/Wham! +match "HTTP/.* 200" +match "$base/uri/Wham!" + +curl -v $base/uri/Wham%21 +match "HTTP/.* 200" +match "$base/uri/Wham%21" + +curl -v $base/uri/AC%2FDC +skipif "HTTP/.* 404" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +match "HTTP/.* 200" +match "$base/uri/AC%2FDC" + +curl -v $base/uri/bin%00ary +skipif "HTTP/.* 40[04]" # skip nginx (400) and Apache (404) +match "HTTP/.* 200" +match "$base/uri/bin%00ary" + +curl -v $base/uri/AC/DC +match "HTTP/.* 200" +match "$base/uri/AC/DC" + +curl -v $base/uri/http://example.com:8080/ +match "HTTP/.* 200" +match "$base/uri/http://example.com:8080/" + +curl -v $base/uri? +match "HTTP/.* 200" +match "$base/uri" # trailing "?" not reported for empty query string + +curl -v $base/uri?query +match "HTTP/.* 200" +match "$base/uri?query" + +curl -v $base/uri?q=a +match "HTTP/.* 200" +match "$base/uri?q=a" + +curl -v $base/uri?q=a! +match "HTTP/.* 200" +match "$base/uri?q=a!" + +curl -v $base/uri?q=a%21 +match "HTTP/.* 200" +match "$base/uri?q=a%21" + +curl -v $base/uri?q=w%C3%B6rd +match "HTTP/.* 200" +match "$base/uri?q=w%C3%B6rd" + +curl -v $base/uri?q=+ +match "HTTP/.* 200" +match "$base/uri?q=+" + +curl -v $base/uri?q=%20 +match "HTTP/.* 200" +match "$base/uri?q=%20" + +curl -v $base/uri?q=a%2Fb +match "HTTP/.* 200" +match "$base/uri?q=a%2Fb" + +curl -v $base/uri?q=a%00b +match "HTTP/.* 200" +match "$base/uri?q=a%00b" + +curl -v $base/uri?q=a\&q=b +match "HTTP/.* 200" +match "$base/uri?q=a&q=b" + +curl -v $base/uri?q%5B%5D=a\&q%5B%5D=b +match "HTTP/.* 200" +match "$base/uri?q%5B%5D=a\&q%5B%5D=b" + +# check query strings with special characters and encoding + +curl -v $base/query +match "HTTP/.* 200" +match "{}" + +curl -v $base/query? +match "HTTP/.* 200" +match "{}" + +curl -v $base/query?query +match "HTTP/.* 200" +match "{\"query\":\"\"}" + +curl -v $base/query?q=a +match "HTTP/.* 200" +match "{\"q\":\"a\"\}" + +curl -v $base/query?q=a! +match "HTTP/.* 200" +match "{\"q\":\"a!\"\}" + +curl -v $base/query?q=a%21 +match "HTTP/.* 200" +match "{\"q\":\"a!\"\}" + +curl -v $base/query?q=w%C3%B6rd +match "HTTP/.* 200" +match "{\"q\":\"wörd\"\}" + +curl -v $base/query?q=a+b +match "HTTP/.* 200" +match "{\"q\":\"a b\"\}" + +curl -v $base/query?q=a%20b +match "HTTP/.* 200" +match "{\"q\":\"a b\"\}" + +curl -v $base/query?q=a%2Fb +match "HTTP/.* 200" +match "{\"q\":\"a/b\"\}" + +curl -v $base/query?q=a%00b +match "HTTP/.* 200" +match "{\"q\":\"a\\\\u0000b\"\}" + +curl -v $base/query?q=a\&q=b +match "HTTP/.* 200" +match "{\"q\":\"b\"}" + +curl -v $base/query?q%5B%5D=a\&q%5B%5D=b +match "HTTP/.* 200" +match "{\"q\":[[]\"a\",\"b\"[]]}" + +# check endpoint accepting single placeholder with special characters and encoding + +curl -v $base/users/foo +match "HTTP/.* 200" +match "Hello foo!" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" + +curl -v $base/users/w%C3%B6rld +match "HTTP/.* 200" +match "Hello wörld!" + +curl -v $base/users/w%F6rld +match "HTTP/.* 200" +match "Hello w�rld!" # demo expects UTF-8 instead of ISO-8859-1 + +curl -v $base/users/a+b +match "HTTP/.* 200" +match "Hello a+b!" + +curl -v $base/users/Wham! +match "HTTP/.* 200" +match "Hello Wham!!" + +curl -v $base/users/Wham%21 +match "HTTP/.* 200" +match "Hello Wham!!" + +curl -v $base/users/AC%2FDC +skipif "HTTP/.* 404" # skip Apache (404 unless `AllowEncodedSlashes NoDecode`) +match "HTTP/.* 200" +match "Hello AC/DC!" + +curl -v $base/users/bi%00n +skipif "HTTP/.* 40[04]" # skip nginx (400) and Apache (404) +match "HTTP/.* 200" +match "Hello bi�n!" + +curl -v $base/users +match "HTTP/.* 404" + +curl -v $base/users/ +match "HTTP/.* 404" + +curl -v $base/users/a/b +match "HTTP/.* 404" + +# check filesystem access + +curl -v $base/robots.txt +match "HTTP/.* 200" +match -iP "Content-Type: text/plain[\r\n]" + +curl -v $base/source +match -i "Location: /source/" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base/source/ +match "HTTP/.* 200" + +curl -v $base/source/composer.json +match "HTTP/.* 200" +match -iP "Content-Type: application/json[\r\n]" + +curl -v $base/source/public/robots.txt +match "HTTP/.* 200" +match -iP "Content-Type: text/plain[\r\n]" + +curl -v $base/source/public/robots.txt/ +match -i "Location: ../robots.txt" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base/source/public/robots.txt// +match "HTTP/.* 404" + +curl -v $base/source//public/robots.txt +match "HTTP/.* 404" + +curl -v $base/source/public +match -i "Location: public/" +match -iP "Content-Type: text/html; charset=utf-8[\r\n]" + +curl -v $base/source/invalid +match "HTTP/.* 404" + +curl -v $base/source/bin%00ary +match "HTTP/.* 40[40]" # expects 404, but not processed with nginx (400) and Apache (404) + +# check different request methods + +curl -v $base/method +match "HTTP/.* 200" +match "GET" + +curl -v $base/method -I +match "HTTP/.* 200" +match -iP "Content-Length: 5[\r\n]" # HEAD has no response body + +curl -v $base/method -X POST +match "HTTP/.* 200" +match "POST" + +curl -v $base/method -X PUT +match "HTTP/.* 200" +match "PUT" + +curl -v $base/method -X PATCH +match "HTTP/.* 200" +match "PATCH" + +curl -v $base/method -X DELETE +match "HTTP/.* 200" +match "DELETE" + +curl -v $base/method -X OPTIONS +match "HTTP/.* 200" +match "OPTIONS" + +curl -v $base -X OPTIONS --request-target "*" # OPTIONS * HTTP/1.1 +skipif "Server: nginx" # skip nginx (400) +match "HTTP/.* 200" + +curl -v $base/method/get +match "HTTP/.* 200" +match -iP "Content-Length: 4[\r\n]" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" +match -iP "X-Is-Head: false[\r\n]" +match -P "GET$" + +curl -v $base/method/get -I +match "HTTP/.* 200" +match -iP "Content-Length: 4[\r\n]" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" +match -iP "X-Is-Head: true[\r\n]" + +curl -v $base/method/head +match "HTTP/.* 200" +match -iP "Content-Length: 5[\r\n]" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" +match -iP "X-Is-Head: false[\r\n]" +match -P "HEAD$" + +curl -v $base/method/head -I +match "HTTP/.* 200" +match -iP "Content-Length: 5[\r\n]" +match -iP "Content-Type: text/plain; charset=utf-8[\r\n]" +match -iP "X-Is-Head: true[\r\n]" + +# check ETag caching headers + +curl -v $base/etag/ +match "HTTP/.* 200" +match -iP "Content-Length: 0[\r\n]" +match -iP "Etag: \"_\"" + +curl -v $base/etag/ -H 'If-None-Match: "_"' +match "HTTP/.* 304" +notmatch -i "Content-Length" +match -iP "Etag: \"_\"" + +curl -v $base/etag/a +match "HTTP/.* 200" +match -iP "Content-Length: 2[\r\n]" +match -iP "Etag: \"a\"" + +curl -v $base/etag/a -H 'If-None-Match: "a"' +skipif "Server: Apache" # skip Apache (no Content-Length) +match "HTTP/.* 304" +match -iP "Content-Length: 2[\r\n]" +match -iP "Etag: \"a\"" + +# check HTTP request headers + +curl -v $base/headers -H 'Accept: text/html' +match "HTTP/.* 200" +match "\"Accept\": \"text/html\"" + +curl -v $base/headers -d 'name=Alice' +match "HTTP/.* 200" +match "\"Content-Type\": \"application/x-www-form-urlencoded\"" +match "\"Content-Length\": \"10\"" + +curl -v $base/headers -u user:pass +match "HTTP/.* 200" +match "\"Authorization\": \"Basic dXNlcjpwYXNz\"" + +curl -v $base/headers +match "HTTP/.* 200" +notmatch -i "\"Content-Type\"" +notmatch -i "\"Content-Length\"" + +curl -v $base/headers -H User-Agent: -H Accept: -H Host: -10 +match "HTTP/.* 200" +match "{}" + +curl -v $base/headers -H 'Content-Length: 0' +match "HTTP/.* 200" +match "\"Content-Length\": \"0\"" + +curl -v $base/headers -H 'Empty;' +match "HTTP/.* 200" +match "\"Empty\": \"\"" + +curl -v $base/headers -H 'Content-Type;' +skipif "Server: Apache" # skip Apache (discards empty Content-Type) +match "HTTP/.* 200" +match "\"Content-Type\": \"\"" + +curl -v $base/headers -H 'DNT: 1' +skipif "Server: nginx" # skip nginx which doesn't report original case (DNT->Dnt) +match "HTTP/.* 200" +match "\"DNT\"" +notmatch "\"Dnt\"" + +curl -v $base/headers -H 'V: a' -H 'V: b' +skipif "Server: nginx" # skip nginx (last only) and PHP webserver (first only) +skipifnot "Server:" +match "HTTP/.* 200" +match "\"V\": \"a, b\"" + +# check HTTP response with multiple cookie headers + +curl -v $base/set-cookie +match "HTTP/.* 200" +match "Set-Cookie: 1=1" +match "Set-Cookie: 2=2" + +# check rejecting HTTP proxy requests + +curl -v --proxy $baseWithPort $base/debug +skipif "Server: nginx" # skip nginx (continues like direct request) +match "HTTP/.* 400" + +curl -v --proxy $baseWithPort -p $base/debug +skipif "CONNECT aborted" # skip PHP development server (rejects as "Malformed HTTP request") +match "HTTP/.* 400" + +# check HTTP redirects + +curl -v $base/location/201 +match "HTTP/.* 201" +match "Location: /foobar" + +curl -v $base/location/202 +match "HTTP/.* 202" +match "Location: /foobar" + +curl -v $base/location/301 +match "HTTP/.* 301" +match "Location: /foobar" + +curl -v $base/location/302 +match "HTTP/.* 302" +match "Location: /foobar" + +# end echo "OK ($n)" From 9a5b910b21a1a618a8dc91badf0b575fc5f8b8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 12 Apr 2024 18:54:41 +0200 Subject: [PATCH 2/3] Rename script awaiting HTTP port to `await.bash` --- .github/workflows/ci.yml | 12 ++++++------ tests/{await.sh => await.bash} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename tests/{await.sh => await.bash} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6528db4..ad8169c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: php-version: ${{ matrix.php }} - run: composer install -d tests/integration/ - run: php tests/integration/public/index.php & - - run: bash tests/await.sh + - run: bash tests/await.bash - run: bash tests/integration.bash Docker: @@ -104,7 +104,7 @@ jobs: - run: composer install -d tests/integration/ - run: docker build -f tests/integration/${{ matrix.dockerfile }} tests/integration/ - run: docker run -d -p 8080:8080 -v "$PWD/composer.json":/app/composer.json $(docker images -q | head -n1) - - run: bash tests/await.sh + - run: bash tests/await.bash - run: bash tests/integration.bash - run: docker stop $(docker ps -qn1) - run: docker logs $(docker ps -qn1) @@ -129,7 +129,7 @@ jobs: - run: docker build -f tests/integration/Dockerfile-basics tests/integration/ - run: docker run -d -p 8080:8080 -v "$PWD/composer.json":/app/composer.json $(docker images -q | head -n1) - run: docker run -d --net=host -v "$PWD/tests/integration/":/home/framework-x/ -v "$PWD"/tests/integration/${{ matrix.config.path }}:/etc/nginx/conf.d/default.conf nginx:stable-alpine - - run: bash tests/await.sh http://localhost + - run: bash tests/await.bash http://localhost - run: bash tests/integration.bash http://localhost - run: docker stop $(docker ps -qn2) - run: docker logs $(docker ps -qn1) @@ -159,7 +159,7 @@ jobs: - run: composer install -d tests/integration/ - run: docker run -d -v "$PWD/tests/integration/":/home/framework-x/ php:${{ matrix.php }}-fpm - run: docker run -d -p 80:80 --link $(docker ps -qn1):php -v "$PWD/tests/integration/":/home/framework-x/ -v "$PWD"/tests/integration/nginx-fpm.conf:/etc/nginx/conf.d/default.conf nginx:stable-alpine - - run: bash tests/await.sh http://localhost + - run: bash tests/await.bash http://localhost - run: bash tests/integration.bash http://localhost - run: docker logs $(docker ps -qn1) if: ${{ always() }} @@ -185,7 +185,7 @@ jobs: php-version: ${{ matrix.php }} - run: composer install -d tests/integration/ - run: docker run -d -p 80:80 -v "$PWD/tests/integration/":/home/framework-x/ php:${{ matrix.php }}-apache sh -c "rmdir /var/www/html;ln -s /home/framework-x/public /var/www/html;ln -s /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled; apache2-foreground" - - run: bash tests/await.sh http://localhost + - run: bash tests/await.bash http://localhost - run: bash tests/integration.bash http://localhost - run: docker logs $(docker ps -qn1) if: ${{ always() }} @@ -211,5 +211,5 @@ jobs: php-version: ${{ matrix.php }} - run: composer install -d tests/integration/ - run: php -S localhost:8080 tests/integration/public/index.php & - - run: bash tests/await.sh + - run: bash tests/await.bash - run: bash tests/integration.bash diff --git a/tests/await.sh b/tests/await.bash similarity index 100% rename from tests/await.sh rename to tests/await.bash From 12063e93c18066b05731fafb3c59479b05091089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 12 Apr 2024 18:41:47 +0200 Subject: [PATCH 3/3] Ignore trailing slash for integration tests and increase wait timeout --- .github/workflows/ci.yml | 12 ++++++------ README.md | 2 +- tests/await.bash | 5 +++-- tests/integration.bash | 3 ++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad8169c..4f08560 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,8 +129,8 @@ jobs: - run: docker build -f tests/integration/Dockerfile-basics tests/integration/ - run: docker run -d -p 8080:8080 -v "$PWD/composer.json":/app/composer.json $(docker images -q | head -n1) - run: docker run -d --net=host -v "$PWD/tests/integration/":/home/framework-x/ -v "$PWD"/tests/integration/${{ matrix.config.path }}:/etc/nginx/conf.d/default.conf nginx:stable-alpine - - run: bash tests/await.bash http://localhost - - run: bash tests/integration.bash http://localhost + - run: bash tests/await.bash http://localhost/ + - run: bash tests/integration.bash http://localhost/ - run: docker stop $(docker ps -qn2) - run: docker logs $(docker ps -qn1) if: ${{ always() }} @@ -159,8 +159,8 @@ jobs: - run: composer install -d tests/integration/ - run: docker run -d -v "$PWD/tests/integration/":/home/framework-x/ php:${{ matrix.php }}-fpm - run: docker run -d -p 80:80 --link $(docker ps -qn1):php -v "$PWD/tests/integration/":/home/framework-x/ -v "$PWD"/tests/integration/nginx-fpm.conf:/etc/nginx/conf.d/default.conf nginx:stable-alpine - - run: bash tests/await.bash http://localhost - - run: bash tests/integration.bash http://localhost + - run: bash tests/await.bash http://localhost/ + - run: bash tests/integration.bash http://localhost/ - run: docker logs $(docker ps -qn1) if: ${{ always() }} @@ -185,8 +185,8 @@ jobs: php-version: ${{ matrix.php }} - run: composer install -d tests/integration/ - run: docker run -d -p 80:80 -v "$PWD/tests/integration/":/home/framework-x/ php:${{ matrix.php }}-apache sh -c "rmdir /var/www/html;ln -s /home/framework-x/public /var/www/html;ln -s /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled; apache2-foreground" - - run: bash tests/await.bash http://localhost - - run: bash tests/integration.bash http://localhost + - run: bash tests/await.bash http://localhost/ + - run: bash tests/integration.bash http://localhost/ - run: docker logs $(docker ps -qn1) if: ${{ always() }} diff --git a/README.md b/README.md index 78a2686..853285f 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ your installation like this: ```bash $ php tests/integration/public/index.php -$ tests/integration.bash http://localhost:8080 +$ tests/integration.bash http://localhost:8080/ ``` ## License diff --git a/tests/await.bash b/tests/await.bash index 442abf6..5e7812a 100755 --- a/tests/await.bash +++ b/tests/await.bash @@ -1,8 +1,9 @@ #!/bin/bash -base=${1:-http://localhost:8080} +base=${1:-http://localhost:8080/} +base=${base%/} -for i in {1..20} +for i in {1..600} do out=$(curl -v -X PROBE $base/ 2>&1) && exit 0 || echo -n . sleep 0.1 diff --git a/tests/integration.bash b/tests/integration.bash index 7b8b6a8..c2800c6 100755 --- a/tests/integration.bash +++ b/tests/integration.bash @@ -1,6 +1,7 @@ #!/bin/bash -base=${1:-http://localhost:8080} +base=${1:-http://localhost:8080/} +base=${base%/} baseWithPort=$(php -r 'echo parse_url($argv[1],PHP_URL_PORT) ? $argv[1] : $argv[1] . ":80";' "$base") n=0