From 890b70a133f7c17b427bb814a5e68ef51953da7b Mon Sep 17 00:00:00 2001 From: Thomas VIAL Date: Thu, 25 Feb 2016 00:17:01 +0100 Subject: [PATCH] Fixes 84 => moved to bats unit test framework --- test/auth/smtp-auth-plain-wrong.txt | 3 + test/bats/bats | 142 ++++++++++++ test/bats/bats-exec-suite | 55 +++++ test/bats/bats-exec-test | 346 ++++++++++++++++++++++++++++ test/bats/bats-format-tap-stream | 165 +++++++++++++ test/bats/bats-preprocess | 52 +++++ test/tests.bats | 340 +++++++++++++++++++++++++++ 7 files changed, 1103 insertions(+) create mode 100644 test/auth/smtp-auth-plain-wrong.txt create mode 100755 test/bats/bats create mode 100755 test/bats/bats-exec-suite create mode 100755 test/bats/bats-exec-test create mode 100755 test/bats/bats-format-tap-stream create mode 100755 test/bats/bats-preprocess create mode 100644 test/tests.bats diff --git a/test/auth/smtp-auth-plain-wrong.txt b/test/auth/smtp-auth-plain-wrong.txt new file mode 100644 index 00000000..d8d8ad2a --- /dev/null +++ b/test/auth/smtp-auth-plain-wrong.txt @@ -0,0 +1,3 @@ +EHLO mail +AUTH PLAIN WRONGPASSWORD +QUIT diff --git a/test/bats/bats b/test/bats/bats new file mode 100755 index 00000000..7e1c9eda --- /dev/null +++ b/test/bats/bats @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +set -e + +version() { + echo "Bats 0.4.0" +} + +usage() { + version + echo "Usage: bats [-c] [-p | -t] [ ...]" +} + +help() { + usage + echo + echo " is the path to a Bats test file, or the path to a directory" + echo " containing Bats test files." + echo + echo " -c, --count Count the number of test cases without running any tests" + echo " -h, --help Display this help message" + echo " -p, --pretty Show results in pretty format (default for terminals)" + echo " -t, --tap Show results in TAP format" + echo " -v, --version Display the version number" + echo + echo " For more information, see https://github.com/sstephenson/bats" + echo +} + +resolve_link() { + $(type -p greadlink readlink | head -1) "$1" +} + +abs_dirname() { + local cwd="$(pwd)" + local path="$1" + + while [ -n "$path" ]; do + cd "${path%/*}" + local name="${path##*/}" + path="$(resolve_link "$name" || true)" + done + + pwd + cd "$cwd" +} + +expand_path() { + { cd "$(dirname "$1")" 2>/dev/null + local dirname="$PWD" + cd "$OLDPWD" + echo "$dirname/$(basename "$1")" + } || echo "$1" +} + +BATS_LIBEXEC="$(abs_dirname "$0")" +export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")" +export BATS_CWD="$(abs_dirname .)" +export PATH="$BATS_LIBEXEC:$PATH" + +options=() +arguments=() +for arg in "$@"; do + if [ "${arg:0:1}" = "-" ]; then + if [ "${arg:1:1}" = "-" ]; then + options[${#options[*]}]="${arg:2}" + else + index=1 + while option="${arg:$index:1}"; do + [ -n "$option" ] || break + options[${#options[*]}]="$option" + let index+=1 + done + fi + else + arguments[${#arguments[*]}]="$arg" + fi +done + +unset count_flag pretty +[ -t 0 ] && [ -t 1 ] && pretty="1" +[ -n "$CI" ] && pretty="" + +for option in "${options[@]}"; do + case "$option" in + "h" | "help" ) + help + exit 0 + ;; + "v" | "version" ) + version + exit 0 + ;; + "c" | "count" ) + count_flag="-c" + ;; + "t" | "tap" ) + pretty="" + ;; + "p" | "pretty" ) + pretty="1" + ;; + * ) + usage >&2 + exit 1 + ;; + esac +done + +if [ "${#arguments[@]}" -eq 0 ]; then + usage >&2 + exit 1 +fi + +filenames=() +for filename in "${arguments[@]}"; do + if [ -d "$filename" ]; then + shopt -s nullglob + for suite_filename in "$(expand_path "$filename")"/*.bats; do + filenames["${#filenames[@]}"]="$suite_filename" + done + shopt -u nullglob + else + filenames["${#filenames[@]}"]="$(expand_path "$filename")" + fi +done + +if [ "${#filenames[@]}" -eq 1 ]; then + command="bats-exec-test" +else + command="bats-exec-suite" +fi + +if [ -n "$pretty" ]; then + extended_syntax_flag="-x" + formatter="bats-format-tap-stream" +else + extended_syntax_flag="" + formatter="cat" +fi + +set -o pipefail execfail +exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter" \ No newline at end of file diff --git a/test/bats/bats-exec-suite b/test/bats/bats-exec-suite new file mode 100755 index 00000000..29ab255d --- /dev/null +++ b/test/bats/bats-exec-suite @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -e + +count_only_flag="" +if [ "$1" = "-c" ]; then + count_only_flag=1 + shift +fi + +extended_syntax_flag="" +if [ "$1" = "-x" ]; then + extended_syntax_flag="-x" + shift +fi + +trap "kill 0; exit 1" int + +count=0 +for filename in "$@"; do + let count+="$(bats-exec-test -c "$filename")" +done + +if [ -n "$count_only_flag" ]; then + echo "$count" + exit +fi + +echo "1..$count" +status=0 +offset=0 +for filename in "$@"; do + index=0 + { + IFS= read -r # 1..n + while IFS= read -r line; do + case "$line" in + "begin "* ) + let index+=1 + echo "${line/ $index / $(($offset + $index)) }" + ;; + "ok "* | "not ok "* ) + [ -n "$extended_syntax_flag" ] || let index+=1 + echo "${line/ $index / $(($offset + $index)) }" + [ "${line:0:6}" != "not ok" ] || status=1 + ;; + * ) + echo "$line" + ;; + esac + done + } < <( bats-exec-test $extended_syntax_flag "$filename" ) + offset=$(($offset + $index)) +done + +exit "$status" diff --git a/test/bats/bats-exec-test b/test/bats/bats-exec-test new file mode 100755 index 00000000..8f3bd510 --- /dev/null +++ b/test/bats/bats-exec-test @@ -0,0 +1,346 @@ +#!/usr/bin/env bash +set -e +set -E +set -T + +BATS_COUNT_ONLY="" +if [ "$1" = "-c" ]; then + BATS_COUNT_ONLY=1 + shift +fi + +BATS_EXTENDED_SYNTAX="" +if [ "$1" = "-x" ]; then + BATS_EXTENDED_SYNTAX="$1" + shift +fi + +BATS_TEST_FILENAME="$1" +if [ -z "$BATS_TEST_FILENAME" ]; then + echo "usage: bats-exec " >&2 + exit 1 +elif [ ! -f "$BATS_TEST_FILENAME" ]; then + echo "bats: $BATS_TEST_FILENAME does not exist" >&2 + exit 1 +else + shift +fi + +BATS_TEST_DIRNAME="$(dirname "$BATS_TEST_FILENAME")" +BATS_TEST_NAMES=() + +load() { + local name="$1" + local filename + + if [ "${name:0:1}" = "/" ]; then + filename="${name}" + else + filename="$BATS_TEST_DIRNAME/${name}.bash" + fi + + [ -f "$filename" ] || { + echo "bats: $filename does not exist" >&2 + exit 1 + } + + source "${filename}" +} + +run() { + local e E T oldIFS + [[ ! "$-" =~ e ]] || e=1 + [[ ! "$-" =~ E ]] || E=1 + [[ ! "$-" =~ T ]] || T=1 + set +e + set +E + set +T + output="$("$@" 2>&1)" + status="$?" + oldIFS=$IFS + IFS=$'\n' lines=($output) + [ -z "$e" ] || set -e + [ -z "$E" ] || set -E + [ -z "$T" ] || set -T + IFS=$oldIFS +} + +setup() { + true +} + +teardown() { + true +} + +skip() { + BATS_TEST_SKIPPED=${1:-1} + BATS_TEST_COMPLETED=1 + exit 0 +} + +bats_test_begin() { + BATS_TEST_DESCRIPTION="$1" + if [ -n "$BATS_EXTENDED_SYNTAX" ]; then + echo "begin $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 + fi + setup +} + +bats_test_function() { + local test_name="$1" + BATS_TEST_NAMES["${#BATS_TEST_NAMES[@]}"]="$test_name" +} + +bats_capture_stack_trace() { + BATS_PREVIOUS_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) + BATS_CURRENT_STACK_TRACE=() + + local test_pattern=" $BATS_TEST_NAME $BATS_TEST_SOURCE" + local setup_pattern=" setup $BATS_TEST_SOURCE" + local teardown_pattern=" teardown $BATS_TEST_SOURCE" + + local frame + local index=1 + + while frame="$(caller "$index")"; do + BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame" + if [[ "$frame" = *"$test_pattern" || \ + "$frame" = *"$setup_pattern" || \ + "$frame" = *"$teardown_pattern" ]]; then + break + else + let index+=1 + fi + done + + BATS_SOURCE="$(bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}")" + BATS_LINENO="$(bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}")" +} + +bats_print_stack_trace() { + local frame + local index=1 + local count="${#@}" + + for frame in "$@"; do + local filename="$(bats_trim_filename "$(bats_frame_filename "$frame")")" + local lineno="$(bats_frame_lineno "$frame")" + + if [ $index -eq 1 ]; then + echo -n "# (" + else + echo -n "# " + fi + + local fn="$(bats_frame_function "$frame")" + if [ "$fn" != "$BATS_TEST_NAME" ]; then + echo -n "from function \`$fn' " + fi + + if [ $index -eq $count ]; then + echo "in test file $filename, line $lineno)" + else + echo "in file $filename, line $lineno," + fi + + let index+=1 + done +} + +bats_print_failed_command() { + local frame="$1" + local status="$2" + local filename="$(bats_frame_filename "$frame")" + local lineno="$(bats_frame_lineno "$frame")" + + local failed_line="$(bats_extract_line "$filename" "$lineno")" + local failed_command="$(bats_strip_string "$failed_line")" + echo -n "# \`${failed_command}' " + + if [ $status -eq 1 ]; then + echo "failed" + else + echo "failed with status $status" + fi +} + +bats_frame_lineno() { + local frame="$1" + local lineno="${frame%% *}" + echo "$lineno" +} + +bats_frame_function() { + local frame="$1" + local rest="${frame#* }" + local fn="${rest%% *}" + echo "$fn" +} + +bats_frame_filename() { + local frame="$1" + local rest="${frame#* }" + local filename="${rest#* }" + + if [ "$filename" = "$BATS_TEST_SOURCE" ]; then + echo "$BATS_TEST_FILENAME" + else + echo "$filename" + fi +} + +bats_extract_line() { + local filename="$1" + local lineno="$2" + sed -n "${lineno}p" "$filename" +} + +bats_strip_string() { + local string="$1" + printf "%s" "$string" | sed -e "s/^[ "$'\t'"]*//" -e "s/[ "$'\t'"]*$//" +} + +bats_trim_filename() { + local filename="$1" + local length="${#BATS_CWD}" + + if [ "${filename:0:length+1}" = "${BATS_CWD}/" ]; then + echo "${filename:length+1}" + else + echo "$filename" + fi +} + +bats_debug_trap() { + if [ "$BASH_SOURCE" != "$1" ]; then + bats_capture_stack_trace + fi +} + +bats_error_trap() { + BATS_ERROR_STATUS="$?" + BATS_ERROR_STACK_TRACE=( "${BATS_PREVIOUS_STACK_TRACE[@]}" ) + trap - debug +} + +bats_teardown_trap() { + trap "bats_exit_trap" exit + local status=0 + teardown >>"$BATS_OUT" 2>&1 || status="$?" + + if [ $status -eq 0 ]; then + BATS_TEARDOWN_COMPLETED=1 + elif [ -n "$BATS_TEST_COMPLETED" ]; then + BATS_ERROR_STATUS="$status" + BATS_ERROR_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) + fi + + bats_exit_trap +} + +bats_exit_trap() { + local status + local skipped + trap - err exit + + skipped="" + if [ -n "$BATS_TEST_SKIPPED" ]; then + skipped=" # skip" + if [ "1" != "$BATS_TEST_SKIPPED" ]; then + skipped+=" ($BATS_TEST_SKIPPED)" + fi + fi + + if [ -z "$BATS_TEST_COMPLETED" ] || [ -z "$BATS_TEARDOWN_COMPLETED" ]; then + echo "not ok $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 + bats_print_stack_trace "${BATS_ERROR_STACK_TRACE[@]}" >&3 + bats_print_failed_command "${BATS_ERROR_STACK_TRACE[${#BATS_ERROR_STACK_TRACE[@]}-1]}" "$BATS_ERROR_STATUS" >&3 + sed -e "s/^/# /" < "$BATS_OUT" >&3 + status=1 + else + echo "ok ${BATS_TEST_NUMBER}${skipped} ${BATS_TEST_DESCRIPTION}" >&3 + status=0 + fi + + rm -f "$BATS_OUT" + exit "$status" +} + +bats_perform_tests() { + echo "1..$#" + test_number=1 + status=0 + for test_name in "$@"; do + "$0" $BATS_EXTENDED_SYNTAX "$BATS_TEST_FILENAME" "$test_name" "$test_number" || status=1 + let test_number+=1 + done + exit "$status" +} + +bats_perform_test() { + BATS_TEST_NAME="$1" + if [ "$(type -t "$BATS_TEST_NAME" || true)" = "function" ]; then + BATS_TEST_NUMBER="$2" + if [ -z "$BATS_TEST_NUMBER" ]; then + echo "1..1" + BATS_TEST_NUMBER="1" + fi + + BATS_TEST_COMPLETED="" + BATS_TEARDOWN_COMPLETED="" + trap "bats_debug_trap \"\$BASH_SOURCE\"" debug + trap "bats_error_trap" err + trap "bats_teardown_trap" exit + "$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 + BATS_TEST_COMPLETED=1 + + else + echo "bats: unknown test name \`$BATS_TEST_NAME'" >&2 + exit 1 + fi +} + +if [ -z "$TMPDIR" ]; then + BATS_TMPDIR="/tmp" +else + BATS_TMPDIR="${TMPDIR%/}" +fi + +BATS_TMPNAME="$BATS_TMPDIR/bats.$$" +BATS_PARENT_TMPNAME="$BATS_TMPDIR/bats.$PPID" +BATS_OUT="${BATS_TMPNAME}.out" + +bats_preprocess_source() { + BATS_TEST_SOURCE="${BATS_TMPNAME}.src" + { tr -d '\r' < "$BATS_TEST_FILENAME"; echo; } | bats-preprocess > "$BATS_TEST_SOURCE" + trap "bats_cleanup_preprocessed_source" err exit + trap "bats_cleanup_preprocessed_source; exit 1" int +} + +bats_cleanup_preprocessed_source() { + rm -f "$BATS_TEST_SOURCE" +} + +bats_evaluate_preprocessed_source() { + if [ -z "$BATS_TEST_SOURCE" ]; then + BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src" + fi + source "$BATS_TEST_SOURCE" +} + +exec 3<&1 + +if [ "$#" -eq 0 ]; then + bats_preprocess_source + bats_evaluate_preprocessed_source + + if [ -n "$BATS_COUNT_ONLY" ]; then + echo "${#BATS_TEST_NAMES[@]}" + else + bats_perform_tests "${BATS_TEST_NAMES[@]}" + fi +else + bats_evaluate_preprocessed_source + bats_perform_test "$@" +fi diff --git a/test/bats/bats-format-tap-stream b/test/bats/bats-format-tap-stream new file mode 100755 index 00000000..614768f4 --- /dev/null +++ b/test/bats/bats-format-tap-stream @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +set -e + +# Just stream the TAP output (sans extended syntax) if tput is missing +command -v tput >/dev/null || exec grep -v "^begin " + +header_pattern='[0-9]+\.\.[0-9]+' +IFS= read -r header + +if [[ "$header" =~ $header_pattern ]]; then + count="${header:3}" + index=0 + failures=0 + skipped=0 + name="" + count_column_width=$(( ${#count} * 2 + 2 )) +else + # If the first line isn't a TAP plan, print it and pass the rest through + printf "%s\n" "$header" + exec cat +fi + +update_screen_width() { + screen_width="$(tput cols)" + count_column_left=$(( $screen_width - $count_column_width )) +} + +trap update_screen_width WINCH +update_screen_width + +begin() { + go_to_column 0 + printf_with_truncation $(( $count_column_left - 1 )) " %s" "$name" + clear_to_end_of_line + go_to_column $count_column_left + printf "%${#count}s/${count}" "$index" + go_to_column 1 +} + +pass() { + go_to_column 0 + printf " ✓ %s" "$name" + advance +} + +skip() { + local reason="$1" + [ -z "$reason" ] || reason=": $reason" + go_to_column 0 + printf " - %s (skipped%s)" "$name" "$reason" + advance +} + +fail() { + go_to_column 0 + set_color 1 bold + printf " ✗ %s" "$name" + advance +} + +log() { + set_color 1 + printf " %s\n" "$1" + clear_color +} + +summary() { + printf "\n%d test%s" "$count" "$(plural "$count")" + + printf ", %d failure%s" "$failures" "$(plural "$failures")" + + if [ "$skipped" -gt 0 ]; then + printf ", %d skipped" "$skipped" + fi + + printf "\n" +} + +printf_with_truncation() { + local width="$1" + shift + local string="$(printf "$@")" + + if [ "${#string}" -gt "$width" ]; then + printf "%s..." "${string:0:$(( $width - 4 ))}" + else + printf "%s" "$string" + fi +} + +go_to_column() { + local column="$1" + printf "\x1B[%dG" $(( $column + 1 )) +} + +clear_to_end_of_line() { + printf "\x1B[K" +} + +advance() { + clear_to_end_of_line + echo + clear_color +} + +set_color() { + local color="$1" + local weight="$2" + printf "\x1B[%d;%dm" $(( 30 + $color )) "$( [ "$weight" = "bold" ] && echo 1 || echo 22 )" +} + +clear_color() { + printf "\x1B[0m" +} + +plural() { + [ "$1" -eq 1 ] || echo "s" +} + +_buffer="" + +buffer() { + _buffer="${_buffer}$("$@")" +} + +flush() { + printf "%s" "$_buffer" + _buffer="" +} + +finish() { + flush + printf "\n" +} + +trap finish EXIT + +while IFS= read -r line; do + case "$line" in + "begin "* ) + let index+=1 + name="${line#* $index }" + buffer begin + flush + ;; + "ok "* ) + skip_expr="ok $index # skip (\(([^)]*)\))?" + if [[ "$line" =~ $skip_expr ]]; then + let skipped+=1 + buffer skip "${BASH_REMATCH[2]}" + else + buffer pass + fi + ;; + "not ok "* ) + let failures+=1 + buffer fail + ;; + "# "* ) + buffer log "${line:2}" + ;; + esac +done + +buffer summary diff --git a/test/bats/bats-preprocess b/test/bats/bats-preprocess new file mode 100755 index 00000000..04297ed0 --- /dev/null +++ b/test/bats/bats-preprocess @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -e + +encode_name() { + local name="$1" + local result="test_" + + if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then + name="${name//_/-5f}" + name="${name//-/-2d}" + name="${name// /_}" + result+="$name" + else + local length="${#name}" + local char i + + for ((i=0; i, orig_to=' /var/log/mail.log | grep 'status=sent' | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 1 ] +} + +@test "checking smtp: user1 should have received 2 mails" { + run docker exec mail /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/new | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 2 ] +} + +@test "checking smtp: rejects mail to unknown user" { + run docker exec mail /bin/sh -c "grep ': Recipient address rejected: User unknown in virtual mailbox table' /var/log/mail.log | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 1 ] +} + +@test "checking smtp: redirects mail to external alias" { + run docker exec mail /bin/sh -c "grep -- '-> ' /var/log/mail.log | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 1 ] +} + +@test "checking smtp: rejects spam" { + run docker exec mail /bin/sh -c "grep 'Blocked SPAM' /var/log/mail.log | grep spam@external.tld | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 1 ] +} + +@test "checking smtp: rejects virus" { + run docker exec mail /bin/sh -c "grep 'Blocked INFECTED' /var/log/mail.log | grep virus@external.tld | wc -l" + [ "$status" -eq 0 ] + [ "$output" = 1 ] +} + +# +# accounts +# + +@test "checking accounts: user accounts" { + run docker exec mail sasldblistusers2 + [ "$status" -eq 0 ] + [ "${lines[0]}" = "user1@localhost.localdomain: userPassword" ] + [ "${lines[1]}" = "user2@otherdomain.tld: userPassword" ] +} + +@test "checking accounts: user mail folders for user1" { + run docker exec mail ls -A /var/mail/localhost.localdomain/user1 + [ "$status" -eq 0 ] + [ "${lines[0]}" = ".Drafts" ] + [ "${lines[1]}" = ".Sent" ] + [ "${lines[2]}" = ".Trash" ] + [ "${lines[3]}" = "courierimapsubscribed" ] + [ "${lines[4]}" = "cur" ] + [ "${lines[5]}" = "new" ] + [ "${lines[6]}" = "tmp" ] +} + +@test "checking accounts: user mail folders for user2" { + run docker exec mail ls -A /var/mail/otherdomain.tld/user2 + [ "$status" -eq 0 ] + [ "${lines[0]}" = ".Drafts" ] + [ "${lines[1]}" = ".Sent" ] + [ "${lines[2]}" = ".Trash" ] + [ "${lines[3]}" = "courierimapsubscribed" ] + [ "${lines[4]}" = "cur" ] + [ "${lines[5]}" = "new" ] + [ "${lines[6]}" = "tmp" ] +} + +# +# postfix +# + +@test "checking postfix: vhost file is correct" { + run docker exec mail cat /etc/postfix/vhost + [ "$status" -eq 0 ] + [ "${lines[0]}" = "localhost.localdomain" ] + [ "${lines[1]}" = "otherdomain.tld" ] +} + +# +# spamassassin +# + +@test "checking spamassassin: docker env variables are set correctly (default)" { + run docker exec mail_pop3 /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'" + [ "$status" -eq 0 ] + run docker exec mail_pop3 /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'" + [ "$status" -eq 0 ] + run docker exec mail_pop3 /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 6.31'" + [ "$status" -eq 0 ] +} + +@test "checking spamassassin: docker env variables are set correctly (custom)" { + run docker exec mail /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 1.0'" + [ "$status" -eq 0 ] + run docker exec mail /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'" + [ "$status" -eq 0 ] + run docker exec mail /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 3.0'" + [ "$status" -eq 0 ] +} + +# +# opendkim +# + +@test "checking opendkim: /etc/opendkim/KeyTable should contain 2 entries" { + run docker exec mail /bin/sh -c "cat /etc/opendkim/KeyTable | wc -l" + [ "$status" -eq 0 ] + [ "$output" -eq 2 ] +} + +@test "checking opendkim: /etc/opendkim/keys/ should contain 2 entries" { + run docker exec mail /bin/sh -c "ls -l /etc/opendkim/keys/ | grep '^d' | wc -l" + [ "$status" -eq 0 ] + [ "$output" -eq 2 ] +} + +# +# opendmarc +# + +@test "checking opendkim: server fqdn should be added to /etc/opendmarc.conf as AuthservID" { + run docker exec mail grep ^AuthservID /etc/opendmarc.conf + [ "$status" -eq 0 ] + [ "$output" = "AuthservID mail.my-domain.com" ] +} + +@test "checking opendkim: server fqdn should be added to /etc/opendmarc.conf as TrustedAuthservIDs" { + run docker exec mail grep ^TrustedAuthservID /etc/opendmarc.conf + [ "$status" -eq 0 ] + [ "$output" = "TrustedAuthservIDs mail.my-domain.com" ] +} + +# +# letsencrypt +# + +@test "checking letsencrypt: lets-encrypt-x1-cross-signed.pem is installed" { + run docker exec mail grep 'BEGIN CERTIFICATE' /etc/ssl/certs/lets-encrypt-x1-cross-signed.pem + [ "$status" -eq 0 ] +} + +@test "checking letsencrypt: lets-encrypt-x2-cross-signed.pem is installed" { + run docker exec mail grep 'BEGIN CERTIFICATE' /etc/ssl/certs/lets-encrypt-x2-cross-signed.pem + [ "$status" -eq 0 ] +} + +# +# ssl +# + +@test "checking ssl: generated default cert is installed" { + run docker exec mail /bin/sh -c "openssl s_client -connect 0.0.0.0:587 -starttls smtp -CApath /etc/ssl/certs/ | grep 'Verify return code: 0 (ok)'" + [ "$status" -eq 0 ] +} + +# +# fail2ban +# + +@test "checking fail2ban: localhost is not banned" { + run docker exec mail /bin/sh -c "fail2ban-client status sasl | grep 'IP list:\s*127.0.0.1'" + [ "$status" -eq 1 ] +} + +@test "checking fail2ban: ban ip on multiple failed login" { + docker exec mail fail2ban-client status sasl + docker exec mail fail2ban-client set sasl delignoreip 127.0.0.1/8 + docker exec mail /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt' + docker exec mail /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt' + docker exec mail /bin/sh -c 'nc -w 1 0.0.0.0 25 < /tmp/test/auth/smtp-auth-login-wrong.txt' + sleep 5 + run docker exec mail /bin/sh -c "fail2ban-client status sasl | grep 'IP list:\s*127.0.0.1'" + [ "$status" -eq 0 ] +} + +@test "checking fail2ban: unban ip works" { + docker exec mail fail2ban-client set sasl addignoreip 127.0.0.1/8 + docker exec mail fail2ban-client set sasl unbanip 127.0.0.1 + sleep 5 + run docker exec mail /bin/sh -c "fail2ban-client status sasl | grep 'IP list:\s*127.0.0.1'" + [ "$status" -eq 1 ] +} + +# +# system +# + +@test "checking system: freshclam cron is enabled" { + run docker exec mail crontab -l + [ "$status" -eq 0 ] + [ "$output" = "0 1 * * * /usr/bin/freshclam --quiet" ] +} + +@test "checking system: /var/log/mail.log is error free" { + run docker exec mail grep 'non-null host address bits in' /var/log/mail.log + [ "$status" -eq 1 ] + run docker exec mail grep ': error:' /var/log/mail.log + [ "$status" -eq 1 ] + run docker exec mail_pop3 grep 'non-null host address bits in' /var/log/mail.log + [ "$status" -eq 1 ] + run docker exec mail_pop3 grep ': error:' /var/log/mail.log + [ "$status" -eq 1 ] +} + +@test "checking system: sets the server fqdn" { + run docker exec mail hostname + [ "$status" -eq 0 ] + [ "$output" = "mail.my-domain.com" ] +} + +@test "checking system: sets the server domain name in /etc/mailname" { + run docker exec mail cat /etc/mailname + [ "$status" -eq 0 ] + [ "$output" = "my-domain.com" ] +}