diff --git a/api-test.sh b/api-test.sh index cb94ddc..c2f1b7c 100755 --- a/api-test.sh +++ b/api-test.sh @@ -4,6 +4,7 @@ set -o pipefail RED=$(tput setaf 1) GREEN=$(tput setaf 2) BOLD=$(tput bold) +UNDERLINE=$(tput smul) RESET=$(tput sgr 0) ACTION="" @@ -23,12 +24,17 @@ HEADER_ONLY=0 SILENT=0 API_ERROR=0 +# Helper methods echo_v() { if [ $VERBOSE -eq 1 ]; then echo $1 fi } +echo_t() { + printf "\t%s\n" "$1" +} + bytes_to_human() { b=${1:-0} d='' @@ -42,86 +48,51 @@ bytes_to_human() { echo "$b$d ${S[$s]}" } -run() { - for arg in "$@"; do - case $arg in - -i | --include) - SHOW_HEADER=1 - shift - ;; - -I | --header-only) - HEADER_ONLY=1 - shift - ;; - -s | --silent) - SILENT=1 - shift - ;; - -h | --help) - usage run - exit - ;; - esac - done - +color_response() { case $1 in - all) - api_factory "$(jq -r '.testCases | keys[]' $FILE)" - ;; - *) - api_factory $@ - ;; + 2[0-9][0-9]) echo $GREEN ;; + [45][0-9][0-9]) echo $RED ;; + *) ;; esac } -api_factory() { - for TEST_CASE in $@; do - API_ERROR=0 - echo "${BOLD}Running Case:${RESET} $TEST_CASE" - echo_v "${BOLD}Description: ${RESET}$(jq -r ".testCases.$TEST_CASE.description" $FILE)" - echo_v "${BOLD}Action: ${RESET}$(jq -r ".testCases.$TEST_CASE.method //\"GET\" | ascii_upcase" $FILE) $(jq -r ".testCases.$TEST_CASE.path" $FILE)" - call_api $TEST_CASE - display_results +# Show usage +function usage() { + case $1 in + run) + echo "USAGE: $COMMAND_NAME [-v] -f file_name run [-hiIs] [ARGS]" echo "" + echo "OPTIONS:" + echo " -h (--help) print this message" + echo " -i (--include) include header" + echo " -I (--header-only) header only" + echo " -s (--silent) silent mode" echo "" - done -} - -display_results() { - - if [[ $API_ERROR == 1 ]]; then - return - fi - - local res=$(jq -r '.http_status + " " + .http_message ' <<<"$RESPONSE_HEADER") - local status=$(jq -r '.http_status' <<<"$RESPONSE_HEADER") - echo "Response:" - echo "${BOLD}$(color_response $status)$res${RESET}" - if [[ $HEADER_ONLY == 1 ]]; then - echo "HEADER:" - echo "$RESPONSE_HEADER" | jq -C '.' - else - if [[ $SHOW_HEADER == 1 ]]; then - echo "HEADER:" - echo "$RESPONSE_HEADER" | jq -C '.' - fi - if [[ $SILENT == 0 ]]; then - echo "BODY:" - echo "$RESPONSE_BODY" | jq -C '.' - fi - fi - echo "META:" - echo "$META" | jq -C '.' -} - -color_response() { - case $1 in - 2[0-9][0-9]) echo $GREEN ;; - [45][0-9][0-9]) echo $RED ;; - *) ;; + echo "ARGS:" + echo " all Run all test case." + echo " Run provided test case." + echo "" + echo "EXAMPLE:" + echo "'api-test -f test.json run test_case_1 test_case_2', 'api-test -f test.json run all'" + exit + ;; + *) + echo "USAGE: $COMMAND_NAME [-hv] -f file_name [CMD] [ARGS]" + echo "" + echo "OPTIONS:" + echo " -h (--help) print this message" + echo " -v (--verbose) verbose logging" + echo " -f (--file) file to test" + echo "" + echo "COMMANDS:" + echo " run Run test cases specified in the test file." + echo " Example: 'api-test -f test.json run test_case_1 test_case_2', 'api-test -f test.json run all'" + exit + ;; esac } +# api methods call_api() { ROUTE=$(jq -r ".testCases.$1.path" $FILE) BODY="$(jq -r ".testCases.$1.body" $FILE)" @@ -152,7 +123,7 @@ call_api() { parse_header "$header" } -function parse_header() { +parse_header() { local RESPONSE=($(echo "$header" | tr '\r' ' ' | sed -n 1p)) local header=$(echo "$header" | sed '1d;$d' | sed 's/: /" : "/' | sed 's/^/"/' | tr '\r' ' ' | sed 's/ $/",/' | sed '1 s/^/{/' | sed '$ s/,$/}/') RESPONSE_HEADER=$(echo "$header" "{ \"http_version\": \"${RESPONSE[0]}\", @@ -161,38 +132,255 @@ function parse_header() { \"http_response\": \"${RESPONSE[@]:0}\" }" | jq -s add) } -# Show usage -function usage() { - case $1 in - run) - echo "USAGE: $COMMAND_NAME [-v] -f file_name run [-hiIs] [ARGS]" +## run specific methods +display_results() { + + if [[ $API_ERROR == 1 ]]; then + return + fi + + local res=$(jq -r '.http_status + " " + .http_message ' <<<"$RESPONSE_HEADER") + local status=$(jq -r '.http_status' <<<"$RESPONSE_HEADER") + echo "Response:" + echo "${BOLD}$(color_response $status)$res${RESET}" + if [[ $HEADER_ONLY == 1 ]]; then + echo "HEADER:" + echo "$RESPONSE_HEADER" | jq -C '.' + else + if [[ $SHOW_HEADER == 1 ]]; then + echo "HEADER:" + echo "$RESPONSE_HEADER" | jq -C '.' + fi + if [[ $SILENT == 0 ]]; then + echo "BODY:" + echo "$RESPONSE_BODY" | jq -C '.' + fi + + fi + echo "META:" + echo "$META" | jq -C '.' +} + +api_factory() { + for TEST_CASE in $@; do + API_ERROR=0 + echo "${BOLD}Running Case:${RESET} $TEST_CASE" + echo_v "${BOLD}Description: ${RESET}$(jq -r ".testCases.$TEST_CASE.description" $FILE)" + echo_v "${BOLD}Action: ${RESET}$(jq -r ".testCases.$TEST_CASE.method //\"GET\" | ascii_upcase" $FILE) $(jq -r ".testCases.$TEST_CASE.path" $FILE)" + call_api $TEST_CASE + display_results echo "" - echo "OPTIONS:" - echo " -h (--help) print this message" - echo " -i (--include) include header" - echo " -I (--header-only) header only" - echo " -s (--silent) silent mode" echo "" - echo "ARGS:" - echo " all Run all test case." - echo " Run provided test case." + done +} + +test_factory() { + for TEST_CASE in $@; do + API_ERROR=0 + echo "${BOLD}Testing Case:${RESET} $TEST_CASE" + echo_v "${BOLD}Description: ${RESET}$(jq -r ".testCases.$TEST_CASE.description" $FILE)" + echo_v "${BOLD}Action: ${RESET}$(jq -r ".testCases.$TEST_CASE.method //\"GET\" | ascii_upcase" $FILE) $(jq -r ".testCases.$TEST_CASE.path" $FILE)" + if [[ -z $(jq -r ".testCases.$TEST_CASE.expect? | select(. !=null)" $FILE) ]]; then + tput cuf 2 + echo "No test cases found" + echo "" + echo "" + continue + fi + call_api $TEST_CASE + if [[ $API_ERROR == 1 ]]; then + continue + fi + + tput cuf 2 + echo "${UNDERLINE}a. Checking condition for header${RESET}" + test_runner $TEST_CASE "header" "$RESPONSE_HEADER" echo "" - echo "EXAMPLE:" - echo "'api-test -f test.json run test_case_1 test_case_2', 'api-test -f test.json run all'" - exit - ;; - *) - echo "USAGE: $COMMAND_NAME [-hv] -f file_name [CMD] [ARGS]" echo "" - echo "OPTIONS:" - echo " -h (--help) print this message" - echo " -v (--verbose) verbose logging" - echo " -f (--file) file to test" + tput cuf 2 + echo "${UNDERLINE}b. Checking condition for body${RESET}" + test_runner $TEST_CASE "body" "$RESPONSE_BODY" echo "" - echo "COMMANDS:" - echo " run Run test cases specified in the test file." - echo " Example: 'api-test -f test.json run test_case_1 test_case_2', 'api-test -f test.json run all'" - exit + echo "" + done +} + +test_runner() { + for test in ""contains eq path_eq path_contains hasKey[]""; do + local TEST_SCENARIO=$(jq -r ".testCases.$1.expect.$2.$test? | select(. !=null)" $FILE) + if [[ -z $TEST_SCENARIO ]]; then + continue + fi + tput cuf 4 + if [[ $test == "contains" ]]; then + echo "Checking contains comparision${RESET}" + contains "$TEST_SCENARIO" "$3" + elif [[ $test == "eq" ]]; then + echo "Checking equality comparision${RESET}" + check_eq "$TEST_SCENARIO" "$3" + elif [[ $test == "path_eq" ]]; then + echo "Checking path equality comparision${RESET}" + path_checker "$TEST_SCENARIO" "$3" + elif [[ $test == "path_contains" ]]; then + echo "Checking path equality comparision${RESET}" + path_checker "$TEST_SCENARIO" "$3" 1 + else + echo "Checking has key comparision${RESET}" + has_key "$TEST_SCENARIO" "$3" + fi + done +} + +contains() { + tput cuf 6 + local check=$(jq --argjson a "$1" --argjson b "$2" -n '$a | select(. != null) | $b | contains($a)') + if [[ $check == "true" ]]; then + echo "${GREEN}${BOLD}Check Passed${RESET}" + else + echo "${RED}${BOLD}Check Failed${RESET}" + echo "EXPECTED:" + echo "${GREEN}$1${RESET}" + echo "GOT:" + echo "${RED}$2${RESET}" + fi +} + +has_key() { + # local paths=$(jq -r 'def path2text($value): + # def tos: if type == "number" then . else "\"\(tojson)\"" end; + # reduce .[] as $segment (""; . + # + ($segment + # | if type == "string" then "." + . else "[\(.)]" end)); + # paths(scalars) as $p + # | getpath($p) as $v + # | $p | path2text($v)' <<<"$2") + local paths=$(jq -r 'path(..)|[.[]|tostring]|join(".")' <<<"$2") + tput cuf 6 + for path in $1; do + local FOUND=0 + for data_path in $paths; do + if [[ "$path" == "$data_path" ]]; then + FOUND=1 + break + fi + done + if [[ $FOUND == 0 ]]; then + echo "${RED}${BOLD}Check Failed${RESET}" + echo "CANNOT FIND KEY:" + echo "${RED}$path${RESET}" + return + fi + done + echo "${GREEN}${BOLD}Check Passed${RESET}" +} + +check_eq() { + tput cuf 6 + local type=$(jq -r --argjson a "$1" -n '$a|type') + local check + if [[ $type == "object" || $type == "array" ]]; then + check=$(jq --argjson a "$1" --argjson b "$2" -n 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); ($a | (post_recurse | arrays) |= sort) as $a | ($b | (post_recurse | arrays) |= sort) as $b | $a == $b') + else + check=$(jq --argjson a "$1" --argjson b "$2" -n '$a == $b') + fi + if [[ $check == "true" ]]; then + echo "${GREEN}${BOLD}Check Passed${RESET}" + else + tput cuf 2 + echo "${RED}${BOLD}Check Failed${RESET}" + echo "EXPECTED:" + echo "${GREEN}$1${RESET}" + echo "GOT:" + echo "${RED}$2${RESET}" + fi +} + +path_checker() { + local keys=$(jq -r --argjson a "$1" -n '$a | keys[]') + if [[ -z "$keys" ]]; then + return + fi + for key in $keys; do + tput cuf 6 + local value=$(jq -c -r --argjson a "$1" -n "\$a | .\"$key\"") + echo "When path is '$key'" + local compare_value=$(jq -r --argjson a "$2" -n "\$a | try .$key catch \"OBJECT_FETCH_ERROR_JQ_API_TEST\"" 2>/dev/null) + if [[ -z "$compare_value" ]]; then + tput cuf 8 + echo "${RED}${BOLD}Check Failed${RESET}" + tput cuf 2 + echo "INVALID PATH SYNTAX: ${RED}data[0]target_id${RESET}" + return + fi + tput cuf 2 + if [[ $3 == 0 ]]; then + check_eq "$value" "$compare_value" + else + contains "$value" "$compare_value" + fi + done +} + +run() { + for arg in "$@"; do + case $arg in + -i | --include) + SHOW_HEADER=1 + shift + ;; + -I | --header-only) + HEADER_ONLY=1 + shift + ;; + -s | --silent) + SILENT=1 + shift + ;; + -h | --help) + usage run + exit + ;; + esac + done + + case $1 in + all) + api_factory "$(jq -r '.testCases | keys[]' $FILE)" + ;; + *) + api_factory $@ + ;; + esac +} + +test() { + for arg in "$@"; do + case $arg in + -i | --include) + SHOW_HEADER=1 + shift + ;; + -I | --header-only) + HEADER_ONLY=1 + shift + ;; + -s | --silent) + SILENT=1 + shift + ;; + -h | --help) + usage run + exit + ;; + esac + done + + case $1 in + all) + test_factory "$(jq -r '.testCases | keys[]' $FILE)" + ;; + *) + test_factory $@ ;; esac } @@ -241,7 +429,7 @@ case $ACTION in run) run $@ ;; -test) ;; +test) test $@ ;; *) usage ;;