test(easy-docker): add bats coverage for critical flows

This commit is contained in:
RocketQuack 2026-04-15 17:15:39 +02:00
parent f88cf1d8b8
commit 0571a84648
10 changed files with 1086 additions and 6 deletions

38
.github/workflows/easy-docker.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Easy Docker Tests
on:
push:
branches:
- main
paths:
- "easy-docker.sh"
- "scripts/easy-docker/**"
- "tests/easy-docker/**"
- ".github/workflows/easy-docker.yml"
pull_request:
branches:
- main
paths:
- "easy-docker.sh"
- "scripts/easy-docker/**"
- "tests/easy-docker/**"
- ".github/workflows/easy-docker.yml"
jobs:
bats:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Bats
run: |
BATS_VERSION="v1.11.1"
curl -fsSL "https://github.com/bats-core/bats-core/archive/refs/tags/${BATS_VERSION}.tar.gz" -o bats-core.tar.gz
tar -xzf bats-core.tar.gz
sudo "./bats-core-${BATS_VERSION#v}/install.sh" /usr/local
- name: Run easy-docker Bats tests
run: bats -p --recursive tests/easy-docker

View file

@ -12,13 +12,13 @@ USAGE
parse_cli_options() {
local result_var="${1}"
local disable_installation_fallback=0
local disable_installation_fallback_value=0
shift
while [ "$#" -gt 0 ]; do
case "$1" in
--no-installation-fallback)
disable_installation_fallback=1
disable_installation_fallback_value=1
;;
-h | --help)
print_usage
@ -33,6 +33,6 @@ parse_cli_options() {
shift
done
printf -v "${result_var}" "%s" "${disable_installation_fallback}"
printf -v "${result_var}" "%s" "${disable_installation_fallback_value}"
return 0
}

View file

@ -2,6 +2,12 @@
get_easy_docker_repo_root() {
local app_lib_dir=""
if [ -n "${EASY_DOCKER_REPO_ROOT_OVERRIDE:-}" ]; then
printf '%s\n' "${EASY_DOCKER_REPO_ROOT_OVERRIDE}"
return 0
fi
app_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# core.sh lives in scripts/easy-docker/lib/app/wizard/common
# so we need 6 levels up to reach repository root.
@ -55,14 +61,15 @@ create_stack_directory_with_metadata() {
return 2
fi
if [ -z "${frappe_branch}" ]; then
return 1
fi
if ! mkdir -p "${created_stack_dir}"; then
return 1
fi
created_at="$(get_current_utc_timestamp)"
if [ -z "${frappe_branch}" ]; then
return 1
fi
if ! cat >"${metadata_path}" <<EOF; then
{

View file

@ -0,0 +1,86 @@
#!/usr/bin/env bats
setup() {
ROOT_DIR="$(cd "${BATS_TEST_DIRNAME}/../.." && pwd)"
MAIN_SCRIPT="${ROOT_DIR}/scripts/easy-docker/main.sh"
SYSTEM_BASH="$(command -v bash)"
SYSTEM_CAT="$(command -v cat)"
SYSTEM_CHMOD="$(command -v chmod)"
SYSTEM_DIRNAME="$(command -v dirname)"
SYSTEM_ENV="$(command -v env)"
SYSTEM_MKDIR="$(command -v mkdir)"
SYSTEM_MKTEMP="$(command -v mktemp)"
SYSTEM_RM="$(command -v rm)"
TEST_TMPDIR="$("${SYSTEM_MKTEMP}" -d)"
STUB_BIN="${TEST_TMPDIR}/bin"
"${SYSTEM_MKDIR}" -p "${STUB_BIN}"
write_passthrough_stub cat "${SYSTEM_CAT}"
write_passthrough_stub dirname "${SYSTEM_DIRNAME}"
}
teardown() {
if [ -n "${TEST_TMPDIR:-}" ] && [ -d "${TEST_TMPDIR}" ]; then
"${SYSTEM_RM}" -rf "${TEST_TMPDIR}"
fi
}
write_stub() {
local name="$1"
shift
{
printf '#!%s\n' "${SYSTEM_BASH}"
printf '%s\n' "$@"
} >"${STUB_BIN}/${name}"
"${SYSTEM_CHMOD}" +x "${STUB_BIN}/${name}"
}
write_passthrough_stub() {
local name="$1"
local target="$2"
{
printf '#!%s\n' "${SYSTEM_BASH}"
printf 'exec "%s" "$@"\n' "${target}"
} >"${STUB_BIN}/${name}"
"${SYSTEM_CHMOD}" +x "${STUB_BIN}/${name}"
}
@test "help prints usage and exits cleanly" {
run "${SYSTEM_ENV}" "PATH=${STUB_BIN}" "${SYSTEM_BASH}" "${MAIN_SCRIPT}" --help
[ "${status}" -eq 0 ]
[[ "${output}" == *"Usage: bash easy-docker.sh [options]"* ]]
[[ "${output}" == *"--no-installation-fallback"* ]]
}
@test "unknown option is rejected before startup" {
run "${SYSTEM_ENV}" "PATH=${STUB_BIN}" "${SYSTEM_BASH}" "${MAIN_SCRIPT}" --definitely-unknown
[ "${status}" -eq 1 ]
[[ "${output}" == *"Unknown option: --definitely-unknown"* ]]
[[ "${output}" == *"Usage: bash easy-docker.sh [options]"* ]]
}
@test "missing gum fails without interactive fallback when disabled" {
run "${SYSTEM_ENV}" "PATH=${STUB_BIN}" "${SYSTEM_BASH}" "${MAIN_SCRIPT}" --no-installation-fallback
[ "${status}" -eq 1 ]
[[ "${output}" == *"gum is not installed. Trying package manager installation..."* ]]
[[ "${output}" == *"No supported package manager was found."* ]]
[[ "${output}" == *"Installation fallback is disabled."* ]]
[[ "${output}" == *"Install gum manually:"* ]]
}
@test "missing docker stops after gum dependency succeeds" {
write_stub gum 'exit 0'
run "${SYSTEM_ENV}" "PATH=${STUB_BIN}" "${SYSTEM_BASH}" "${MAIN_SCRIPT}" --no-installation-fallback
[ "${status}" -eq 1 ]
[[ "${output}" == *"docker is not installed."* ]]
[[ "${output}" == *"Install Docker first:"* ]]
}

View file

@ -0,0 +1,179 @@
#!/usr/bin/env bats
load 'test_helper.bash'
setup() {
easy_docker_test_begin
easy_docker_test_source_core_render_modules
unset ERPNEXT_VERSION
unset FRAPPE_BRANCH
}
teardown() {
easy_docker_test_end
}
@test "is_valid_stack_name accepts safe names" {
local name=""
for name in alpha alpha-1 alpha_1 alpha.1; do
run is_valid_stack_name "${name}"
[ "${status}" -eq 0 ]
done
}
@test "is_valid_stack_name rejects empty and unsafe names" {
local name=""
for name in "" "bad name" "bad/name" "bad:name" "bad*name"; do
run is_valid_stack_name "${name}"
[ "${status}" -eq 1 ]
done
}
@test "get_env_file_key_value parses exported and quoted values" {
local sandbox_root=""
local stack_dir=""
local env_file=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "env-parse")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "env-parse")"
mkdir -p "${stack_dir}"
env_file="${stack_dir}/stack.env"
cat >"${env_file}" <<'EOF'
# comment
export ERPNEXT_VERSION=15.9.0-test
STACK_NAME="My Stack"
IGNORED=value
EOF
run get_env_file_key_value "${env_file}" ERPNEXT_VERSION
[ "${status}" -eq 0 ]
[ "${output}" = "15.9.0-test" ]
run get_env_file_key_value "${env_file}" STACK_NAME
[ "${status}" -eq 0 ]
[ "${output}" = "My Stack" ]
}
@test "get_stack_compose_project_name normalizes metadata stack names" {
local sandbox_root=""
local stack_dir=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "project-name")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "project-name")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "My Stack! 01"
}
EOF
run get_stack_compose_project_name "${stack_dir}"
[ "${status}" -eq 0 ]
[ "${output}" = "easydocker-my-stack-01" ]
}
@test "get_metadata_compose_files_lines returns compose file entries" {
local sandbox_root=""
local stack_dir=""
local expected=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "compose-lines")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "compose-lines")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "compose-lines",
"compose_files": [
"compose.yaml",
"overrides/compose.proxy.yaml",
"overrides/compose.redis.yaml"
]
}
EOF
expected=$'compose.yaml\noverrides/compose.proxy.yaml\noverrides/compose.redis.yaml'
run get_metadata_compose_files_lines "${stack_dir}/metadata.json"
[ "${status}" -eq 0 ]
[ "${output}" = "${expected}" ]
}
@test "render_stack_compose_from_metadata writes generated compose with stubbed docker config" {
local sandbox_root=""
local stack_dir=""
local env_path=""
local generated_compose_path=""
local invocation_log=""
easy_docker_test_install_docker_stub
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-smoke")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-smoke")"
mkdir -p "${stack_dir}"
cat >"${sandbox_root}/compose.yaml" <<'EOF'
services:
backend:
image: frappe/backend
EOF
cat >"${sandbox_root}/overrides/compose.proxy.yaml" <<'EOF'
services:
frontend:
image: frappe/frontend
EOF
cat >"${sandbox_root}/overrides/compose.redis.yaml" <<'EOF'
services:
redis-cache:
image: redis:7
EOF
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "My Stack! 01",
"compose_files": [
"compose.yaml",
"overrides/compose.proxy.yaml",
"overrides/compose.redis.yaml"
]
}
EOF
env_path="${stack_dir}/My Stack! 01.env"
cat >"${env_path}" <<'EOF'
DB_HOST=localhost
EOF
generated_compose_path="${stack_dir}/compose.generated.yaml"
invocation_log="${EASY_DOCKER_TEST_TMPDIR}/docker.invocations"
export ERPNEXT_VERSION="15.9.0-test"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 0 ]
[ -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
run cat "${generated_compose_path}"
[ "${status}" -eq 0 ]
[[ "${output}" == *"invocation=docker compose --project-name easydocker-my-stack-01 --env-file ${env_path}"* ]]
[[ "${output}" == *"-f ${sandbox_root}/compose.yaml"* ]]
[[ "${output}" == *"-f ${sandbox_root}/overrides/compose.proxy.yaml"* ]]
[[ "${output}" == *"-f ${sandbox_root}/overrides/compose.redis.yaml"* ]]
[[ "${output}" == *"erpnext=15.9.0-test"* ]]
run cat "${invocation_log}"
[ "${status}" -eq 0 ]
[[ "${output}" == *"docker compose --project-name easydocker-my-stack-01 --env-file ${env_path} -f "* ]]
[[ "${output}" == *"config"* ]]
}

View file

@ -0,0 +1,120 @@
#!/usr/bin/env bats
load 'test_helper.bash'
setup() {
easy_docker_test_begin
easy_docker_test_source_gum_modules
}
teardown() {
easy_docker_test_end
}
@test "should_use_github_fallback rejects non-interactive terminals" {
local script_path=""
local repo_root=""
repo_root="$(easy_docker_test_repo_root)"
script_path="${EASY_DOCKER_TEST_TMPDIR}/run-should-use-github-fallback"
easy_docker_test_write_executable "${script_path}" \
"source \"${repo_root}/scripts/easy-docker/lib/install/gum/ensure.sh\"" \
'should_use_github_fallback'
run "${script_path}" </dev/null
[ "${status}" -eq 1 ]
[[ "${output}" == *"GitHub fallback prompt requires an interactive terminal."* ]]
}
@test "ensure_gum succeeds immediately when gum is already installed" {
# shellcheck disable=SC2317
command_exists() {
[ "${1}" = "gum" ]
}
run ensure_gum 0
[ "${status}" -eq 0 ]
[ -z "${output}" ]
}
@test "ensure_gum stops with manual guidance when fallback is disabled" {
# shellcheck disable=SC2317
command_exists() {
return 1
}
# shellcheck disable=SC2317
install_gum_with_package_manager() {
echo "Package manager installation did not succeed."
return 1
}
run ensure_gum 1
[ "${status}" -eq 1 ]
[[ "${output}" == *"gum is not installed. Trying package manager installation..."* ]]
[[ "${output}" == *"Package manager installation did not succeed."* ]]
[[ "${output}" == *"Installation fallback is disabled."* ]]
[[ "${output}" == *"Install gum manually:"* ]]
}
@test "ensure_gum succeeds when github fallback installs gum" {
gum_installed=0
# shellcheck disable=SC2317
command_exists() {
if [ "${1}" = "gum" ] && [ "${gum_installed}" -eq 1 ]; then
return 0
fi
return 1
}
# shellcheck disable=SC2317
install_gum_with_package_manager() {
echo "Package manager installation did not succeed."
return 1
}
# shellcheck disable=SC2317
should_use_github_fallback() {
return 0
}
# shellcheck disable=SC2317
install_gum_from_github_release() {
gum_installed=1
return 0
}
run ensure_gum 0
[ "${status}" -eq 0 ]
[[ "${output}" == *"Trying pinned GitHub release fallback..."* ]]
[[ "${output}" == *"gum was installed successfully."* ]]
}
@test "ensure_gum aborts when github fallback is declined" {
# shellcheck disable=SC2317
command_exists() {
return 1
}
# shellcheck disable=SC2317
install_gum_with_package_manager() {
echo "No supported package manager was found."
return 1
}
# shellcheck disable=SC2317
should_use_github_fallback() {
return 1
}
run ensure_gum 0
[ "${status}" -eq 1 ]
[[ "${output}" == *"GitHub fallback was not selected."* ]]
[[ "${output}" == *"Install gum manually:"* ]]
}

View file

@ -0,0 +1,147 @@
#!/usr/bin/env bats
load 'test_helper.bash'
setup() {
easy_docker_test_begin
easy_docker_test_source_docker_modules
}
teardown() {
easy_docker_test_end
}
@test "format_missing_commands_list joins newline separated commands" {
run format_missing_commands_list $'docker exec\ndocker compose pull\ndocker compose logs'
[ "${status}" -eq 0 ]
[ "${output}" = "docker exec,docker compose pull,docker compose logs" ]
}
@test "get_missing_docker_commands reports missing docker and compose subcommands" {
docker_supports_command() {
case "$*" in
"exec" | "compose pull")
return 1
;;
*)
return 0
;;
esac
}
run get_missing_docker_commands
[ "${status}" -eq 1 ]
[ "${output}" = $'docker exec\ndocker compose pull' ]
}
@test "ensure_docker fails when docker is not installed" {
# shellcheck disable=SC2317
command_exists() {
return 1
}
run ensure_docker
[ "${status}" -eq 1 ]
[[ "${output}" == *"docker is not installed."* ]]
[[ "${output}" == *"Install Docker first:"* ]]
}
@test "ensure_docker fails when docker compose is unavailable" {
# shellcheck disable=SC2317
command_exists() {
[ "${1}" = "docker" ]
}
# shellcheck disable=SC2317
docker_compose_available() {
return 1
}
run ensure_docker
[ "${status}" -eq 1 ]
[[ "${output}" == *"docker compose (Compose v2 command) is not available."* ]]
[[ "${output}" == *"This script requires Docker Compose v2 via the 'docker compose' command."* ]]
}
@test "ensure_docker fails when the docker daemon is not running" {
# shellcheck disable=SC2317
command_exists() {
[ "${1}" = "docker" ]
}
# shellcheck disable=SC2317
docker_compose_available() {
return 0
}
# shellcheck disable=SC2317
docker_daemon_running() {
return 1
}
run ensure_docker
[ "${status}" -eq 1 ]
[[ "${output}" == *"docker daemon is not running."* ]]
[[ "${output}" == *"Start the Docker daemon/service and retry."* ]]
}
@test "ensure_docker reports missing required docker commands" {
# shellcheck disable=SC2317
command_exists() {
[ "${1}" = "docker" ]
}
# shellcheck disable=SC2317
docker_compose_available() {
return 0
}
# shellcheck disable=SC2317
docker_daemon_running() {
return 0
}
# shellcheck disable=SC2317
get_missing_docker_commands() {
printf '%s\n' "docker exec" "docker compose pull"
return 1
}
run ensure_docker
[ "${status}" -eq 1 ]
[[ "${output}" == *"Missing required docker commands: docker exec,docker compose pull"* ]]
[[ "${output}" == *"Standard 'docker' and 'docker compose' commands are required."* ]]
}
@test "ensure_docker succeeds when all prerequisites are satisfied" {
# shellcheck disable=SC2317
command_exists() {
[ "${1}" = "docker" ]
}
# shellcheck disable=SC2317
docker_compose_available() {
return 0
}
# shellcheck disable=SC2317
docker_daemon_running() {
return 0
}
# shellcheck disable=SC2317
get_missing_docker_commands() {
return 0
}
run ensure_docker
[ "${status}" -eq 0 ]
[ -z "${output}" ]
}

View file

@ -0,0 +1,149 @@
#!/usr/bin/env bats
load 'test_helper.bash'
setup() {
easy_docker_test_begin
easy_docker_test_source_core_render_modules
}
teardown() {
easy_docker_test_end
}
@test "create_stack_directory_with_metadata writes metadata and returns the stack directory" {
local sandbox_root=""
local stack_dir=""
local result_stack_dir=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "create-metadata")"
easy_docker_test_override_repo_root "${sandbox_root}"
if ! create_stack_directory_with_metadata result_stack_dir "demo-stack" "production" "version-15"; then
false
fi
stack_dir="$(easy_docker_test_stack_dir "demo-stack")"
[ "${result_stack_dir}" = "${stack_dir}" ]
[ -d "${result_stack_dir}" ]
[ "$(get_metadata_string_field "${result_stack_dir}/metadata.json" "stack_name")" = "demo-stack" ]
[ "$(get_metadata_string_field "${result_stack_dir}/metadata.json" "setup_type")" = "production" ]
[ "$(get_metadata_string_field "${result_stack_dir}/metadata.json" "frappe_branch")" = "version-15" ]
[ -n "$(get_metadata_string_field "${result_stack_dir}/metadata.json" "created_at")" ]
}
@test "create_stack_directory_with_metadata rejects duplicate stack directories" {
local sandbox_root=""
local result_stack_dir=""
local metadata_path=""
local status_code=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "duplicate-stack")"
easy_docker_test_override_repo_root "${sandbox_root}"
if ! create_stack_directory_with_metadata result_stack_dir "duplicate-stack" "production" "version-15"; then
false
fi
metadata_path="${result_stack_dir}/metadata.json"
printf '%s\n' "original" >"${metadata_path}"
result_stack_dir=""
if create_stack_directory_with_metadata result_stack_dir "duplicate-stack" "production" "version-15"; then
status_code=0
else
status_code=$?
fi
[ "${status_code}" -eq 2 ]
[ "${result_stack_dir}" = "" ]
[ "$(cat "${metadata_path}")" = "original" ]
}
@test "create_stack_directory_with_metadata does not leave a partial stack behind when frappe_branch is missing" {
local sandbox_root=""
local stack_dir=""
local result_stack_dir=""
local status_code=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "missing-frappe-branch")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "missing-frappe-branch")"
if create_stack_directory_with_metadata result_stack_dir "missing-frappe-branch" "production" ""; then
status_code=0
else
status_code=$?
fi
[ "${status_code}" -eq 1 ]
[ "${result_stack_dir}" = "" ]
[ ! -d "${stack_dir}" ]
}
@test "rollback_stack_directory removes managed stack directories" {
local sandbox_root=""
local stack_dir=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "rollback-stack")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "rollback-stack")"
mkdir -p "${stack_dir}/nested"
printf '%s\n' "payload" >"${stack_dir}/nested/file.txt"
if ! rollback_stack_directory "${stack_dir}"; then
false
fi
[ ! -d "${stack_dir}" ]
}
@test "rollback_stack_directory rejects paths outside the managed stacks tree" {
local sandbox_root=""
local outside_dir=""
local status_code=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "rollback-outside")"
easy_docker_test_override_repo_root "${sandbox_root}"
outside_dir="$(mktemp -d)"
if rollback_stack_directory "${outside_dir}"; then
status_code=0
else
status_code=$?
fi
[ "${status_code}" -eq 2 ]
[ -d "${outside_dir}" ]
rm -rf "${outside_dir}"
}
@test "get_stack_dir_by_name returns the matching stack directory and ignores junk entries" {
local sandbox_root=""
local stacks_dir=""
local matching_stack_dir=""
local junk_dir=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "stack-lookup")"
easy_docker_test_override_repo_root "${sandbox_root}"
stacks_dir="${sandbox_root}/.easy-docker/stacks"
junk_dir="${stacks_dir}/not-a-stack"
matching_stack_dir="${stacks_dir}/target-stack"
mkdir -p "${junk_dir}" "${matching_stack_dir}"
printf '%s\n' '{ "stack_name": "target-stack" }' >"${matching_stack_dir}/metadata.json"
run get_stack_dir_by_name "target-stack"
[ "${status}" -eq 0 ]
[ "${output}" = "${matching_stack_dir}" ]
}
@test "get_stack_dir_by_name fails when the stack is absent" {
local sandbox_root=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "stack-missing")"
easy_docker_test_override_repo_root "${sandbox_root}"
run get_stack_dir_by_name "missing-stack"
[ "${status}" -eq 1 ]
[ -z "${output}" ]
}

View file

@ -0,0 +1,207 @@
#!/usr/bin/env bats
load 'test_helper.bash'
setup() {
easy_docker_test_begin
easy_docker_test_source_core_render_modules
}
teardown() {
easy_docker_test_end
}
write_docker_stub() {
local body="${1}"
easy_docker_test_write_bin_command docker \
'set -euo pipefail' \
"${body}"
easy_docker_test_prepend_bin_dir
}
@test "render_stack_compose_from_metadata fails when metadata.json is missing" {
local sandbox_root=""
local stack_dir=""
local generated_compose_path=""
local docker_log=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-missing-metadata")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-missing-metadata")"
mkdir -p "${stack_dir}"
# shellcheck disable=SC2016
write_docker_stub 'echo "docker should not have been called" >&2; exit 99'
generated_compose_path="${stack_dir}/compose.generated.yaml"
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 1 ]
[ ! -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
[ ! -f "${docker_log}" ]
}
@test "render_stack_compose_from_metadata fails when the env file is missing" {
local sandbox_root=""
local stack_dir=""
local generated_compose_path=""
local docker_log=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-missing-env")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-missing-env")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "render-missing-env",
"compose_files": [
"compose.yaml"
]
}
EOF
# shellcheck disable=SC2016
write_docker_stub 'echo "docker should not have been called" >&2; exit 99'
generated_compose_path="${stack_dir}/compose.generated.yaml"
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 1 ]
[ ! -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
[ ! -f "${docker_log}" ]
}
@test "render_stack_compose_from_metadata fails when compose_files are missing" {
local sandbox_root=""
local stack_dir=""
local generated_compose_path=""
local docker_log=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-missing-compose-files")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-missing-compose-files")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "render-missing-compose-files"
}
EOF
cat >"${stack_dir}/render-missing-compose-files.env" <<'EOF'
ERPNEXT_VERSION=15.9.0-test
EOF
# shellcheck disable=SC2016
write_docker_stub 'echo "docker should not have been called" >&2; exit 99'
generated_compose_path="${stack_dir}/compose.generated.yaml"
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 1 ]
[ ! -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
[ ! -f "${docker_log}" ]
}
@test "render_stack_compose_from_metadata fails when a referenced compose file is missing" {
local sandbox_root=""
local stack_dir=""
local generated_compose_path=""
local docker_log=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-missing-source")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-missing-source")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "render-missing-source",
"compose_files": [
"compose.yaml",
"overrides/compose.proxy.yaml"
]
}
EOF
cat >"${stack_dir}/render-missing-source.env" <<'EOF'
ERPNEXT_VERSION=15.9.0-test
EOF
cat >"${sandbox_root}/compose.yaml" <<'EOF'
services:
backend:
image: frappe/backend
EOF
# shellcheck disable=SC2016
write_docker_stub 'echo "docker should not have been called" >&2; exit 99'
generated_compose_path="${stack_dir}/compose.generated.yaml"
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 1 ]
[ ! -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
[ ! -f "${docker_log}" ]
}
@test "render_stack_compose_from_metadata removes its temporary file after a docker config failure" {
local sandbox_root=""
local stack_dir=""
local generated_compose_path=""
local docker_log=""
sandbox_root="$(easy_docker_test_create_repo_sandbox "render-docker-failure")"
easy_docker_test_override_repo_root "${sandbox_root}"
stack_dir="$(easy_docker_test_stack_dir "render-docker-failure")"
mkdir -p "${stack_dir}"
cat >"${stack_dir}/metadata.json" <<'EOF'
{
"stack_name": "render-docker-failure",
"compose_files": [
"compose.yaml",
"overrides/compose.proxy.yaml"
]
}
EOF
cat >"${stack_dir}/render-docker-failure.env" <<'EOF'
ERPNEXT_VERSION=15.9.0-test
EOF
cat >"${sandbox_root}/compose.yaml" <<'EOF'
services:
backend:
image: frappe/backend
EOF
mkdir -p "${sandbox_root}/overrides"
cat >"${sandbox_root}/overrides/compose.proxy.yaml" <<'EOF'
services:
frontend:
image: frappe/frontend
EOF
# shellcheck disable=SC2016
write_docker_stub 'printf "%s\n" "docker $*" >>"${EASY_DOCKER_TEST_TMPDIR}/docker.log"; if [ "${*: -1}" = "config" ]; then echo "simulated docker compose config failure" >&2; exit 23; fi; exit 0'
generated_compose_path="${stack_dir}/compose.generated.yaml"
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
run render_stack_compose_from_metadata "${stack_dir}"
[ "${status}" -eq 1 ]
[ ! -f "${generated_compose_path}" ]
[ ! -f "${generated_compose_path}.tmp" ]
[ -f "${docker_log}" ]
[ "$(cat "${docker_log}")" != "" ]
}

View file

@ -0,0 +1,147 @@
#!/usr/bin/env bash
easy_docker_test_repo_root() {
local helper_dir=""
helper_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
(cd "${helper_dir}/../.." && pwd)
}
easy_docker_test_begin() {
EASY_DOCKER_TEST_TMPDIR="$(mktemp -d)"
export EASY_DOCKER_TEST_TMPDIR
unset EASY_DOCKER_REPO_ROOT_OVERRIDE
}
easy_docker_test_end() {
if [ -n "${EASY_DOCKER_TEST_TMPDIR:-}" ] && [ -d "${EASY_DOCKER_TEST_TMPDIR}" ]; then
rm -rf "${EASY_DOCKER_TEST_TMPDIR}"
fi
}
easy_docker_test_bin_dir() {
printf '%s/bin\n' "${EASY_DOCKER_TEST_TMPDIR}"
}
easy_docker_test_write_executable() {
local target_path="${1}"
local system_bash=""
shift
system_bash="$(command -v bash)"
mkdir -p "$(dirname "${target_path}")"
{
printf '#!%s\n' "${system_bash}"
printf '%s\n' "$@"
} >"${target_path}"
chmod +x "${target_path}"
}
easy_docker_test_write_bin_command() {
local command_name="${1}"
local target_path=""
shift
target_path="$(easy_docker_test_bin_dir)/${command_name}"
easy_docker_test_write_executable "${target_path}" "$@"
}
easy_docker_test_prepend_bin_dir() {
PATH="$(easy_docker_test_bin_dir):${PATH}"
export PATH
}
easy_docker_test_source_common_modules() {
local repo_root=""
repo_root="$(easy_docker_test_repo_root)"
# shellcheck source=scripts/easy-docker/lib/core/commands.sh
source "${repo_root}/scripts/easy-docker/lib/core/commands.sh"
# shellcheck source=scripts/easy-docker/lib/core/messages.sh
source "${repo_root}/scripts/easy-docker/lib/core/messages.sh"
}
easy_docker_test_source_core_render_modules() {
local repo_root=""
repo_root="$(easy_docker_test_repo_root)"
easy_docker_test_source_common_modules
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/core.sh
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/core.sh"
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose/render.sh
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/compose/render.sh"
}
easy_docker_test_source_docker_modules() {
local repo_root=""
repo_root="$(easy_docker_test_repo_root)"
easy_docker_test_source_common_modules
# shellcheck source=scripts/easy-docker/lib/checks/docker.sh
source "${repo_root}/scripts/easy-docker/lib/checks/docker.sh"
}
easy_docker_test_source_gum_modules() {
local repo_root=""
repo_root="$(easy_docker_test_repo_root)"
easy_docker_test_source_common_modules
# shellcheck source=scripts/easy-docker/lib/install/gum/package_manager.sh
source "${repo_root}/scripts/easy-docker/lib/install/gum/package_manager.sh"
# shellcheck source=scripts/easy-docker/lib/install/gum/github_release.sh
source "${repo_root}/scripts/easy-docker/lib/install/gum/github_release.sh"
# shellcheck source=scripts/easy-docker/lib/install/gum/ensure.sh
source "${repo_root}/scripts/easy-docker/lib/install/gum/ensure.sh"
}
easy_docker_test_create_repo_sandbox() {
local sandbox_name="${1}"
local sandbox_root=""
sandbox_root="${EASY_DOCKER_TEST_TMPDIR}/repo-${sandbox_name}"
mkdir -p "${sandbox_root}/.easy-docker/stacks" "${sandbox_root}/overrides"
printf '%s\n' "${sandbox_root}"
}
easy_docker_test_override_repo_root() {
EASY_DOCKER_REPO_ROOT_OVERRIDE="${1}"
export EASY_DOCKER_REPO_ROOT_OVERRIDE
}
easy_docker_test_stack_dir() {
local stack_name="${1}"
printf '%s/.easy-docker/stacks/%s\n' "${EASY_DOCKER_REPO_ROOT_OVERRIDE}" "${stack_name}"
}
easy_docker_test_install_docker_stub() {
local log_file=""
log_file="${EASY_DOCKER_TEST_TMPDIR}/docker.invocations"
# shellcheck disable=SC2016
easy_docker_test_write_bin_command docker \
'set -euo pipefail' \
"log_file=\"${log_file}\"" \
'printf '"'"'%s\n'"'"' "docker $*" >>"${log_file}"' \
'if [ "${1:-}" != "compose" ]; then' \
' echo "unexpected docker subcommand: ${1:-}" >&2' \
' exit 64' \
'fi' \
'if [ "${!#}" != "config" ]; then' \
' echo "expected docker compose config invocation" >&2' \
' exit 65' \
'fi' \
'printf '"'"'invocation=%s\n'"'"' "docker $*"' \
'printf '"'"'erpnext=%s\n'"'"' "${ERPNEXT_VERSION:-}"'
easy_docker_test_prepend_bin_dir
}