From 836846fa7ed0039704b2df6664770bc65b23a183 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Mon, 17 Jul 2023 22:17:19 +0200 Subject: [PATCH] tests(cli): add initial CLI tests --- .github/workflows/test.yaml | 2 + Makefile | 6 +- tests/cli/README.md | 27 ++ tests/cli/fixtures/mock-a-rena-oas.yml | 41 +++ tests/cli/mytestfile.test.sh | 42 +++ tests/cli/test.sh | 451 +++++++++++++++++++++++++ 6 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 tests/cli/README.md create mode 100644 tests/cli/fixtures/mock-a-rena-oas.yml create mode 100755 tests/cli/mytestfile.test.sh create mode 100755 tests/cli/test.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c3a2929c8..281bc9f21 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -30,3 +30,5 @@ jobs: fail_ci_if_error: true - name: Build run: make build + - name: Test CLI + run: make test-cli diff --git a/Makefile b/Makefile index 9767d86d1..245c151f8 100644 --- a/Makefile +++ b/Makefile @@ -49,4 +49,8 @@ setup-kong-ee: test-integration: go test -v -tags=integration \ -race \ - ./tests/integration/... \ No newline at end of file + ./tests/integration/... + +.PHONY: test-cli +test-cli: build + tests/cli/test.sh --suite "decK-cli test suite" diff --git a/tests/cli/README.md b/tests/cli/README.md new file mode 100644 index 000000000..6ea4f57f2 --- /dev/null +++ b/tests/cli/README.md @@ -0,0 +1,27 @@ +# CLI tests + +This suite of tests is not intended to do complex integration tests, but simple +unit-test like tests of the CLI flags. Testing allowed inputs, flag-combinations, etc. + +## Running all tests +Using the `Makefile`; +``` +make test-cli +``` +or directly; +``` +tests/cli/test.sh --suite "decK-cli test suite" +``` + +## Running a single file +``` +tests/cli/mytestfile.test.sh +``` +## Create a new test-file +To create a new testfile run: + +```shell +tests/cli/test.sh --create tests/cli/mytestfile "decK-cli test suite" +``` + +instructions will be in the generated file. diff --git a/tests/cli/fixtures/mock-a-rena-oas.yml b/tests/cli/fixtures/mock-a-rena-oas.yml new file mode 100644 index 000000000..30abd404f --- /dev/null +++ b/tests/cli/fixtures/mock-a-rena-oas.yml @@ -0,0 +1,41 @@ +--- +openapi: 3.1.0 + +info: + description: Mock service for a popular song in the 90s + version: 0.0.1 + title: Mock-a-rena + contact: + name: Los del Rio + +servers: +- url: https://mockbin.org/requests + description: Mock-a-rena service + +tags: +- name: mock + description: Hey.... mock-a-rena! +- name: a-rena + description: tag-arena + +paths: + "/mock": + get: + summary: Mock, mock, mock, mock-a-rena + description: yeah yeah + operationId: mock + tags: + - mock + responses: + '200': + description: Success + "/a-rena": + get: + summary: hey hey + description: a-rena-description + operationId: a-rena + tags: + - marena + responses: + '200': + description: success diff --git a/tests/cli/mytestfile.test.sh b/tests/cli/mytestfile.test.sh new file mode 100755 index 000000000..60e9b1b57 --- /dev/null +++ b/tests/cli/mytestfile.test.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + + +function run_test { + # the suite name below will only be used when running this file directly, when + # running through "test.sh" it must be provided using the "--suite" option. + tinitialize "decK-cli test suite" "${BASH_SOURCE[0]}" + + tchapter "deck file openapi2kong" + + ttest "accepts format 'json'" + ./deck file openapi2kong --format json > /dev/null < tests/cli/fixtures/mock-a-rena-oas.yml + if [ $? -ne 0 ]; then + tfailure "--format=json was not accepted" + else + tsuccess + fi + + ttest "accepts format 'yaml'" + ./deck file openapi2kong --format yaml > /dev/null < tests/cli/fixtures/mock-a-rena-oas.yml + if [ $? -ne 0 ]; then + tfailure "--format=yaml was not accepted" + else + tsuccess + fi + + ttest "--format has a default value" + ./deck file openapi2kong > /dev/null < tests/cli/fixtures/mock-a-rena-oas.yml + if [ $? -ne 0 ]; then + tfailure "unspecified --format was not accepted" + else + tsuccess + fi + + tfinish +} + +# No need to modify anything below this comment + +# shellcheck disable=SC1090 # do not follow source +[[ "$T_PROJECT_NAME" == "" ]] && set -e && if [[ -f "${1:-$(dirname "$(realpath "$0")")/test.sh}" ]]; then source "${1:-$(dirname "$(realpath "$0")")/test.sh}"; else source "${1:-$(dirname "$(realpath "$0")")/run.sh}"; fi && set +e +run_test diff --git a/tests/cli/test.sh b/tests/cli/test.sh new file mode 100755 index 000000000..16e431a43 --- /dev/null +++ b/tests/cli/test.sh @@ -0,0 +1,451 @@ +#!/usr/bin/env bash + +# source: https://github.com/Tieske/test.sh + +if [[ "$T_INIT_COUNT" == "" ]]; then + # first time, initialize global variables + T_PROJECT_NAME="" + T_FILE_NAME="" + T_COUNT_FAILURE=0 + T_COUNT_SUCCESS=0 + T_CURRENT_CHAPTER="" + T_CHAPTER_START_FAILURES=0 + T_CHAPTER_START_SUCCESSES=0 + T_CURRENT_TEST="" + T_COLOR_YELLOW="\033[1;33m" + T_COLOR_RED="\033[0;31m" + T_COLOR_GREEN="\033[1;32m" + T_COLOR_CLEAR="\033[0m" + T_INIT_COUNT=0 + T_FAILURE_ARRAY=() + T_DEBUGGING="" + + figlet -v > /dev/null 2>&1 + T_FIGLET_AVAILABLE=$? +else + # assuming we're being sourced again, just exit + return 0 +fi + + +function texit { + # internal function only + unset T_PROJECT_NAME + unset T_FILE_NAME + unset T_COUNT_FAILURE + unset T_COUNT_SUCCESS + unset T_CURRENT_CHAPTER + unset T_CHAPTER_START_FAILURES + unset T_CHAPTER_START_SUCCESSES + unset T_CURRENT_TEST + unset T_COLOR_YELLOW + unset T_COLOR_RED + unset T_COLOR_GREEN + unset T_COLOR_CLEAR + unset T_INIT_COUNT + unset T_FAILURE_ARRAY + unset T_FIGLET_AVAILABLE + unset T_DEBUGGING + exit "$1" +} + + +function tfooter { + # internal function only + # Arguments: + # 1) successes + # 2) failures + # 3) [optional] boolean; if set Project title, otherwise Chapter title + local indent="" + if [[ "$3" == "" ]]; then + indent=" " + local chapter + if [[ "$T_FILE_NAME" == "" ]]; then + chapter=$T_CURRENT_CHAPTER + else + chapter="$T_CURRENT_CHAPTER ($T_FILE_NAME)" + fi + echo "------------------------------------------------------------------------------------------------------------------------" + echo -e "$T_COLOR_YELLOW$indent""Chapter : $chapter$T_COLOR_CLEAR" + else + echo "========================================================================================================================" + echo -e "$T_COLOR_YELLOW$indent""Project : $T_PROJECT_NAME$T_COLOR_CLEAR" + fi + echo -e "$T_COLOR_YELLOW$indent""Successes: $1$T_COLOR_CLEAR" + echo -e "$T_COLOR_YELLOW$indent""Failures : $2$T_COLOR_CLEAR" +} + + +function theader { + # internal function only + local header=$1 + echo "========================================================================================================================" + if [ "$T_FIGLET_AVAILABLE" -eq 0 ]; then + figlet -c -w 120 "$header" + else + printf "%*s\n" $(( (${#header} + 120) / 2)) "$header" + fi + echo "========================================================================================================================" +} + + +function tmessage { + echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME [ info ] $*$T_COLOR_CLEAR" +} + + +function tdebug { + if [[ "$T_CURRENT_TEST" == "" ]]; then + echo "calling tdebug without a test, call ttest first" + exit 1 + fi + T_DEBUGGING=true + set -x +} + + +function tinitialize { + # Initializes either a test suite or a single test file. Every tinitialize + # MUST be followed by a tfinish, after the tests are completed. + # Arguments: + # 1) [required] name of the test suite (ignored if already set) + # 2) [optional] filename of the testfile + if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then + echo "calling tinitialize after testing already started" + exit 1 + fi + if [[ "$1" == "" ]]; then + echo "calling tinitialize without project name" + exit 1 + fi + + if [[ $T_INIT_COUNT -eq 0 ]]; then + # first time we're being initialized + T_PROJECT_NAME=$1 + theader "$T_PROJECT_NAME" + ((T_INIT_COUNT = T_INIT_COUNT + 1)) + else + # we're being called multiple times, because multiple files run in a row + # do not intialize again, just update the counter + ((T_INIT_COUNT = T_INIT_COUNT + 1)) + fi + T_FILE_NAME=$2 +} + + +function tchapter { + # Initializes a test chapter. + # Call after tinitialize, and before ttest. + if [[ ! "$T_CURRENT_TEST" == "" ]]; then + echo "calling tchapter while test is unfinished, call tfailure or tsuccess first" + exit 1 + fi + if [[ "$1" == "" ]]; then + echo "calling tchapter without chapter name" + exit 1 + fi + + if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then + tfooter $((T_COUNT_SUCCESS - T_CHAPTER_START_SUCCESSES)) $((T_COUNT_FAILURE - T_CHAPTER_START_FAILURES)) + fi + + T_CURRENT_CHAPTER="$*" + T_CHAPTER_START_FAILURES=$T_COUNT_FAILURE + T_CHAPTER_START_SUCCESSES=$T_COUNT_SUCCESS + + theader "$T_CURRENT_CHAPTER" +} + + +function ttest { + # Marks the start of a test. + # The test MUST be finished with either tsuccess or tfailure. + # Arguments: + # 1) name of the test + if [[ "$T_CURRENT_CHAPTER" == "" ]]; then + echo "calling ttest without chapter, call tchapter first" + exit 1 + fi + if [[ ! "$T_CURRENT_TEST" == "" ]]; then + echo "calling ttest while previous test is unfinished, call tfailure or tsuccess first" + exit 1 + fi + if [[ "$1" == "" ]]; then + echo "calling ttest without test description" + exit 1 + fi + T_CURRENT_TEST="$*" + echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME [ start ] $T_CURRENT_CHAPTER: $T_CURRENT_TEST$T_COLOR_CLEAR" +} + + +function tfailure { + # Marks the end of a test, with a failure. + # no arguments + if [[ "$T_DEBUGGING" == "true" ]]; then set +x; T_DEBUGGING=""; fi + if [[ "$T_CURRENT_TEST" == "" ]]; then + echo "calling tfailure without a test, call ttest first" + exit 1 + fi + [[ ! "$1" == "" ]] && tmessage "$*" + local failure="$T_CURRENT_CHAPTER: $T_CURRENT_TEST" + echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME$T_COLOR_RED [ failed ]$T_COLOR_YELLOW $failure$T_COLOR_CLEAR" + + if [[ ! $T_FILE_NAME == "" ]]; then + failure="$failure ($T_FILE_NAME)" + fi + + T_FAILURE_ARRAY+=("$failure") + + ((T_COUNT_FAILURE = T_COUNT_FAILURE + 1)) + T_CURRENT_TEST="" +} + + +function tsuccess { + # Marks the end of a test, as a success. + # no arguments + if [[ "$T_DEBUGGING" == "true" ]]; then set +x; T_DEBUGGING=""; fi + if [[ "$T_CURRENT_TEST" == "" ]]; then + echo "calling tsuccess without a test, call ttest first" + exit 1 + fi + [[ ! "$1" == "" ]] && tmessage "$*" + echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME$T_COLOR_GREEN [ success ]$T_COLOR_YELLOW $T_CURRENT_CHAPTER: $T_CURRENT_TEST$T_COLOR_CLEAR" + ((T_COUNT_SUCCESS = T_COUNT_SUCCESS + 1)) + T_CURRENT_TEST="" +} + + +function tfinish { + # Finishes either a test suite or a single test file. + # no arguments + if [[ ! "$T_CURRENT_TEST" == "" ]]; then + echo "calling tfinish while test is unfinished, call tfailure or tsuccess first" + exit 1 + fi + + if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then + tfooter $((T_COUNT_SUCCESS - T_CHAPTER_START_SUCCESSES)) $((T_COUNT_FAILURE - T_CHAPTER_START_FAILURES)) + T_CURRENT_CHAPTER="" + fi + + ((T_INIT_COUNT = T_INIT_COUNT - 1)) + if [[ $T_INIT_COUNT -eq 0 ]]; then + # this was the last testfile running, so actually wrap it up + tfooter $((T_COUNT_SUCCESS)) $((T_COUNT_FAILURE)) Project + local failure + for failure in "${T_FAILURE_ARRAY[@]}"; do + echo -e "$T_COLOR_YELLOW $failure$T_COLOR_CLEAR" + done + + if [ "$T_COUNT_FAILURE" -eq 0 ] && [ "$T_COUNT_SUCCESS" -gt 0 ]; then + if [ $T_FIGLET_AVAILABLE -eq 0 ]; then + # split in lines and colorize each individually for CI + figlet -c -w 120 "Success!" | while IFS= read -r line; do echo -e "$T_COLOR_GREEN$line $T_COLOR_CLEAR"; done + else + echo -e "$T_COLOR_GREEN Overall succes!$T_COLOR_CLEAR" + fi + texit 0 + else + if [ $T_FIGLET_AVAILABLE -eq 0 ]; then + # split in lines and colorize each individually for CI + figlet -c -w 120 "Failed!" | while IFS= read -r line; do echo -e "$T_COLOR_RED$line $T_COLOR_CLEAR"; done + else + echo -e "$T_COLOR_RED Overall failed!$T_COLOR_CLEAR" + fi + texit 1 + fi + else + # we've finished a file, but not the last one yet. + T_FILE_NAME="" + fi +} + + +function tcreate { + # Creates a new testfile from template. + # Arguments: + # 1) filename of the new testfile (.test.sh extension auto-appended) + # 2) test suite name + if [ "$1" == "" ]; then + echo "first argument missing: filename to create" + texit 1 + elif [ "$2" == "" ]; then + echo "second argument missing: test suite name" + texit 1 + elif [ ! "$3" == "" ]; then + echo "too many arguments" + texit 1 + fi + + local FILENAME="$1" + if [[ "$FILENAME" != *.test.sh ]]; then + FILENAME=$FILENAME.test.sh + fi + + if [ -f "$FILENAME" ]; then + echo "file already exists: $FILENAME" + texit 1 + fi + +cat < "$FILENAME" +#!/usr/bin/env bash + +: ' +There is one dependency; "test.sh", the "figlet" utility is optional. + +Usage test.sh: + 1: ./test.sh [--suite ] [files/dirs...] + 2: ./test.sh --create + + 1: Runs tests. When "files/dirs" is not provided, it will run all "*.test.sh" files + located in the same directory as "test.sh". The suite name defaults to "unknown + test suite". + + 2: Creates a new template test file + +Usage test files: + ./this.test.sh [path-to-test.sh] + + When "path-to-test.sh" is not provided it defaults to the same directory where + the test file is located. + +Assuming "test.sh" is in the same directory as this file: + + /some/path/this.test.sh # runs only this file + /some/path/test.sh # runs all "/some/path/*.test.sh" files + /some/path/test.sh this.test.sh that.test.sh # runs "this.test.sh" and "that.test.sh" files + +When not in the same directory + + /some/path/this.test.sh other/path/test.sh # runs only this file + /other/path/test.sh /some/path/this.test.sh # runs only this file + + +The test themselves are located below this comment in the "run_test" function +' + +function run_test { + # the suite name below will only be used when running this file directly, when + # running through "test.sh" it must be provided using the "--suite" option. + tinitialize "$2" "\${BASH_SOURCE[0]}" + + tchapter "great tests" + + ttest "ensures the file exists" + tdebug # this enables 'set -x' until the first tfailure or tsuccess call + if [ ! -f "some/local/file" ]; then + tmessage "The file did not exist" + tfailure + else + tmessage "The file was found" + tsuccess + fi + + tchapter "awesome tests" + + ttest "ensures the file does NOT exist" + if [ ! -f "some/local/file" ]; then + tsuccess "a success message" + else + tfailure "a failure message" + fi + + tfinish +} + +# No need to modify anything below this comment + +# shellcheck disable=SC1090 # do not follow source +[[ "\$T_PROJECT_NAME" == "" ]] && set -e && if [[ -f "\${1:-\$(dirname "\$(realpath "\$0")")/test.sh}" ]]; then source "\${1:-\$(dirname "\$(realpath "\$0")")/test.sh}"; else source "\${1:-\$(dirname "\$(realpath "\$0")")/run.sh}"; fi && set +e +run_test +EOF + + if [ ! $? -eq 0 ]; then + echo "failed to write file $FILENAME" + texit 1 + fi + chmod +x "$FILENAME" + #bash ./test.sh "$FILENAME" + + echo "Successfully created a new test file: $FILENAME" + echo "Instructions are in the file." +} + + +function main { + # usage: test.sh [--suite ] [filenames...] + # if no filenames given then will execute all "*.test.sh" files in the + # same directory + local FILE_LIST=() + local FILENAME + local SUITE_NAME + + if [[ "$1" == "--create" ]]; then + shift + tcreate "$@" + texit 0 + elif [[ "$1" == "--suite" ]]; then + shift + SUITE_NAME=$1 + shift + else + SUITE_NAME="unknown test suite" + fi + + if [[ $# -gt 0 ]]; then + # filenames passed; only execute filenames passed in + while [[ $# -gt 0 ]]; do + if [ ! -f "$1" ] ; then + if [ ! -d "$1" ] ; then + echo "test file not found: $1" + texit 1 + fi + # it's a directory, add all files in it + for FILENAME in "$1"/*.test.sh; do + if [ -f "$FILENAME" ] ; then + FILE_LIST+=("$FILENAME") + fi + done + else + # add a single file + FILE_LIST+=("$1") + fi + shift + done + + else + # no parameters passed, go execute all test files we can find + local MY_PATH + if [[ "$0" == "/dev/stdin" ]]; then + # script is executed from stdin, probably through run.sh + MY_PATH=$PWD + else + MY_PATH=$(dirname "$(realpath "$0")") + fi + for FILENAME in "$MY_PATH"/*.test.sh; do + if [[ "$FILENAME" != "$(realpath "$0")" ]]; then + if [ -f "$FILENAME" ] ; then + FILE_LIST+=("$FILENAME") + fi + fi + done + fi + + tinitialize "$SUITE_NAME" + + for FILENAME in "${FILE_LIST[@]}"; do + # shellcheck disable=SC1090 + source "$FILENAME" + done + + tfinish +} + + +# see 'main' for usage +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + # this script is executed, not sourced, so initiate a test run + main "$@" +fi