tests(refactor): Adjust mail_changedetector + change detection helpers (#2997)

* tests(refactor): `mail_changedetector.bats` - Leverage DRY methods

`supervisorctl tail` is not the most reliably way to get logs for the latest change detection and has been known to be fragile in the past.

We can instead read the full log for the service directly with `tac` and `sed` to extract all log content since the last change detection.

Common asserts have also been extracted out into separate methods.

* tests(chore): Remove sleep and redundant change event

Container 1 is still blocked at this point from an existing lock and change event.

Make the lock stale immediately and no extra sleep is required when paired with the helper method to wait until the event is processed (which should remove the stale lock).

* tests(refactor): Add more DRY methods

- Simplify the test case so it's easier to grok.
- 2nd test case (blocking) extracts out initial setup into a separate method and merges the later service restart logic which is redundant.
- Additional comments for improved context of what is going on / expected.

* tests(chore): Revise the change detection helper method

- Add explicit counting arg to change detection support.
- Extract revised logic into it's own generic helper method.
- Utilize this for a separate method that monitors for a change event having started, but not waiting for completion.

This allows dropping the 40 sec of remaining `sleep` in `mail_changedetector` test. It was also required due to potentially missing the timing of a change event completing concurrently in a 2nd container that needed to be waited on and then checked.

* tests(chore): Migrate to current test conventions

- Switch to common container setup helpers
- Update container name and change usage to variables instead.
- Adopt the new convention of prefix variable for test cases (revised test case descriptions).

* tests(chore): Remove legacy change detection

This has since been replaced with the new helper watches the `changedetector` service logs directly instead of only detecting a change has occurred via checksum comparison.

No tests use this method anymore, it was originally for `tests.bats`. Thus the tests in `test_helper.bats` are being dropped too. The new helper has test coverage in `changedetector` tests.

* chore: Lock removal should not incur `sleep 5` afterwards

- A new lock should be created by this script after removal. The sleep doesn't help avoid a race condition with lock file creation after removal.
- Reduces test time as a bonus.
- Added some additional comments to test.

* tests(chore): `tls_letsencrypt.bats` leverage improved change detection

- No need to wait on the change detection service anymore during container startup.
- No need to count change events processed either as waiting a fixed duration is no longer relied on.
- This makes the reload count method redundant, dropped.

* tests(chore): Convert `setup-cli.bats` to new test conventions

This test file was already adapted to the original common setup helpers.

- `TEST_NAME` replaced with `CONTAINER_NAME`.
- Prefix var added, test case descriptions drop explicit prefix.
- No other changes.

* tests(chore): Extract out helpers related to change-detection

- New helper file for sharing these helpers to tests.
- Includes the helpful log method from changedetector tests.
- No longer need to maintain duplicate copies of these methods during the test migration. All tests that use them are now importing the separate helper file.
- `tls_letsencrypt.bats` has switched to using the log helper.
- Generic log count helper is removed from `test_helper/common.bash` as any test that needs it in future can adapt to `helper/common.bash`.

* tests(refactor): `tls_letsencrypt.bats` remove `_get_service_logs()`

This helper does not seem useful as moving away from `supervisorctl tail` and no other tests had a need for it.

* tests(chore): Remove common setup methods from `test_helper/common.bash`

No other tests depend on this. Future tests will adopt the revised versions from `helper/setup.bash`.

Additionally updates `helper/setup.bash` comments that are no longer applicable to `TEST_TMP_CONFIG` and `CONTAINER_NAME`.

* chore: Use `|| true` to simplify setting `EXPECTED_COUNT` correctly
This commit is contained in:
Brennan Kinney 2023-01-16 20:39:46 +13:00 committed by GitHub
parent 8b36e903a2
commit 8d80c6317f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 267 additions and 376 deletions

View file

@ -12,15 +12,16 @@ function _create_lock
LOCK_FILE="/tmp/docker-mailserver/${SCRIPT_NAME}.lock" LOCK_FILE="/tmp/docker-mailserver/${SCRIPT_NAME}.lock"
while [[ -e "${LOCK_FILE}" ]] while [[ -e "${LOCK_FILE}" ]]
do do
_log 'warn' "Lock file '${LOCK_FILE}' exists - another execution of '${SCRIPT_NAME}' is happening - trying again shortly"
# Handle stale lock files left behind on crashes # Handle stale lock files left behind on crashes
# or premature/non-graceful exits of containers while they're making changes # or premature/non-graceful exits of containers while they're making changes
if [[ -n "$(find "${LOCK_FILE}" -mmin +1 2>/dev/null)" ]] if [[ -n "$(find "${LOCK_FILE}" -mmin +1 2>/dev/null)" ]]
then then
_log 'warn' 'Lock file older than 1 minute - removing stale lock file' _log 'warn' 'Lock file older than 1 minute - removing stale lock file'
rm -f "${LOCK_FILE}" rm -f "${LOCK_FILE}"
fi else
_log 'warn' "Lock file '${LOCK_FILE}' exists - another execution of '${SCRIPT_NAME}' is happening - trying again shortly"
sleep 5 sleep 5
fi
done done
trap _remove_lock EXIT trap _remove_lock EXIT

View file

@ -0,0 +1,29 @@
#!/bin/bash
load "${REPOSITORY_ROOT}/test/helper/common"
function wait_until_change_detection_event_begins() {
local MATCH_CONTENT='Change detected'
local MATCH_IN_LOG='/var/log/supervisor/changedetector.log'
_wait_until_expected_count_is_matched "${@}"
}
# NOTE: Change events can start and finish all within < 1 sec,
# Reliably track the completion of a change event by counting events:
function wait_until_change_detection_event_completes() {
local MATCH_CONTENT='Completed handling of detected change'
local MATCH_IN_LOG='/var/log/supervisor/changedetector.log'
_wait_until_expected_count_is_matched "${@}"
}
function _get_logs_since_last_change_detection() {
local CONTAINER_NAME=${1}
local MATCH_IN_FILE='/var/log/supervisor/changedetector.log'
local MATCH_STRING='Change detected'
# Read file in reverse, collect lines until match with sed is found,
# then stop and return these lines back in original order (flipped again through tac):
docker exec "${CONTAINER_NAME}" bash -c "tac ${MATCH_IN_FILE} | sed '/${MATCH_STRING}/q' | tac"
}

View file

@ -180,40 +180,40 @@ function wait_for_service() {
container_has_service_running "${CONTAINER_NAME}" "${SERVICE_NAME}" container_has_service_running "${CONTAINER_NAME}" "${SERVICE_NAME}"
} }
function wait_for_changes_to_be_detected_in_container() {
local CONTAINER_NAME="${1}"
local TIMEOUT=${TEST_TIMEOUT_IN_SECONDS}
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; _obtain_hostname_and_domainname; cmp --silent -- <(_monitored_files_checksums) "${CHKSUM_FILE}" >/dev/null'
}
# NOTE: Relies on ENV `LOG_LEVEL=debug` or higher # NOTE: Relies on ENV `LOG_LEVEL=debug` or higher
function wait_until_change_detection_event_completes() { function _wait_until_expected_count_is_matched() {
local CONTAINER_NAME="${1}" function __get_count() {
# NOTE: `|| true` required due to `set -e` usage:
# https://github.com/docker-mailserver/docker-mailserver/pull/2997#discussion_r1070583876
docker exec "${CONTAINER_NAME}" grep --count "${MATCH_CONTENT}" "${MATCH_IN_LOG}" || true
}
# WARNING: Keep in mind it is a '>=' comparison.
# If you provide an explict count to match, ensure it is not too low to cause a false-positive.
function __has_expected_count() {
[[ $(__get_count) -ge "${EXPECTED_COUNT}" ]]
}
local CONTAINER_NAME=${1}
local EXPECTED_COUNT=${2}
# Ensure early failure if arg is missing: # Ensure early failure if arg is missing:
assert_not_equal "${CONTAINER_NAME}" "" assert_not_equal "${CONTAINER_NAME}" ''
# Ensure the container is configured with the required `LOG_LEVEL` ENV: # Ensure the container is configured with the required `LOG_LEVEL` ENV:
assert_regex \ assert_regex \
$(docker exec "${CONTAINER_NAME}" env | grep '^LOG_LEVEL=') \ $(docker exec "${CONTAINER_NAME}" env | grep '^LOG_LEVEL=') \
'=(debug|trace)$' '=(debug|trace)$'
# NOTE: Change events can start and finish all within < 1 sec, # Default behaviour is to wait until one new match is found (eg: incremented),
# Reliably track the completion of a change event by comparing the before/after count: # unless explicitly set (useful for waiting on a min count to be reached):
function __change_event_count() { if [[ -z $EXPECTED_COUNT ]]
docker exec "${CONTAINER_NAME}" grep --count "${CHANGE_EVENT_END}" /var/log/supervisor/changedetector.log then
} # +1 of starting count:
EXPECTED_COUNT=$(( $(__get_count) + 1 ))
fi
function __is_changedetector_finished() { repeat_until_success_or_timeout 20 __has_expected_count
[[ $(__change_event_count) -gt "${NUM_CHANGE_EVENTS_BEFORE}" ]]
}
# Count by completions of this debug log line from `check-for-changes.sh`:
local CHANGE_EVENT_END='Completed handling of detected change'
local NUM_CHANGE_EVENTS_BEFORE=$(__change_event_count)
repeat_until_success_or_timeout 60 __is_changedetector_finished
} }
# An account added to `postfix-accounts.cf` must wait for the `changedetector` service # An account added to `postfix-accounts.cf` must wait for the `changedetector` service

View file

@ -64,15 +64,14 @@ function wait_for_finished_setup_in_container() {
# #
# For example, if you need an immutable config volume that can't be affected by other tests # For example, if you need an immutable config volume that can't be affected by other tests
# in the file, then use `local TEST_TMP_CONFIG=$(duplicate_config_for_container . "${UNIQUE_ID_HERE}")` # in the file, then use `local TEST_TMP_CONFIG=$(duplicate_config_for_container . "${UNIQUE_ID_HERE}")`
#
# REQUIRED: `CONTAINER_NAME` must be set before this method is called.
# It only affects the `TEST_TMP_CONFIG` directory created,
# but will be used in `common_container_create()` and implicitly in other helper methods.
function init_with_defaults() { function init_with_defaults() {
__initialize_variables __initialize_variables
export TEST_TMP_CONFIG export TEST_TMP_CONFIG
# In `setup_file()` the default name to use for the currently tested docker container
# is `${CONTAINER_NAME}` global defined here. It derives the name from the test filename:
# `basename` to ignore absolute dir path and file extension, only extract filename.
# In `setup_file()` creates a single copy of the test config folder to use for an entire test file:
TEST_TMP_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}") TEST_TMP_CONFIG=$(duplicate_config_for_container . "${CONTAINER_NAME}")
# Common complimentary test files, read-only safe to share across containers: # Common complimentary test files, read-only safe to share across containers:

View file

@ -173,42 +173,6 @@ function wait_for_service() {
container_has_service_running "${CONTAINER_NAME}" "${SERVICE_NAME}" container_has_service_running "${CONTAINER_NAME}" "${SERVICE_NAME}"
} }
function wait_for_changes_to_be_detected_in_container() {
local CONTAINER_NAME="${1}"
local TIMEOUT=${TEST_TIMEOUT_IN_SECONDS}
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; _obtain_hostname_and_domainname; cmp --silent -- <(_monitored_files_checksums) "${CHKSUM_FILE}" >/dev/null'
}
# NOTE: Relies on ENV `LOG_LEVEL=debug` or higher
function wait_until_change_detection_event_completes() {
local CONTAINER_NAME="${1}"
# Ensure early failure if arg is missing:
assert_not_equal "${CONTAINER_NAME}" ""
# Ensure the container is configured with the required `LOG_LEVEL` ENV:
assert_regex \
$(docker exec "${CONTAINER_NAME}" env | grep '^LOG_LEVEL=') \
'=(debug|trace)$'
# NOTE: Change events can start and finish all within < 1 sec,
# Reliably track the completion of a change event by comparing the before/after count:
function __change_event_count() {
docker exec "${CONTAINER_NAME}" grep --count "${CHANGE_EVENT_END}" /var/log/supervisor/changedetector.log
}
function __is_changedetector_finished() {
[[ $(__change_event_count) -gt "${NUM_CHANGE_EVENTS_BEFORE}" ]]
}
# Count by completions of this debug log line from `check-for-changes.sh`:
local CHANGE_EVENT_END='Completed handling of detected change'
local NUM_CHANGE_EVENTS_BEFORE=$(__change_event_count)
repeat_until_success_or_timeout 60 __is_changedetector_finished
}
# An account added to `postfix-accounts.cf` must wait for the `changedetector` service # 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: # to process the update before Dovecot creates the mail account and associated storage dir:
function wait_until_account_maildir_exists() { function wait_until_account_maildir_exists() {
@ -241,72 +205,3 @@ function wait_for_empty_mail_queue_in_container() {
# shellcheck disable=SC2016 # shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c '[[ $(mailq) == *"Mail queue is empty"* ]]' repeat_in_container_until_success_or_timeout "${TIMEOUT}" "${CONTAINER_NAME}" bash -c '[[ $(mailq) == *"Mail queue is empty"* ]]'
} }
# Common defaults appropriate for most tests, override vars in each test when necessary.
# For all tests override in `setup_file()` via an `export` var.
# For individual test override the var via `local` var instead.
#
# For example, if you need an immutable config volume that can't be affected by other tests
# in the file, then use `local TEST_TMP_CONFIG=$(duplicate_config_for_container . "${UNIQUE_ID_HERE}")`
function init_with_defaults() {
export TEST_NAME TEST_TMP_CONFIG
# In `setup_file()` the default name to use for the currently tested docker container
# is `${TEST_NAME}` global defined here. It derives the name from the test filename:
# `basename` to ignore absolute dir path and file extension, only extract filename.
TEST_NAME=$(basename "${BATS_TEST_FILENAME}" '.bats')
# In `setup_file()` creates a single copy of the test config folder to use for an entire test file:
TEST_TMP_CONFIG=$(duplicate_config_for_container . "${TEST_NAME}")
# Common complimentary test files, read-only safe to share across containers:
export TEST_FILES_CONTAINER_PATH='/tmp/docker-mailserver-test'
export TEST_FILES_VOLUME="${PWD}/test/test-files:${TEST_FILES_CONTAINER_PATH}:ro"
# The config volume cannot be read-only as some data needs to be written at container startup
# - two sed failures (unknown lines)
# - dovecot-quotas.cf (setup-stack.sh:_setup_dovecot_quotas)
# - postfix-aliases.cf (setup-stack.sh:_setup_postfix_aliases)
# TODO: Check how many tests need write access. Consider using `docker create` + `docker cp` for easier cleanup.
export TEST_CONFIG_VOLUME="${TEST_TMP_CONFIG}:/tmp/docker-mailserver"
# The common default FQDN assigned to the container `--hostname` option:
export TEST_FQDN='mail.my-domain.com'
# Default Root CA cert used in TLS tests with `openssl` commands:
export TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/ecdsa/ca-cert.ecdsa.pem"
}
# Using `create` and `start` instead of only `run` allows to modify
# the container prior to starting it. Otherwise use this combined method.
# NOTE: Forwards all args to the create method at present.
function common_container_setup() {
common_container_create "$@"
common_container_start
}
# Common docker setup is centralized here.
#
# `X_EXTRA_ARGS` - Optional: Pass an array by it's variable name as a string, it will
# be used as a reference for appending extra config into the `docker create` below:
#
# NOTE: Using array reference for a single input parameter, as this method is still
# under development while adapting tests to it and requirements it must serve (eg: support base config matrix in CI)
function common_container_create() {
[[ -n ${1} ]] && local -n X_EXTRA_ARGS=${1}
run docker create --name "${TEST_NAME}" \
--hostname "${TEST_FQDN}" \
--tty \
--volume "${TEST_FILES_VOLUME}" \
--volume "${TEST_CONFIG_VOLUME}" \
"${X_EXTRA_ARGS[@]}" \
"${NAME}"
assert_success
}
function common_container_start() {
run docker start "${TEST_NAME}"
assert_success
wait_for_finished_setup_in_container "${TEST_NAME}"
}

View file

@ -1,5 +1,6 @@
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common" load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/change-detection"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/tls" load "${REPOSITORY_ROOT}/test/helper/tls"
BATS_TEST_NAME_PREFIX='[Security] (TLS) (SSL_TYPE=letsencrypt) ' BATS_TEST_NAME_PREFIX='[Security] (TLS) (SSL_TYPE=letsencrypt) '
@ -103,18 +104,13 @@ function _initial_setup() {
) )
common_container_setup 'CUSTOM_SETUP_ARGUMENTS' common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_service "${CONTAINER_NAME}" 'changedetector' wait_for_service "${CONTAINER_NAME}" 'changedetector'
# Wait until the changedetector service startup delay is over:
repeat_until_success_or_timeout 20 sh -c "$(_get_service_logs 'changedetector') | grep 'Changedetector is ready'"
} }
# Test `acme.json` extraction works at container startup: # Test `acme.json` extraction works at container startup:
# It should have already extracted `mail.example.test` from the original mounted `acme.json`. # It should have already extracted `mail.example.test` from the original mounted `acme.json`.
function _acme_ecdsa() { function _acme_ecdsa() {
_should_have_succeeded_at_extraction 'mail.example.test' # SSL_DOMAIN value should not be present in current `acme.json`:
_should_fail_to_extract_for_wildcard_env
# SSL_DOMAIN set as ENV, but startup should not have match in `acme.json`:
_should_have_failed_at_extraction '*.example.test' 'mailserver'
_should_have_valid_config 'mail.example.test' 'key.pem' 'fullchain.pem' _should_have_valid_config 'mail.example.test' 'key.pem' 'fullchain.pem'
local ECDSA_KEY_PATH="${LOCAL_BASE_PATH}/key.ecdsa.pem" local ECDSA_KEY_PATH="${LOCAL_BASE_PATH}/key.ecdsa.pem"
@ -127,7 +123,6 @@ function _initial_setup() {
# It should replace the cert files in the existing `letsencrypt/live/mail.example.test/` folder. # It should replace the cert files in the existing `letsencrypt/live/mail.example.test/` folder.
function _acme_rsa() { function _acme_rsa() {
_should_extract_on_changes 'mail.example.test' "${LOCAL_BASE_PATH}/rsa.acme.json" _should_extract_on_changes 'mail.example.test' "${LOCAL_BASE_PATH}/rsa.acme.json"
_should_have_service_reload_count '1'
local RSA_KEY_PATH="${LOCAL_BASE_PATH}/key.rsa.pem" local RSA_KEY_PATH="${LOCAL_BASE_PATH}/key.rsa.pem"
local RSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.rsa.pem" local RSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.rsa.pem"
@ -139,7 +134,6 @@ function _initial_setup() {
# Wildcard `*.example.test` should extract to `example.test/` in `letsencrypt/live/`: # Wildcard `*.example.test` should extract to `example.test/` in `letsencrypt/live/`:
function _acme_wildcard() { function _acme_wildcard() {
_should_extract_on_changes 'example.test' "${LOCAL_BASE_PATH}/wildcard/rsa.acme.json" _should_extract_on_changes 'example.test' "${LOCAL_BASE_PATH}/wildcard/rsa.acme.json"
_should_have_service_reload_count '2'
# As the FQDN has changed since startup, the Postfix + Dovecot configs should be updated: # As the FQDN has changed since startup, the Postfix + Dovecot configs should be updated:
_should_have_valid_config 'example.test' 'key.pem' 'fullchain.pem' _should_have_valid_config 'example.test' 'key.pem' 'fullchain.pem'
@ -194,21 +188,17 @@ function _has_matching_line() {
# Traefik `acme.json` specific # Traefik `acme.json` specific
# #
# It should log success of extraction for the expected domain and restart Postfix. function _should_fail_to_extract_for_wildcard_env() {
function _should_have_succeeded_at_extraction() { # Set as value for ENV `SSL_DOMAIN`, but during startup it should fail to find a match in the current `acme.json`:
local EXPECTED_DOMAIN=${1} local DOMAIN_WILDCARD='*.example.test'
local SERVICE=${2} # The expected domain to be found and extracted instead (value from container `--hostname`):
local DOMAIN_MAIL='mail.example.test'
run $(_get_service_logs "${SERVICE}") # /var/log/mail/mail.log is not equivalent to stdout content,
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'" # Relevant log content only available via docker logs:
} run docker logs "${CONTAINER_NAME}"
assert_output --partial "_extract_certs_from_acme | Unable to find key and/or cert for '${DOMAIN_WILDCARD}' in '/etc/letsencrypt/acme.json'"
function _should_have_failed_at_extraction() { assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${DOMAIN_MAIL}'"
local EXPECTED_DOMAIN=${1}
local SERVICE=${2}
run $(_get_service_logs "${SERVICE}")
assert_output --partial "_extract_certs_from_acme | Unable to find key and/or cert for '${EXPECTED_DOMAIN}' in '/etc/letsencrypt/acme.json'"
} }
# Replace the mounted `acme.json` and wait to see if changes were detected. # Replace the mounted `acme.json` and wait to see if changes were detected.
@ -217,25 +207,12 @@ function _should_extract_on_changes() {
local ACME_JSON=${2} local ACME_JSON=${2}
cp "${ACME_JSON}" "${TEST_TMP_CONFIG}/letsencrypt/acme.json" cp "${ACME_JSON}" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
# Change detection takes a little over 5 seconds to complete (restart services) wait_until_change_detection_event_completes "${CONTAINER_NAME}"
sleep 10
# Expected log lines from the changedetector service: # Expected log lines from the changedetector service:
run $(_get_service_logs 'changedetector') run _get_logs_since_last_change_detection "${CONTAINER_NAME}"
assert_output --partial 'Change detected'
assert_output --partial "'/etc/letsencrypt/acme.json' has changed - extracting certificates" assert_output --partial "'/etc/letsencrypt/acme.json' has changed - extracting certificates"
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'" assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'"
assert_output --partial 'Reloading services due to detected changes'
assert_output --partial 'Completed handling of detected change'
}
# Ensure change detection is not mistakenly validating against previous change events:
function _should_have_service_reload_count() {
local NUM_RELOADS=${1}
# Count how many times processes (like Postfix and Dovecot) have been reloaded by the `changedetector` service:
_run_in_container grep --count 'Completed handling of detected change' '/var/log/supervisor/changedetector.log'
assert_output "${NUM_RELOADS}"
} }
# Extracted cert files from `acme.json` have content matching the expected reference files: # Extracted cert files from `acme.json` have content matching the expected reference files:
@ -278,18 +255,3 @@ function _should_be_equal_in_content() {
assert_output "$(cat "${LOCAL_PATH}")" assert_output "$(cat "${LOCAL_PATH}")"
assert_success assert_success
} }
function _get_service_logs() {
local SERVICE=${1:-'mailserver'}
local CMD_LOGS=(docker exec "${CONTAINER_NAME}" "supervisorctl tail -2200 ${SERVICE}")
# As the `mailserver` service logs are not stored in a file but output to stdout/stderr,
# The `supervisorctl tail` command won't work; we must instead query via `docker logs`:
if [[ ${SERVICE} == 'mailserver' ]]
then
CMD_LOGS=(docker logs "${CONTAINER_NAME}")
fi
echo "${CMD_LOGS[@]}"
}

View file

@ -1,4 +1,5 @@
load "${REPOSITORY_ROOT}/test/helper/common" load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/change-detection"
load "${REPOSITORY_ROOT}/test/helper/setup" load "${REPOSITORY_ROOT}/test/helper/setup"
BATS_TEST_NAME_PREFIX='[SMTP] (delivery) ' BATS_TEST_NAME_PREFIX='[SMTP] (delivery) '

View file

@ -1,87 +1,139 @@
load "${REPOSITORY_ROOT}/test/test_helper/common" load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/change-detection"
load "${REPOSITORY_ROOT}/test/helper/setup"
# Note if tests fail asserting against `supervisorctl tail changedetector` output, BATS_TEST_NAME_PREFIX='[Change Detection] '
# use `supervisorctl tail -<num bytes> changedetector` instead to increase log output.
# Default `<num bytes>` appears to be around 1500. CONTAINER1_NAME='dms-test_changedetector_one'
CONTAINER2_NAME='dms-test_changedetector_two'
function setup_file() { function setup_file() {
local PRIVATE_CONFIG export CONTAINER_NAME
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_changedetector_one)
docker run -d --name mail_changedetector_one \ local CUSTOM_SETUP_ARGUMENTS=(
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ --env LOG_LEVEL=trace
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \ )
-e LOG_LEVEL=trace \
-h mail.my-domain.com -t "${NAME}"
docker run -d --name mail_changedetector_two \ CONTAINER_NAME=${CONTAINER1_NAME}
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \ init_with_defaults
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \ common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
-e LOG_LEVEL=trace \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_changedetector_one CONTAINER_NAME=${CONTAINER2_NAME}
wait_for_finished_setup_in_container mail_changedetector_two # NOTE: No `init_with_defaults` used here,
# Intentionally sharing previous containers config instead.
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
# Set default implicit container fallback for helpers:
CONTAINER_NAME=${CONTAINER1_NAME}
} }
function teardown_file() { function teardown_file() {
docker rm -f mail_changedetector_one docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}"
docker rm -f mail_changedetector_two
} }
@test "checking changedetector: servers are ready" { @test "changedetector service is ready" {
wait_for_service mail_changedetector_one changedetector wait_for_service "${CONTAINER1_NAME}" changedetector
wait_for_service mail_changedetector_two changedetector wait_for_service "${CONTAINER2_NAME}" changedetector
} }
@test "checking changedetector: can detect changes & between two containers using same config" { # NOTE: Non-deterministic behaviour - One container will perform change detection before the other.
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf" # Depending on the timing of the other container checking for a lock file, the lock may no longer be
sleep 25 # present, avoiding the 5 second delay. The first container to create a lock is not deterministic either.
# NOTE: Change detection at this point typically occurs at 2 or 4 seconds since the service was up,
# thus expect 2-8 seconds to complete.
@test "should detect and process changes (in both containers with shared config)" {
_create_change_event
run docker exec mail_changedetector_one /bin/bash -c 'supervisorctl tail -3000 changedetector' _should_perform_standard_change_event "${CONTAINER1_NAME}"
assert_output --partial 'Change detected' _should_perform_standard_change_event "${CONTAINER2_NAME}"
assert_output --partial 'Reloading services due to detected changes' }
assert_output --partial 'Removed lock'
assert_output --partial 'Completed handling of detected change'
run docker exec mail_changedetector_two /bin/bash -c 'supervisorctl tail -3000 changedetector' # Both containers should acknowledge the foreign lock file added,
assert_output --partial 'Change detected' # blocking an attempt to process the pending change event detected.
@test "should find existing lock file and block processing changes (without removing lock)" {
_prepare_blocking_lock_test
# Wait until the 2nd change event attempts to process:
_should_block_change_event_from_processing "${CONTAINER1_NAME}" 2
# NOTE: Although the service is restarted, a change detection should still occur (previous checksum still exists):
_should_block_change_event_from_processing "${CONTAINER2_NAME}" 1
}
@test "should remove lock file when stale" {
# Avoid a race condition (to remove the lock file) by removing the 2nd container:
docker rm -f "${CONTAINER2_NAME}"
# Make the previously created lock file become stale:
docker exec "${CONTAINER1_NAME}" touch -d '60 seconds ago' /tmp/docker-mailserver/check-for-changes.sh.lock
# A 2nd change event should complete (or may already have if quick enough?):
wait_until_change_detection_event_completes "${CONTAINER1_NAME}" 2
# Should have removed the stale lock file, then handle the change event:
run _get_logs_since_last_change_detection "${CONTAINER1_NAME}"
assert_output --partial 'Lock file older than 1 minute - removing stale lock file'
_assert_has_standard_change_event_logs
}
function _should_perform_standard_change_event() {
local CONTAINER_NAME=${1}
# Wait for change detection event to start and complete processing:
# NOTE: An explicit count is provided as the 2nd container may have already completed processing.
wait_until_change_detection_event_completes "${CONTAINER_NAME}" 1
# Container should have created it's own lock file,
# and later removed it when finished processing:
run _get_logs_since_last_change_detection "${CONTAINER_NAME}"
_assert_has_standard_change_event_logs
}
function _should_block_change_event_from_processing() {
local CONTAINER_NAME=${1}
local EXPECTED_COUNT=${2}
# Once the next change event has started, the processing blocked log ('another execution') should be present:
wait_until_change_detection_event_begins "${CONTAINER_NAME}" "${EXPECTED_COUNT}"
run _get_logs_since_last_change_detection "${CONTAINER_NAME}"
_assert_foreign_lock_exists
# This additionally verifies that the change event processing is incomplete (blocked):
_assert_no_lock_actions_performed
}
function _assert_has_standard_change_event_logs() {
assert_output --partial "Creating lock '/tmp/docker-mailserver/check-for-changes.sh.lock'"
assert_output --partial 'Reloading services due to detected changes' assert_output --partial 'Reloading services due to detected changes'
assert_output --partial 'Removed lock' assert_output --partial 'Removed lock'
assert_output --partial 'Completed handling of detected change' assert_output --partial 'Completed handling of detected change'
} }
@test "checking changedetector: lock file found, blocks, and doesn't get prematurely removed" { function _assert_foreign_lock_exists() {
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl stop changedetector" assert_output --partial "Lock file '/tmp/docker-mailserver/check-for-changes.sh.lock' exists"
docker exec mail_changedetector_one /bin/bash -c "touch /tmp/docker-mailserver/check-for-changes.sh.lock" assert_output --partial "- another execution of 'check-for-changes.sh' is happening - trying again shortly"
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl start changedetector"
sleep 15
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail changedetector"
assert_output --partial "another execution of 'check-for-changes.sh' is happening"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl tail changedetector"
assert_output --partial "another execution of 'check-for-changes.sh' is happening"
# Ensure starting a new check-for-changes.sh instance (restarting here) doesn't delete the lock
docker exec mail_changedetector_two /bin/bash -c "rm -f /var/log/supervisor/changedetector.log"
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl restart changedetector"
sleep 5
run docker exec mail_changedetector_two /bin/bash -c "supervisorctl tail changedetector"
refute_output --partial "another execution of 'check-for-changes.sh' is happening"
refute_output --partial "Removed lock"
} }
@test "checking changedetector: lock stale and cleaned up" { function _assert_no_lock_actions_performed() {
docker rm -f mail_changedetector_two refute_output --partial 'Lock file older than 1 minute - removing stale lock file'
docker exec mail_changedetector_one /bin/bash -c "touch /tmp/docker-mailserver/check-for-changes.sh.lock" refute_output --partial "Creating lock '/tmp/docker-mailserver/check-for-changes.sh.lock'"
echo "" >> "$(private_config_path mail_changedetector_one)/postfix-accounts.cf" refute_output --partial 'Removed lock'
sleep 15 }
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail changedetector" function _prepare_blocking_lock_test {
assert_output --partial "another execution of 'check-for-changes.sh' is happening" # Temporarily disable the Container2 changedetector service:
sleep 65 docker exec "${CONTAINER2_NAME}" bash -c 'supervisorctl stop changedetector'
docker exec "${CONTAINER2_NAME}" bash -c 'rm -f /var/log/supervisor/changedetector.log'
run docker exec mail_changedetector_one /bin/bash -c "supervisorctl tail -3000 changedetector"
assert_output --partial "removing stale lock file" # Create a foreign lock file to prevent change processing (in both containers):
docker exec "${CONTAINER1_NAME}" bash -c 'touch /tmp/docker-mailserver/check-for-changes.sh.lock'
# Create a new change to detect (that the foreign lock should prevent from processing):
_create_change_event
# Restore Container2 changedetector service:
# NOTE: The last known checksum is retained in Container2,
# It will be compared to and start a change event.
docker exec "${CONTAINER2_NAME}" bash -c 'supervisorctl start changedetector'
}
function _create_change_event() {
echo '' >>"${TEST_TMP_CONFIG}/postfix-accounts.cf"
} }

View file

@ -1,7 +1,9 @@
load "${REPOSITORY_ROOT}/test/test_helper/common" load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/change-detection"
load "${REPOSITORY_ROOT}/test/helper/setup"
# Globals referenced from `test_helper/common`: BATS_TEST_NAME_PREFIX='[setup.sh] '
# TEST_NAME (should match the filename, minus the bats extension) CONTAINER_NAME='dms-test_setup-cli'
# This is a bare minimal container setup. # This is a bare minimal container setup.
# All test-cases run sequentially against the same container instance, # All test-cases run sequentially against the same container instance,
@ -21,29 +23,29 @@ function setup_file() {
} }
function teardown_file() { function teardown_file() {
docker rm -f "${TEST_NAME}" docker rm -f "${CONTAINER_NAME}"
} }
@test "checking setup.sh: show usage when no arguments provided" { @test "show usage when no arguments provided" {
run ./setup.sh run ./setup.sh
assert_success assert_success
assert_output --partial "This is the main administration script that you use for all your interactions with" assert_output --partial "This is the main administration script that you use for all your interactions with"
} }
@test "checking setup.sh: exit with error when wrong arguments provided" { @test "exit with error when wrong arguments provided" {
run ./setup.sh lol troll run ./setup.sh lol troll
assert_failure assert_failure
assert_line --index 0 --partial "The command 'lol troll' is invalid." assert_line --index 0 --partial "The command 'lol troll' is invalid."
} }
# Create a new account for subsequent tests to depend upon # Create a new account for subsequent tests to depend upon
@test "checking setup.sh: setup.sh email add and login" { @test "email add (with login)" {
local MAIL_ACCOUNT='user@example.com' local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password' local MAIL_PASS='test_password'
local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf" local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf"
# Create an account # Create an account
run ./setup.sh -c "${TEST_NAME}" email add "${MAIL_ACCOUNT}" "${MAIL_PASS}" run ./setup.sh -c "${CONTAINER_NAME}" email add "${MAIL_ACCOUNT}" "${MAIL_PASS}"
assert_success assert_success
# Verify account was added to `postfix-accounts.cf`: # Verify account was added to `postfix-accounts.cf`:
@ -53,25 +55,25 @@ function teardown_file() {
# Ensure you wait until `changedetector` is finished. # Ensure you wait until `changedetector` is finished.
# Mail account and storage directory should now be valid # Mail account and storage directory should now be valid
wait_until_change_detection_event_completes "${TEST_NAME}" wait_until_change_detection_event_completes "${CONTAINER_NAME}"
# Verify mail storage directory exists (polls if storage is slow, eg remote mount): # Verify mail storage directory exists (polls if storage is slow, eg remote mount):
wait_until_account_maildir_exists "${TEST_NAME}" "${MAIL_ACCOUNT}" wait_until_account_maildir_exists "${CONTAINER_NAME}" "${MAIL_ACCOUNT}"
# Verify account authentication is successful (account added to Dovecot UserDB+PassDB): # Verify account authentication is successful (account added to Dovecot UserDB+PassDB):
wait_for_service "${TEST_NAME}" dovecot wait_for_service "${CONTAINER_NAME}" dovecot
local RESPONSE local RESPONSE
RESPONSE=$(docker exec "${TEST_NAME}" doveadm auth test "${MAIL_ACCOUNT}" "${MAIL_PASS}" | grep 'passdb') RESPONSE=$(docker exec "${CONTAINER_NAME}" doveadm auth test "${MAIL_ACCOUNT}" "${MAIL_PASS}" | grep 'passdb')
assert_equal "${RESPONSE}" "passdb: ${MAIL_ACCOUNT} auth succeeded" assert_equal "${RESPONSE}" "passdb: ${MAIL_ACCOUNT} auth succeeded"
} }
@test "checking setup.sh: setup.sh email list" { @test "email list" {
run ./setup.sh -c "${TEST_NAME}" email list run ./setup.sh -c "${CONTAINER_NAME}" email list
assert_success assert_success
} }
# Update an existing account # Update an existing account
@test "checking setup.sh: setup.sh email update" { @test "email update" {
local MAIL_ACCOUNT='user@example.com' local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password' local MAIL_PASS='test_password'
local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf" local DATABASE_ACCOUNTS="${TEST_TMP_CONFIG}/postfix-accounts.cf"
@ -83,12 +85,12 @@ function teardown_file() {
# Update the password should be successful: # Update the password should be successful:
local NEW_PASS='new_password' local NEW_PASS='new_password'
run ./setup.sh -c "${TEST_NAME}" email update "${MAIL_ACCOUNT}" "${NEW_PASS}" run ./setup.sh -c "${CONTAINER_NAME}" email update "${MAIL_ACCOUNT}" "${NEW_PASS}"
refute_output --partial 'Password must not be empty' refute_output --partial 'Password must not be empty'
assert_success assert_success
# NOTE: this was put in place for the next test `setup.sh email del` to properly work. # NOTE: this was put in place for the next test `setup.sh email del` to properly work.
wait_until_change_detection_event_completes "${TEST_NAME}" wait_until_change_detection_event_completes "${CONTAINER_NAME}"
# `postfix-accounts.cf` should have an updated password hash stored: # `postfix-accounts.cf` should have an updated password hash stored:
local NEW_PASS_HASH local NEW_PASS_HASH
@ -97,7 +99,7 @@ function teardown_file() {
assert_not_equal "${NEW_PASS_HASH}" "${MAIL_PASS_HASH}" assert_not_equal "${NEW_PASS_HASH}" "${MAIL_PASS_HASH}"
# Verify Dovecot derives NEW_PASS_HASH from NEW_PASS: # Verify Dovecot derives NEW_PASS_HASH from NEW_PASS:
run docker exec "${TEST_NAME}" doveadm pw -t "${NEW_PASS_HASH}" -p "${NEW_PASS}" run docker exec "${CONTAINER_NAME}" doveadm pw -t "${NEW_PASS_HASH}" -p "${NEW_PASS}"
refute_output 'Fatal: reverse password verification check failed: Password mismatch' refute_output 'Fatal: reverse password verification check failed: Password mismatch'
assert_output "${NEW_PASS_HASH} (verified)" assert_output "${NEW_PASS_HASH} (verified)"
} }
@ -107,19 +109,19 @@ function teardown_file() {
# has no support to mount a volume to `/var/mail` (only via `-c` to use a running container), # has no support to mount a volume to `/var/mail` (only via `-c` to use a running container),
# thus the `-y` option to delete the account maildir has no effect nor informs the user. # thus the `-y` option to delete the account maildir has no effect nor informs the user.
# https://github.com/docker-mailserver/docker-mailserver/issues/949 # https://github.com/docker-mailserver/docker-mailserver/issues/949
@test "checking setup.sh: setup.sh email del" { @test "email del" {
local MAIL_ACCOUNT='user@example.com' local MAIL_ACCOUNT='user@example.com'
local MAIL_PASS='test_password' local MAIL_PASS='test_password'
# Account deletion is successful: # Account deletion is successful:
run ./setup.sh -c "${TEST_NAME}" email del -y "${MAIL_ACCOUNT}" run ./setup.sh -c "${CONTAINER_NAME}" email del -y "${MAIL_ACCOUNT}"
assert_success assert_success
# NOTE: Sometimes the directory still exists, possibly from change detection # NOTE: Sometimes the directory still exists, possibly from change detection
# of the previous test (`email udpate`) triggering. Therefore, the function # of the previous test (`email udpate`) triggering. Therefore, the function
# `wait_until_change_detection_event_completes was added to the # `wait_until_change_detection_event_completes was added to the
# `setup.sh email update` test. # `setup.sh email update` test.
repeat_in_container_until_success_or_timeout 60 "${TEST_NAME}" bash -c '[[ ! -d /var/mail/example.com/user ]]' repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c '[[ ! -d /var/mail/example.com/user ]]'
# Account is not present in `postfix-accounts.cf`: # Account is not present in `postfix-accounts.cf`:
run grep "${MAIL_ACCOUNT}" "${TEST_TMP_CONFIG}/postfix-accounts.cf" run grep "${MAIL_ACCOUNT}" "${TEST_TMP_CONFIG}/postfix-accounts.cf"
@ -130,83 +132,83 @@ function teardown_file() {
# which will rebuild Dovecots accounts from scratch. # which will rebuild Dovecots accounts from scratch.
} }
@test "checking setup.sh: setup.sh email restrict" { @test "email restrict" {
run ./setup.sh -c "${TEST_NAME}" email restrict run ./setup.sh -c "${CONTAINER_NAME}" email restrict
assert_failure assert_failure
run ./setup.sh -c "${TEST_NAME}" email restrict add run ./setup.sh -c "${CONTAINER_NAME}" email restrict add
assert_failure assert_failure
./setup.sh -c "${TEST_NAME}" email restrict add send lorem@impsum.org ./setup.sh -c "${CONTAINER_NAME}" email restrict add send lorem@impsum.org
run ./setup.sh -c "${TEST_NAME}" email restrict list send run ./setup.sh -c "${CONTAINER_NAME}" email restrict list send
assert_output --regexp "^lorem@impsum.org.*REJECT" assert_output --regexp "^lorem@impsum.org.*REJECT"
run ./setup.sh -c "${TEST_NAME}" email restrict del send lorem@impsum.org run ./setup.sh -c "${CONTAINER_NAME}" email restrict del send lorem@impsum.org
assert_success assert_success
run ./setup.sh -c "${TEST_NAME}" email restrict list send run ./setup.sh -c "${CONTAINER_NAME}" email restrict list send
assert_output --partial "Everyone is allowed" assert_output --partial "Everyone is allowed"
./setup.sh -c "${TEST_NAME}" email restrict add receive rec_lorem@impsum.org ./setup.sh -c "${CONTAINER_NAME}" email restrict add receive rec_lorem@impsum.org
run ./setup.sh -c "${TEST_NAME}" email restrict list receive run ./setup.sh -c "${CONTAINER_NAME}" email restrict list receive
assert_output --regexp "^rec_lorem@impsum.org.*REJECT" assert_output --regexp "^rec_lorem@impsum.org.*REJECT"
run ./setup.sh -c "${TEST_NAME}" email restrict del receive rec_lorem@impsum.org run ./setup.sh -c "${CONTAINER_NAME}" email restrict del receive rec_lorem@impsum.org
assert_success assert_success
} }
# alias # alias
@test "checking setup.sh: setup.sh alias list" { @test "alias list" {
run ./setup.sh -c "${TEST_NAME}" alias list run ./setup.sh -c "${CONTAINER_NAME}" alias list
assert_success assert_success
assert_output --partial "alias1@localhost.localdomain user1@localhost.localdomain" assert_output --partial "alias1@localhost.localdomain user1@localhost.localdomain"
assert_output --partial "@localdomain2.com user1@localhost.localdomain" assert_output --partial "@localdomain2.com user1@localhost.localdomain"
} }
@test "checking setup.sh: setup.sh alias add" { @test "alias add" {
./setup.sh -c "${TEST_NAME}" alias add alias@example.com target1@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias add alias@example.com target1@forward.com
./setup.sh -c "${TEST_NAME}" alias add alias@example.com target2@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias add alias@example.com target2@forward.com
./setup.sh -c "${TEST_NAME}" alias add alias2@example.org target3@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias add alias2@example.org target3@forward.com
sleep 5 sleep 5
run grep "alias@example.com target1@forward.com,target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "alias@example.com target1@forward.com,target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_success assert_success
} }
@test "checking setup.sh: setup.sh alias del" { @test "alias del" {
./setup.sh -c "${TEST_NAME}" alias del alias@example.com target1@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias del alias@example.com target1@forward.com
run grep "target1@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "target1@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure assert_failure
run grep "target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "target2@forward.com" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_output "alias@example.com target2@forward.com" assert_output "alias@example.com target2@forward.com"
./setup.sh -c "${TEST_NAME}" alias del alias@example.org target2@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias del alias@example.org target2@forward.com
run grep "alias@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "alias@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure assert_failure
run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_success assert_success
./setup.sh -c "${TEST_NAME}" alias del alias2@example.org target3@forward.com ./setup.sh -c "${CONTAINER_NAME}" alias del alias2@example.org target3@forward.com
run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf" run grep "alias2@example.org" "${TEST_TMP_CONFIG}/postfix-virtual.cf"
assert_failure assert_failure
} }
# quota # quota
@test "checking setup.sh: setup.sh setquota" { @test "setquota" {
./setup.sh -c "${TEST_NAME}" email add quota_user@example.com test_password ./setup.sh -c "${CONTAINER_NAME}" email add quota_user@example.com test_password
./setup.sh -c "${TEST_NAME}" email add quota_user2@example.com test_password ./setup.sh -c "${CONTAINER_NAME}" email add quota_user2@example.com test_password
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 12M run ./setup.sh -c "${CONTAINER_NAME}" quota set quota_user@example.com 12M
assert_success assert_success
run ./setup.sh -c "${TEST_NAME}" quota set 51M quota_user@example.com run ./setup.sh -c "${CONTAINER_NAME}" quota set 51M quota_user@example.com
assert_failure assert_failure
run ./setup.sh -c "${TEST_NAME}" quota set unknown@domain.com 150M run ./setup.sh -c "${CONTAINER_NAME}" quota set unknown@domain.com 150M
assert_failure assert_failure
run ./setup.sh -c "${TEST_NAME}" quota set quota_user2 51M run ./setup.sh -c "${CONTAINER_NAME}" quota set quota_user2 51M
assert_failure assert_failure
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success assert_success
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 26M run ./setup.sh -c "${CONTAINER_NAME}" quota set quota_user@example.com 26M
assert_success assert_success
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:26M\$' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:26M\$' | wc -l | grep 1"
assert_success assert_success
@ -216,47 +218,47 @@ function teardown_file() {
} }
# `quota_user@example.com` created in previous `setquota` test # `quota_user@example.com` created in previous `setquota` test
@test "checking setup.sh: setup.sh delquota" { @test "delquota" {
run ./setup.sh -c "${TEST_NAME}" quota set quota_user@example.com 12M run ./setup.sh -c "${CONTAINER_NAME}" quota set quota_user@example.com 12M
assert_success assert_success
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success assert_success
run ./setup.sh -c "${TEST_NAME}" quota del unknown@domain.com run ./setup.sh -c "${CONTAINER_NAME}" quota del unknown@domain.com
assert_failure assert_failure
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/dovecot-quotas.cf | grep -E '^quota_user@example.com\:12M\$' | wc -l | grep 1"
assert_success assert_success
run ./setup.sh -c "${TEST_NAME}" quota del quota_user@example.com run ./setup.sh -c "${CONTAINER_NAME}" quota del quota_user@example.com
assert_success assert_success
run grep "quota_user@example.com" "${TEST_TMP_CONFIG}/dovecot-quotas.cf" run grep "quota_user@example.com" "${TEST_TMP_CONFIG}/dovecot-quotas.cf"
assert_failure assert_failure
} }
@test "checking setup.sh: setup.sh config dkim help correctly displayed" { @test "config dkim (help correctly displayed)" {
run ./setup.sh -c "${TEST_NAME}" config dkim help run ./setup.sh -c "${CONTAINER_NAME}" config dkim help
assert_success assert_success
assert_line --index 3 --partial " open-dkim - configure DomainKeys Identified Mail (DKIM)" assert_line --index 3 --partial " open-dkim - configure DomainKeys Identified Mail (DKIM)"
} }
# debug # debug
@test "checking setup.sh: setup.sh debug fetchmail" { @test "debug fetchmail" {
run ./setup.sh -c "${TEST_NAME}" debug fetchmail run ./setup.sh -c "${CONTAINER_NAME}" debug fetchmail
assert_failure assert_failure
assert_output --partial "fetchmail: normal termination, status 11" assert_output --partial "fetchmail: normal termination, status 11"
} }
@test "checking setup.sh: setup.sh debug login ls" { @test "debug login ls" {
run ./setup.sh -c "${TEST_NAME}" debug login ls run ./setup.sh -c "${CONTAINER_NAME}" debug login ls
assert_success assert_success
} }
@test "checking setup.sh: setup.sh relay add-domain" { @test "relay add-domain" {
./setup.sh -c "${TEST_NAME}" relay add-domain example1.org smtp.relay1.com 2525 ./setup.sh -c "${CONTAINER_NAME}" relay add-domain example1.org smtp.relay1.com 2525
./setup.sh -c "${TEST_NAME}" relay add-domain example2.org smtp.relay2.com ./setup.sh -c "${CONTAINER_NAME}" relay add-domain example2.org smtp.relay2.com
./setup.sh -c "${TEST_NAME}" relay add-domain example3.org smtp.relay3.com 2525 ./setup.sh -c "${CONTAINER_NAME}" relay add-domain example3.org smtp.relay3.com 2525
./setup.sh -c "${TEST_NAME}" relay add-domain example3.org smtp.relay.com 587 ./setup.sh -c "${CONTAINER_NAME}" relay add-domain example3.org smtp.relay.com 587
# check adding # check adding
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example1.org\s\+\[smtp.relay1.com\]:2525' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example1.org\s\+\[smtp.relay1.com\]:2525' | wc -l | grep 1"
@ -269,10 +271,10 @@ function teardown_file() {
assert_success assert_success
} }
@test "checking setup.sh: setup.sh relay add-auth" { @test "relay add-auth" {
./setup.sh -c "${TEST_NAME}" relay add-auth example.org smtp_user smtp_pass ./setup.sh -c "${CONTAINER_NAME}" relay add-auth example.org smtp_user smtp_pass
./setup.sh -c "${TEST_NAME}" relay add-auth example2.org smtp_user2 smtp_pass2 ./setup.sh -c "${CONTAINER_NAME}" relay add-auth example2.org smtp_user2 smtp_pass2
./setup.sh -c "${TEST_NAME}" relay add-auth example2.org smtp_user2 smtp_pass_new ./setup.sh -c "${CONTAINER_NAME}" relay add-auth example2.org smtp_user2 smtp_pass_new
# test adding # test adding
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-sasl-password.cf | grep -e '^@example.org\s\+smtp_user:smtp_pass' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-sasl-password.cf | grep -e '^@example.org\s\+smtp_user:smtp_pass' | wc -l | grep 1"
@ -282,8 +284,8 @@ function teardown_file() {
assert_success assert_success
} }
@test "checking setup.sh: setup.sh relay exclude-domain" { @test "relay exclude-domain" {
./setup.sh -c "${TEST_NAME}" relay exclude-domain example.org ./setup.sh -c "${CONTAINER_NAME}" relay exclude-domain example.org
run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example.org\s*$' | wc -l | grep 1" run /bin/sh -c "cat ${TEST_TMP_CONFIG}/postfix-relaymap.cf | grep -e '^@example.org\s*$' | wc -l | grep 1"
assert_success assert_success

View file

@ -158,57 +158,6 @@ BATS_TEST_NAME_PREFIX='test helper functions:'
assert_failure assert_failure
} }
@test "wait_for_changes_to_be_detected_in_container fails when timeout is reached" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# wait for the initial checksum file to be created
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; test -e "${CHKSUM_FILE}"'
# there should be no changes in the beginning
TEST_TIMEOUT_IN_SECONDS=0 wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
# trigger some change
docker exec "${CONTAINER_NAME}" /bin/sh -c "addmailuser auser3@mail.my-domain.com mypassword"
# that should be picked up as not yet detected
! TEST_TIMEOUT_IN_SECONDS=0 wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
}
@test "wait_for_changes_to_be_detected_in_container succeeds within timeout" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# variable not local to make visible to teardown
CONTAINER_NAME=$(docker run -d --rm \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-h mail.my-domain.com \
-t "${NAME}")
teardown() { docker rm -f "${CONTAINER_NAME}"; }
# wait for the initial checksum file to be created
# shellcheck disable=SC2016
repeat_in_container_until_success_or_timeout 60 "${CONTAINER_NAME}" bash -c 'source /usr/local/bin/helpers/index.sh; test -e "${CHKSUM_FILE}"'
# trigger some change
docker exec "${CONTAINER_NAME}" /bin/sh -c "addmailuser auser3@mail.my-domain.com mypassword"
# that should eventually be detected
SECONDS=0
wait_for_changes_to_be_detected_in_container "${CONTAINER_NAME}"
[[ ${SECONDS} -gt 0 ]]
}
# TODO investigate why this test fails # TODO investigate why this test fails
@test "wait_for_empty_mail_queue_in_container fails when timeout reached" { @test "wait_for_empty_mail_queue_in_container fails when timeout reached" {
skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177' skip 'disabled as it fails randomly: https://github.com/docker-mailserver/docker-mailserver/pull/2177'

View file

@ -1,4 +1,5 @@
load "${REPOSITORY_ROOT}/test/helper/common" load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/change-detection"
load "${REPOSITORY_ROOT}/test/helper/setup" load "${REPOSITORY_ROOT}/test/helper/setup"
CONTAINER_NAME='mail' CONTAINER_NAME='mail'