2022-11-25 21:58:25 +00:00
#!/bin/bash
2023-01-21 23:05:28 +00:00
# ? ABOUT: Functions defined here aid with common functionality during tests.
# ! ATTENTION: Functions prefixed with `__` are intended for internal use within this file only, not in tests.
# ! -------------------------------------------------------------------
# ? >> Miscellaneous initialization functionality
# shellcheck disable=SC2155
# Load additional BATS libraries for more functionality.
#
# ## Note
#
# This function is internal and should not be used in tests.
2022-11-25 21:58:25 +00:00
function __load_bats_helper( ) {
load " ${ REPOSITORY_ROOT } /test/test_helper/bats-support/load "
load " ${ REPOSITORY_ROOT } /test/test_helper/bats-assert/load "
2023-01-29 13:52:38 +00:00
load " ${ REPOSITORY_ROOT } /test/helper/sending "
2022-11-25 21:58:25 +00:00
}
__load_bats_helper
2023-01-21 23:05:28 +00:00
# Properly handle the container name given to tests. This makes the whole
# test suite more robust as we can be sure that the container name is
# properly set. Sometimes, we need to provide an explicit container name;
# this function eases the pain by either providing the explicitly given
# name or `CONTAINER_NAME` if it is set.
#
# @param ${1} = explicit container name [OPTIONAL]
#
# ## Attention
#
# Note that this function checks whether the name given to it starts with
# the prefix `dms-test_`. One must adhere to this naming convention.
#
# ## Panics
#
# If neither an explicit non-empty argument is given nor `CONTAINER_NAME`
# is set.
#
# ## "Calling Convention"
#
# This function should be called the following way:
#
# local SOME_VAR=$(__handle_container_name "${X:-}")
#
# Where `X` is an arbitrary argument of the function you're calling.
#
# ## Note
#
# This function is internal and should not be used in tests.
function __handle_container_name( ) {
2023-05-24 07:06:59 +00:00
if [ [ -n ${ 1 :- } ] ] && [ [ ${ 1 :- } = ~ ^dms-test_ ] ] ; then
2023-01-21 23:05:28 +00:00
printf '%s' " ${ 1 } "
return 0
2023-05-24 07:06:59 +00:00
elif [ [ -n ${ CONTAINER_NAME +set } ] ] ; then
2023-01-21 23:05:28 +00:00
printf '%s' " ${ CONTAINER_NAME } "
return 0
else
2023-01-24 08:21:39 +00:00
echo 'ERROR: (helper/common.sh) Container name was either provided explicitly without the required "dms-test_" prefix, or CONTAINER_NAME is not set for implicit usage' >& 2
2023-01-21 23:05:28 +00:00
exit 1
fi
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# ? << Miscellaneous initialization functionality
# ! -------------------------------------------------------------------
# ? >> Functions to execute commands inside a container
# Execute a command inside a container with an explicit name.
#
# @param ${1} = container name
# @param ... = command to execute
function _exec_in_container_explicit( ) {
local CONTAINER_NAME = ${ 1 : ?Container name must be provided when using explicit }
2023-01-03 06:00:13 +00:00
shift 1
2023-01-21 23:05:28 +00:00
docker exec " ${ CONTAINER_NAME } " " ${ @ } "
2023-01-03 06:00:13 +00:00
}
2023-01-21 23:05:28 +00:00
# Execute a command inside the container with name ${CONTAINER_NAME}.
#
# @param ... = command to execute
function _exec_in_container( ) {
_exec_in_container_explicit " ${ CONTAINER_NAME : ?Container name must be provided } " " ${ @ } "
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Execute a command inside a container with an explicit name. The command is run with
# BATS' `run` so you can check the exit code and use `assert_`.
#
# @param ${1} = container name
# @param ... = command to execute
function _run_in_container_explicit( ) {
local CONTAINER_NAME = ${ 1 : ?Container name must be provided when using explicit }
shift 1
run _exec_in_container_explicit " ${ CONTAINER_NAME } " " ${ @ } "
}
2023-01-12 21:10:58 +00:00
2023-01-21 23:05:28 +00:00
# Execute a command inside the container with name ${CONTAINER_NAME}. The command
# is run with BATS' `run` so you can check the exit code and use `assert_`.
#
# @param ... = command to execute
function _run_in_container( ) {
_run_in_container_explicit " ${ CONTAINER_NAME : ?Container name must be provided } " " ${ @ } "
2023-01-12 21:10:58 +00:00
}
2023-01-21 23:05:28 +00:00
# Execute a command inside the container with name ${CONTAINER_NAME}. Moreover,
# the command is run by Bash with `/bin/bash -c`.
#
# @param ... = command to execute with Bash
function _exec_in_container_bash( ) { _exec_in_container /bin/bash -c " ${ @ } " ; }
# Execute a command inside the container with name ${CONTAINER_NAME}. The command
# is run with BATS' `run` so you can check the exit code and use `assert_`. Moreover,
# the command is run by Bash with `/bin/bash -c`.
#
# @param ... = Bash command to execute
function _run_in_container_bash( ) { _run_in_container /bin/bash -c " ${ @ } " ; }
2023-01-24 08:21:39 +00:00
# Run a command in Bash and filter the output given a regex.
#
# @param ${1} = command to run in Bash
# @param ${2} = regex to filter [OPTIONAL]
#
# ## Attention
#
# The regex is given to `grep -E`, so make sure it is compatible.
#
# ## Note
#
# If no regex is provided, this function will default to one that strips
# empty lines and Bash comments from the output.
function _run_in_container_bash_and_filter_output( ) {
local COMMAND = ${ 1 : ?Command must be provided }
local FILTER_REGEX = ${ 2 :- ^[[ : space : ]]* $|^ *# }
_run_in_container_bash " ${ COMMAND } | grep -E -v ' ${ FILTER_REGEX } ' "
assert_success
}
2023-01-21 23:05:28 +00:00
# ? << Functions to execute commands inside a container
# ! -------------------------------------------------------------------
# ? >> Functions about executing commands with timeouts
# Repeats a given command inside a container until the timeout is over.
#
# @param ${1} = timeout
# @param ${2} = container name
# @param ... = test command for container
function _repeat_in_container_until_success_or_timeout( ) {
local TIMEOUT = " ${ 1 : ?Timeout duration must be provided } "
local CONTAINER_NAME = " ${ 2 : ?Container name must be provided } "
shift 2
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
_repeat_until_success_or_timeout \
--fatal-test " _container_is_running ${ CONTAINER_NAME } " \
" ${ TIMEOUT } " \
_exec_in_container " ${ @ } "
refactor: Parallel Tests
- `disabled_clamav_spamassassin`:
- Just shuffling the test order around, and removing the restart test at the end which doesn't make sense.
- `postscreen`:
- Now uses common helper for getting container IP
- Does not appear to need the `NET_ADMIN` capability?
- Reduced startup time for the 2nd container + additional context about it's relevance.
- Test cases are largely the same, but refactored the `nc` alternative that properly waits it's turn. This only needs to run once. Added additional commentary and made into a generic method if needed in other tests.
- `fail2ban`:
- Use the common container IP helper method.
- Postscreen isn't affecting this test, it's not required to do the much slower exchange with the mail server when sending a login failure.
- IP being passed into ENV is no longer necessary.
- `sleep 5` in the related test cases doesn't seem necessary, can better rely on polling with timeout.
- `sleep 10` for `setup.sh` also doesn't appear to be necessary.
- `postgrey`:
- Reduced POSTGREY_DELAY to 3, which shaves a fair amount of wasted time while still verifying the delay works.
- One of the checks in `main.cf` doesn't seem to need to know about the earlier spamhaus portion of the line to work, removed.
- Better test case descriptions.
- Improved log matching via standard method that better documents the expected triplet under test.
- Removed a redundant whitelist file and test that didn't seem to have any relevance. Added a TODO with additional notes about a concern with these tests.
- Reduced test time as 8 second timeouts from `-w 8` don't appear to be required, better to poll with grep instead.
- Replaced `wc -l` commands with a new method to assert expected line count, better enabling assertions on the actual output.
- `undef_spam_subject`:
- Split to two separate test cases, and initialize each container in their case instead of `setup_file()`, allowing for using the default `teardown()` method (and slight benefit if running in parallel).
- `permit_docker`:
- Not a parallel test, but I realized that the repeat helper methods don't necessarily play well with `run` as the command (can cause false positive of what was successful).
2023-01-03 06:11:36 +00:00
}
2023-01-21 23:05:28 +00:00
# Repeats a given command until the timeout is over.
#
# @option --fatal-test <COMMAND EVAL STRING> = additional test whose failure aborts immediately
# @param ${1} = timeout
# @param ... = test to run
function _repeat_until_success_or_timeout( ) {
2022-11-25 21:58:25 +00:00
local FATAL_FAILURE_TEST_COMMAND
2023-05-24 07:06:59 +00:00
if [ [ " ${ 1 :- } " = = "--fatal-test" ] ] ; then
2023-01-21 23:05:28 +00:00
FATAL_FAILURE_TEST_COMMAND = " ${ 2 : ?Provided --fatal-test but no command } "
2022-11-25 21:58:25 +00:00
shift 2
fi
2023-01-21 23:05:28 +00:00
local TIMEOUT = ${ 1 : ?Timeout duration must be provided }
shift 1
2023-05-24 07:06:59 +00:00
if ! [ [ " ${ TIMEOUT } " = ~ ^[ 0-9] +$ ] ] ; then
2023-01-21 23:05:28 +00:00
echo " First parameter for timeout must be an integer, received \" ${ TIMEOUT } \" "
2022-11-25 21:58:25 +00:00
return 1
fi
local STARTTIME = ${ SECONDS }
2023-05-26 05:42:03 +00:00
until " ${ @ } " ; do
2023-05-24 07:06:59 +00:00
if [ [ -n ${ FATAL_FAILURE_TEST_COMMAND } ] ] && ! eval " ${ FATAL_FAILURE_TEST_COMMAND } " ; then
2022-11-25 21:58:25 +00:00
echo " \` ${ FATAL_FAILURE_TEST_COMMAND } \` failed, early aborting repeat_until_success of \` ${ * } \` " >& 2
return 1
fi
sleep 1
2023-05-24 07:06:59 +00:00
if [ [ $(( SECONDS - STARTTIME )) -gt ${ TIMEOUT } ] ] ; then
2022-11-25 21:58:25 +00:00
echo " Timed out on command: ${ * } " >& 2
return 1
fi
done
}
2023-01-21 23:05:28 +00:00
# Like `_repeat_until_success_or_timeout` . The command is run with BATS' `run`
# so you can check the exit code and use `assert_`.
#
# @param ${1} = timeout
# @param ... = test command to run
function _run_until_success_or_timeout( ) {
local TIMEOUT = ${ 1 : ?Timeout duration must be provided }
shift 1
2023-05-24 07:06:59 +00:00
if [ [ ! ${ TIMEOUT } = ~ ^[ 0-9] +$ ] ] ; then
2023-01-21 23:05:28 +00:00
echo " First parameter for timeout must be an integer, received \" ${ TIMEOUT } \" "
2022-11-25 21:58:25 +00:00
return 1
fi
local STARTTIME = ${ SECONDS }
2023-02-06 22:59:01 +00:00
# shellcheck disable=SC2154
2023-05-26 05:42:03 +00:00
until run " ${ @ } " && [ [ ${ status } -eq 0 ] ] ; do
2022-11-25 21:58:25 +00:00
sleep 1
2023-05-24 07:06:59 +00:00
if ( ( SECONDS - STARTTIME > TIMEOUT ) ) ; then
2022-11-25 21:58:25 +00:00
echo " Timed out on command: ${ * } " >& 2
return 1
fi
done
}
2023-01-21 23:05:28 +00:00
# ? << Functions about executing commands with timeouts
# ! -------------------------------------------------------------------
# ? >> Functions to wait until a condition is met
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# Wait until a port is ready.
#
# @param ${1} = port
# @param ${2} = container name [OPTIONAL]
function _wait_for_tcp_port_in_container( ) {
local PORT = ${ 1 : ?Port number must be provided }
local CONTAINER_NAME = $( __handle_container_name " ${ 2 :- } " )
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
_repeat_until_success_or_timeout \
--fatal-test " _container_is_running ${ CONTAINER_NAME } " \
" ${ TEST_TIMEOUT_IN_SECONDS } " \
_exec_in_container_bash " nc -z 0.0.0.0 ${ PORT } "
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Wait for SMTP port (25) to become ready.
#
# @param ${1} = name of the container [OPTIONAL]
function _wait_for_smtp_port_in_container( ) {
local CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
_wait_for_tcp_port_in_container 25
2022-11-25 21:58:25 +00:00
}
2023-02-18 14:51:28 +00:00
# Wait until the SMTP port (25) can respond.
2023-01-21 23:05:28 +00:00
#
# @param ${1} = name of the container [OPTIONAL]
function _wait_for_smtp_port_in_container_to_respond( ) {
local CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
2022-11-25 21:58:25 +00:00
local COUNT = 0
2023-05-26 05:42:03 +00:00
until [ [ $( _exec_in_container timeout 10 /bin/bash -c 'echo QUIT | nc localhost 25' ) = = *'221 2.0.0 Bye' * ] ] ; do
2023-05-24 07:06:59 +00:00
if [ [ ${ COUNT } -eq 20 ] ] ; then
2022-11-25 21:58:25 +00:00
echo "Unable to receive a valid response from 'nc localhost 25' within 20 seconds"
return 1
fi
sleep 1
2023-01-21 23:05:28 +00:00
( ( COUNT += 1 ) )
2022-11-25 21:58:25 +00:00
done
}
2023-01-21 23:05:28 +00:00
# Checks whether a service is running inside a container (${1}).
#
# @param ${1} = service name
# @param ${2} = container name [OPTIONAL]
function _should_have_service_running_in_container( ) {
local SERVICE_NAME = " ${ 1 : ?Service name must be provided } "
local CONTAINER_NAME = $( __handle_container_name " ${ 2 :- } " )
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
_run_in_container /usr/bin/supervisorctl status " ${ SERVICE_NAME } "
assert_success
assert_output --partial 'RUNNING'
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Wait until a service is running.
#
# @param ${1} = name of the service to wait for
# @param ${2} = container name [OPTIONAL]
function _wait_for_service( ) {
local SERVICE_NAME = " ${ 1 : ?Service name must be provided } "
local CONTAINER_NAME = $( __handle_container_name " ${ 2 :- } " )
_repeat_until_success_or_timeout \
--fatal-test " _container_is_running ${ CONTAINER_NAME } " \
" ${ TEST_TIMEOUT_IN_SECONDS } " \
_should_have_service_running_in_container " ${ SERVICE_NAME } "
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# An account added to `postfix-accounts.cf` must wait for the `changedetector` service
# to process the update before Dovecot creates the mail account and associated storage dir.
#
# @param ${1} = mail account name
# @param ${2} = container name
function _wait_until_account_maildir_exists( ) {
local MAIL_ACCOUNT = ${ 1 : ?Mail account must be provided }
local CONTAINER_NAME = $( __handle_container_name " ${ 2 :- } " )
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
local LOCAL_PART = " ${ MAIL_ACCOUNT %@* } "
local DOMAIN_PART = " ${ MAIL_ACCOUNT #*@ } "
local MAIL_ACCOUNT_STORAGE_DIR = " /var/mail/ ${ DOMAIN_PART } / ${ LOCAL_PART } "
_repeat_in_container_until_success_or_timeout 60 " ${ CONTAINER_NAME } " \
/bin/bash -c " [[ -d ${ MAIL_ACCOUNT_STORAGE_DIR } ]] "
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Wait until the mail queue is empty inside a container (${1}).
#
# @param ${1} = container name [OPTIONAL]
function _wait_for_empty_mail_queue_in_container( ) {
local CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
local TIMEOUT = ${ TEST_TIMEOUT_IN_SECONDS }
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# shellcheck disable=SC2016
_repeat_in_container_until_success_or_timeout \
" ${ TIMEOUT } " \
" ${ CONTAINER_NAME } " \
/bin/bash -c '[[ $(mailq) == "Mail queue is empty" ]]'
}
2023-01-16 07:39:46 +00:00
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# ? << Functions to wait until a condition is met
# ! -------------------------------------------------------------------
# ? >> Miscellaneous helper functions
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# Adds a mail account and waits for the associated files to be created.
#
# @param ${1} = mail account name
# @param ${2} = password [OPTIONAL]
# @param ${3} = container name [OPTIONAL]
function _add_mail_account_then_wait_until_ready( ) {
local MAIL_ACCOUNT = ${ 1 : ?Mail account must be provided }
local MAIL_PASS = " ${ 2 :- password_not_relevant_to_test } "
local CONTAINER_NAME = $( __handle_container_name " ${ 3 :- } " )
2022-11-25 21:58:25 +00:00
2023-02-03 22:52:30 +00:00
# Required to detect a new account and create the maildir:
_wait_for_service changedetector " ${ CONTAINER_NAME } "
2023-01-21 23:05:28 +00:00
_run_in_container setup email add " ${ MAIL_ACCOUNT } " " ${ MAIL_PASS } "
assert_success
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
_wait_until_account_maildir_exists " ${ MAIL_ACCOUNT } "
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Assert that the number of lines output by a previous command matches the given
# amount (${1}). `lines` is a special BATS variable updated via `run`.
#
# @param ${1} = number of lines that the output should have
function _should_output_number_of_lines( ) {
2023-02-06 22:59:01 +00:00
# shellcheck disable=SC2154
2023-01-21 23:05:28 +00:00
assert_equal " ${# lines [@] } " " ${ 1 : ?Number of lines not provided } "
}
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# Reloads the postfix service.
#
# @param ${1} = container name [OPTIONAL]
function _reload_postfix( ) {
local CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# Reloading Postfix config after modifying it within 2 seconds will cause Postfix to delay reading `main.cf`:
# WORKAROUND: https://github.com/docker-mailserver/docker-mailserver/pull/2998
_exec_in_container touch -d '2 seconds ago' /etc/postfix/main.cf
_exec_in_container postfix reload
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Get the IP of the container (${1}).
#
# @param ${1} = container name [OPTIONAL]
function _get_container_ip( ) {
local TARGET_CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
docker inspect --format '{{ .NetworkSettings.IPAddress }}' " ${ TARGET_CONTAINER_NAME } "
}
2022-11-25 21:58:25 +00:00
2023-01-21 23:05:28 +00:00
# Check if a container is running.
#
# @param ${1} = container name [OPTIONAL]
function _container_is_running( ) {
local TARGET_CONTAINER_NAME = $( __handle_container_name " ${ 1 :- } " )
[ [ $( docker inspect -f '{{.State.Running}}' " ${ TARGET_CONTAINER_NAME } " ) = = 'true' ] ]
2022-11-25 21:58:25 +00:00
}
2023-01-21 23:05:28 +00:00
# Checks if the directory exists and then how many files it contains at the top-level.
#
# @param ${1} = directory
# @param ${2} = number of files that should be in ${1}
2023-05-25 23:01:41 +00:00
function _count_files_in_directory_in_container( ) {
2023-01-21 23:05:28 +00:00
local DIRECTORY = ${ 1 : ?No directory provided }
local NUMBER_OF_LINES = ${ 2 : ?No line count provided }
tests(refactor): `open_dkim.bats` (#3060)
* tests(refactor): Make test cases for opendkim keysizes DRY
- These all do roughly the same logic that can be split into two separate methods.
- `_should_generate_dkim_key()` covers a bit more logic as it can be leveraged to handle other test cases that also perform the same logic.
- The `config/opendkim/` doesn't seem necessary for tests. Only the first few test cases here are testing against it, so we can conditionally make that available. `process_check_restart.bats` also depended on it to run OpenDKIM successfully, but this was due to the `setup-stack.sh` config defaults failing to find an "empty" file forcing `supervisord` to constantly restart the process..
- With this, there we inverse the default opendkim config, so we don't have to mount unique / empty subfolders for each test case, followed by copying over the two extra configs.
* tests(refactor): DRY up more test cases
All the remaining test cases but the last one were refactored here for a clean commit diff. The last test case will be refactored in the following commit.
Plenty of repeated logic spread across these test cases, now condensed into shared methods.
* tests(refactor): Make final test case DRY
* chore: Migrate to new testing helpers
* chore: Revise test case descriptions
* tests(refactor): Improve and simplify assertions
* tests(refactor): Use common container setup instead of `docker run`
- As the majority of test cases are only running `open-dkim` helper, we don't actually have to wait for a full container setup. So an alternative container start is called.
- Also improves assertions a bit more instead of just counting lines.
- Some test cases don't bind mount all of `/tmp/docker-mailserver` contents, thus don't raise permission errors on subsequent test runs.
- Instead of `rm -f` on some config files, have opted to mount them read-only instead, or alternatively mount an anonymous empty volume instead.
- Collapsed the first three test cases into one, thus no `setup_file()` necessary.
- Shift the `_wait_for_finished_setup_in_container()` method into `_common_container_setup()` instead since nothing else is using `_common_container_start()` yet, this allows for avoiding the wait.
* tests(refactor): Collapse dkim key size test cases into single test case
This makes these tests a bit more DRY, and enhances the raised quality issue with these tests. Now not only is the domain checked in the generated DNS dkim record, but we also verify the key size is corrected in the public and private keys via openssl.
* chore: Revise container names
* chore: Swap order of test case 1 and 2
* tests(refactor): Assert generated log output
- `__should_have_tables_trustedhosts_for_domain` shifted in each test case to just after generating the domains keys.
- Asserts `open-dkim` logs instead of just counting them.
- Added checks for domains that should not be present in a test case.
- Additional coverage and notes about the alias from vhost `@localdomain.com`
- Single assert statement with switch statement as all are using common args.
* chore: Minor changes
* tests(refactor): Share `find` logic in helpers and tests
* tests(fix): Listing file content does not need to match line order
The order printed from local system vs CI differed causing the CI to fail. The order of lines is irrelevant so `--index` is not required.
Additionally correct the prefix of the called method to be only one `_` now that it's a `common.bash` helper method.
* chore: Collapse custom DKIM selector test into custom DKIM domain test
These cover the same test logic for the most part, the first domain could also be testing the custom selector.
`special_use_folders.bats` + `mailbox_format_dbox` can assert lines instead, removing the need for `--partial`.
* Apply suggestions from code review
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
* chore: Split switch statement method into wrapper methods
---------
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2023-02-09 11:18:06 +00:00
_should_have_content_in_directory " ${ DIRECTORY } " '-type f'
_should_output_number_of_lines " ${ NUMBER_OF_LINES } "
}
2022-11-25 21:58:25 +00:00
tests(refactor): `open_dkim.bats` (#3060)
* tests(refactor): Make test cases for opendkim keysizes DRY
- These all do roughly the same logic that can be split into two separate methods.
- `_should_generate_dkim_key()` covers a bit more logic as it can be leveraged to handle other test cases that also perform the same logic.
- The `config/opendkim/` doesn't seem necessary for tests. Only the first few test cases here are testing against it, so we can conditionally make that available. `process_check_restart.bats` also depended on it to run OpenDKIM successfully, but this was due to the `setup-stack.sh` config defaults failing to find an "empty" file forcing `supervisord` to constantly restart the process..
- With this, there we inverse the default opendkim config, so we don't have to mount unique / empty subfolders for each test case, followed by copying over the two extra configs.
* tests(refactor): DRY up more test cases
All the remaining test cases but the last one were refactored here for a clean commit diff. The last test case will be refactored in the following commit.
Plenty of repeated logic spread across these test cases, now condensed into shared methods.
* tests(refactor): Make final test case DRY
* chore: Migrate to new testing helpers
* chore: Revise test case descriptions
* tests(refactor): Improve and simplify assertions
* tests(refactor): Use common container setup instead of `docker run`
- As the majority of test cases are only running `open-dkim` helper, we don't actually have to wait for a full container setup. So an alternative container start is called.
- Also improves assertions a bit more instead of just counting lines.
- Some test cases don't bind mount all of `/tmp/docker-mailserver` contents, thus don't raise permission errors on subsequent test runs.
- Instead of `rm -f` on some config files, have opted to mount them read-only instead, or alternatively mount an anonymous empty volume instead.
- Collapsed the first three test cases into one, thus no `setup_file()` necessary.
- Shift the `_wait_for_finished_setup_in_container()` method into `_common_container_setup()` instead since nothing else is using `_common_container_start()` yet, this allows for avoiding the wait.
* tests(refactor): Collapse dkim key size test cases into single test case
This makes these tests a bit more DRY, and enhances the raised quality issue with these tests. Now not only is the domain checked in the generated DNS dkim record, but we also verify the key size is corrected in the public and private keys via openssl.
* chore: Revise container names
* chore: Swap order of test case 1 and 2
* tests(refactor): Assert generated log output
- `__should_have_tables_trustedhosts_for_domain` shifted in each test case to just after generating the domains keys.
- Asserts `open-dkim` logs instead of just counting them.
- Added checks for domains that should not be present in a test case.
- Additional coverage and notes about the alias from vhost `@localdomain.com`
- Single assert statement with switch statement as all are using common args.
* chore: Minor changes
* tests(refactor): Share `find` logic in helpers and tests
* tests(fix): Listing file content does not need to match line order
The order printed from local system vs CI differed causing the CI to fail. The order of lines is irrelevant so `--index` is not required.
Additionally correct the prefix of the called method to be only one `_` now that it's a `common.bash` helper method.
* chore: Collapse custom DKIM selector test into custom DKIM domain test
These cover the same test logic for the most part, the first domain could also be testing the custom selector.
`special_use_folders.bats` + `mailbox_format_dbox` can assert lines instead, removing the need for `--partial`.
* Apply suggestions from code review
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
* chore: Split switch statement method into wrapper methods
---------
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2023-02-09 11:18:06 +00:00
# Checks if the directory exists and then list the top-level content.
#
# @param ${1} = directory
# @param ${2} = Additional options to `find`
function _should_have_content_in_directory( ) {
local DIRECTORY = ${ 1 : ?No directory provided }
local FIND_OPTIONS = ${ 2 :- }
2023-02-02 02:30:16 +00:00
tests(refactor): `open_dkim.bats` (#3060)
* tests(refactor): Make test cases for opendkim keysizes DRY
- These all do roughly the same logic that can be split into two separate methods.
- `_should_generate_dkim_key()` covers a bit more logic as it can be leveraged to handle other test cases that also perform the same logic.
- The `config/opendkim/` doesn't seem necessary for tests. Only the first few test cases here are testing against it, so we can conditionally make that available. `process_check_restart.bats` also depended on it to run OpenDKIM successfully, but this was due to the `setup-stack.sh` config defaults failing to find an "empty" file forcing `supervisord` to constantly restart the process..
- With this, there we inverse the default opendkim config, so we don't have to mount unique / empty subfolders for each test case, followed by copying over the two extra configs.
* tests(refactor): DRY up more test cases
All the remaining test cases but the last one were refactored here for a clean commit diff. The last test case will be refactored in the following commit.
Plenty of repeated logic spread across these test cases, now condensed into shared methods.
* tests(refactor): Make final test case DRY
* chore: Migrate to new testing helpers
* chore: Revise test case descriptions
* tests(refactor): Improve and simplify assertions
* tests(refactor): Use common container setup instead of `docker run`
- As the majority of test cases are only running `open-dkim` helper, we don't actually have to wait for a full container setup. So an alternative container start is called.
- Also improves assertions a bit more instead of just counting lines.
- Some test cases don't bind mount all of `/tmp/docker-mailserver` contents, thus don't raise permission errors on subsequent test runs.
- Instead of `rm -f` on some config files, have opted to mount them read-only instead, or alternatively mount an anonymous empty volume instead.
- Collapsed the first three test cases into one, thus no `setup_file()` necessary.
- Shift the `_wait_for_finished_setup_in_container()` method into `_common_container_setup()` instead since nothing else is using `_common_container_start()` yet, this allows for avoiding the wait.
* tests(refactor): Collapse dkim key size test cases into single test case
This makes these tests a bit more DRY, and enhances the raised quality issue with these tests. Now not only is the domain checked in the generated DNS dkim record, but we also verify the key size is corrected in the public and private keys via openssl.
* chore: Revise container names
* chore: Swap order of test case 1 and 2
* tests(refactor): Assert generated log output
- `__should_have_tables_trustedhosts_for_domain` shifted in each test case to just after generating the domains keys.
- Asserts `open-dkim` logs instead of just counting them.
- Added checks for domains that should not be present in a test case.
- Additional coverage and notes about the alias from vhost `@localdomain.com`
- Single assert statement with switch statement as all are using common args.
* chore: Minor changes
* tests(refactor): Share `find` logic in helpers and tests
* tests(fix): Listing file content does not need to match line order
The order printed from local system vs CI differed causing the CI to fail. The order of lines is irrelevant so `--index` is not required.
Additionally correct the prefix of the called method to be only one `_` now that it's a `common.bash` helper method.
* chore: Collapse custom DKIM selector test into custom DKIM domain test
These cover the same test logic for the most part, the first domain could also be testing the custom selector.
`special_use_folders.bats` + `mailbox_format_dbox` can assert lines instead, removing the need for `--partial`.
* Apply suggestions from code review
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
* chore: Split switch statement method into wrapper methods
---------
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2023-02-09 11:18:06 +00:00
_run_in_container_bash " [[ -d ${ DIRECTORY } ]] && find ${ DIRECTORY } -mindepth 1 -maxdepth 1 ${ FIND_OPTIONS } -printf '%f\n' "
assert_success
2022-11-25 21:58:25 +00:00
}
tests: Extract some test cases out from `tests.bats` (#2980)
While working on tests, I noticed that some of the configs being mounted were adding a few seconds to the start-up time of each container. Notably `postfix-*` and `dovecot.conf` config files, which have been extracted out into their own tests with those files moved into a separate config folder.
`tests.bats` has been adapted to the common setup helper, and removed ENV no longer required to run those tests. Future PRs will extract out more tests.
Review may be easier via individual commit diffs and their associated commit messages describing relevant changes.
<details>
<summary>Commit message history for reference</summary>
```markdown
tests(chore): `tests.bats` - Remove redundant config
===
- ONEDIR volume support no longer relevant, this should have been dropped.
- ClamAV ENV no longer relevant as related tests have been extracted already.
- Same with the some of the SpamAssassin ENV config.
- `VIRUSMAILS_DELETE_DELAY` is tested in the file, but doesn't use this ENV at all? (runs a separate instance to test the ENV instead)
- Hostname updated in preparation for migrating to new test helpers. Relevant test lines referencing the hostname have likewise been updated.
```
```markdown
tests(chore): `tests.bats` - Convert to common setup
===
ENV remains the same, but required adding `ENABLE_AMAVIS=1` to bring that back, while the following became redundant as they're now defaulting to explicitly disabled in the helper method:
- `ENABLE_CLAMAV=0`
- `LOG_LEVEL=debug`
- `ENABLE_UPDATE_CHECK=0`
- `--hostname` + `--tty` + standard `--volume` lines
- `-e` option expanded to long-name `--env`, and all `\` dropped as no longer necessary.
`wait_for_finished_setup_in_container` is now redundant thanks to `common_container_setup`.
```
```markdown
tests(refactor): `tests.bats` - Extract out Dovecot Sieve tests
===
Sieve test files relocated into `test/config/dovecot-sieve/` for better isolation.
`dovecot.sieve` was not using the `reject` import, and we should not encourage it? (docs still do):
https://support.tigertech.net/sieve#the-sieve-reject-jmp
```
```markdown
tests: `tests.bats` - Extract out `checking smtp` tests
===
Migrated to the standard template and copied over the original test cases with `_run_in_container` adjustment only.
Identified minimum required ENV along with which mail is required for each test case.
```
```markdown
tests(refactor): `smtp-delivery.bats`
===
- Disabled `ENABLE_SRS=1`, not necessary for these tests.
- Added a SpamAssassin related test (X-SPAM headers) which requires `SA_TAG` to properly pass (or `ENABLE_SRS=1` to deliver into inbox).
- Many lines with double quotes changed to single quote wrapping, and moving out `grep` filters into `assert_output --partial` lines instead.
- Instead of `wc -l` making failures less helpful, switch to the helper method `_should_output_number_of_lines`
- x2 `assert_output` with different EOF style of usage was not actually failing on tests when it should. Changed to assert partial output of each expected line, and count the number of lines instead.
- Added additional comments related to the test cases with a `TODO` note about `SPAMASSASSIN_SPAM_TO_INBOX=1`.
- Revised test case names, including using the common prefix var.
- `tests.bats` no longer needs to send all these emails, no other test cases require them. This affects a test checking a `/mail` folder exists which has been corrected, and a quotas test case adjusted to expect an empty quota size output.
```
```markdown
tests: `tests.bats` - Extract out test cases for config overrides
===
Slight improvement by additionally matching `postconf` output to verify the setting is properly applied.
```
```markdown
tests: `tests.bats` - Extract out Amavis SpamAssassin test case
===
Removes the need for SpamAssassin ENV in `tests.bats`.
```
</details>
2023-01-06 22:36:20 +00:00
2023-01-29 13:52:38 +00:00
# Filters a service's logs (under `/var/log/supervisor/<SERVICE>.log`) given
# a specific string.
#
# @param ${1} = service name
# @param ${2} = string to filter by
# @param ${3} = container name [OPTIONAL]
#
# ## Attention
#
# The string given to this function is interpreted by `grep -E`, i.e.
# as a regular expression. In case you use characters that are special
# in regular expressions, you need to escape them!
function _filter_service_log( ) {
local SERVICE = ${ 1 : ?Service name must be provided }
local STRING = ${ 2 : ?String to match must be provided }
local CONTAINER_NAME = $( __handle_container_name " ${ 3 :- } " )
2023-10-14 15:14:10 +00:00
local FILE = " /var/log/supervisor/ ${ SERVICE } .log "
2023-01-29 13:52:38 +00:00
2023-10-14 15:14:10 +00:00
# Fallback to alternative log location:
[ [ -f ${ FILE } ] ] || FILE = " /var/log/mail/ ${ SERVICE } .log "
_run_in_container grep -E " ${ STRING } " " ${ FILE } "
2023-01-29 13:52:38 +00:00
}
# Like `_filter_service_log` but asserts that the string was found.
#
# @param ${1} = service name
# @param ${2} = string to filter by
# @param ${3} = container name [OPTIONAL]
#
# ## Attention
#
# The string given to this function is interpreted by `grep -E`, i.e.
# as a regular expression. In case you use characters that are special
# in regular expressions, you need to escape them!
function _service_log_should_contain_string( ) {
local SERVICE = ${ 1 : ?Service name must be provided }
local STRING = ${ 2 : ?String to match must be provided }
local CONTAINER_NAME = $( __handle_container_name " ${ 3 :- } " )
_filter_service_log " ${ SERVICE } " " ${ STRING } "
assert_success
}
# Filters the mail log for lines that belong to a certain email identified
# by its ID. You can obtain the ID of an email you want to send by using
2023-02-24 09:44:18 +00:00
# `_send_email_and_get_id`.
2023-01-29 13:52:38 +00:00
#
# @param ${1} = email ID
# @param ${2} = container name [OPTIONAL]
function _print_mail_log_for_id( ) {
local MAIL_ID = ${ 1 : ?Mail ID must be provided }
local CONTAINER_NAME = $( __handle_container_name " ${ 2 :- } " )
2024-01-11 09:34:08 +00:00
_run_in_container grep -E " ${ MAIL_ID } " /var/log/mail.log
2023-01-29 13:52:38 +00:00
}
2024-01-03 00:17:54 +00:00
# A simple wrapper for netcat (`nc`). This is useful when sending
# "raw" e-mails or doing IMAP-related work.
#
# @param ${1} = the file that is given to `nc`
# @param ${1} = custom parameters for `nc` [OPTIONAL] (default: 0.0.0.0 25)
function _nc_wrapper( ) {
local FILE = ${ 1 : ?Must provide name of template file }
local NC_PARAMETERS = ${ 2 :- 0 .0.0.0 25 }
[ [ -v CONTAINER_NAME ] ] || return 1
2024-01-11 09:34:08 +00:00
_run_in_container_bash " nc ${ NC_PARAMETERS } < /tmp/docker-mailserver-test/ ${ FILE } "
2024-01-03 00:17:54 +00:00
}
2023-01-21 23:05:28 +00:00
# ? << Miscellaneous helper functions
# ! -------------------------------------------------------------------