Files
wireguard-scripts/tests/run.sh
T

520 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
TESTS_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly TESTS_DIR
# shellcheck source=helpers.sh
source "${TESTS_DIR}/helpers.sh"
# shellcheck source=../wg-manager.sh
source "${PROJECT_DIR}/wg-manager.sh"
case $# in
0) ;;
1) [[ $1 == config ]] || { printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64; } ;;
*) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;;
esac
test_group=${1-}
setup_test_environment
write_valid_config() {
local output_file=$1 override_key=${2-} override_value=${3-}
local wg_interface=wg0 wg_conf="${TEST_ROOT}/wg0.conf" client_dir="${TEST_ROOT}/clients"
local server_endpoint=vpn.example.invalid server_port=51820 server_vpn_ip=192.0.2.1
local client_dns='192.0.2.53,2001:db8::53' vpn_subnet_prefix=192.0.2 start_host=2 end_host=254
local use_ipv6=0 ipv6_prefix=2001:db8:ffff:: default_client_mode=full-tunnel
local recent_handshake_seconds=180 backup_retention=10
case $override_key in
'') ;;
WG_INTERFACE) wg_interface=$override_value ;;
WG_CONF) wg_conf=$override_value ;;
CLIENT_DIR) client_dir=$override_value ;;
SERVER_ENDPOINT) server_endpoint=$override_value ;;
SERVER_PORT) server_port=$override_value ;;
SERVER_VPN_IP) server_vpn_ip=$override_value ;;
CLIENT_DNS) client_dns=$override_value ;;
VPN_SUBNET_PREFIX) vpn_subnet_prefix=$override_value ;;
START_HOST) start_host=$override_value ;;
END_HOST) end_host=$override_value ;;
USE_IPV6) use_ipv6=$override_value ;;
IPV6_PREFIX) ipv6_prefix=$override_value ;;
DEFAULT_CLIENT_MODE) default_client_mode=$override_value ;;
RECENT_HANDSHAKE_SECONDS) recent_handshake_seconds=$override_value ;;
BACKUP_RETENTION) backup_retention=$override_value ;;
RANGE_REVERSED) start_host=254; end_host=2 ;;
*) printf 'unknown test config key: %s\n' "$override_key" >&2; return 64 ;;
esac
cat >"$output_file" <<EOF
# Safe test configuration
WG_INTERFACE="${wg_interface}"
WG_CONF="${wg_conf}"
CLIENT_DIR="${client_dir}"
SERVER_ENDPOINT="${server_endpoint}"
SERVER_PORT="${server_port}"
SERVER_VPN_IP="${server_vpn_ip}"
CLIENT_DNS="${client_dns}"
VPN_SUBNET_PREFIX="${vpn_subnet_prefix}"
START_HOST="${start_host}"
END_HOST="${end_host}"
USE_IPV6="${use_ipv6}"
IPV6_PREFIX="${ipv6_prefix}"
DEFAULT_CLIENT_MODE="${default_client_mode}"
RECENT_HANDSHAKE_SECONDS="${recent_handshake_seconds}"
BACKUP_RETENTION="${backup_retention}"
EOF
}
remove_config_key() {
local input_file=$1 key=$2 line temp_file
temp_file="${input_file}.tmp"
while IFS= read -r line || [[ -n $line ]]; do
[[ $line == "${key}="* ]] && continue
printf '%s\n' "$line"
done <"$input_file" >"$temp_file"
mv -- "$temp_file" "$input_file"
}
replace_config_line() {
local input_file=$1 prefix=$2 replacement=$3 line temp_file
temp_file="${input_file}.tmp"
while IFS= read -r line || [[ -n $line ]]; do
if [[ $line == "${prefix}"* ]]; then printf '%s\n' "$replacement"; else printf '%s\n' "$line"; fi
done <"$input_file" >"$temp_file"
mv -- "$temp_file" "$input_file"
}
run_config_tests() {
local valid_config="${TEST_ROOT}/valid.conf" status output marker
write_valid_config "$valid_config"
export WG_MANAGER_CONFIG="$valid_config"
export WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups"
export WG_MANAGER_LOG_DIR="${TEST_ROOT}/logs"
export WG_MANAGER_METADATA_DIR="${TEST_ROOT}/clients"
export WG_MANAGER_ARCHIVE_DIR="${TEST_ROOT}/archive"
export WG_MANAGER_LOCK_FILE="${TEST_ROOT}/manager.lock"
begin_test 'valid restricted configuration loads with isolated paths'
load_config
assert_equal wg0 "$WG_INTERFACE" 'имя интерфейса'
assert_equal "$TEST_ROOT/wg0.conf" "$WG_CONF" 'путь WG_CONF'
assert_equal "$TEST_ROOT/clients" "$CLIENT_DIR" 'путь клиентов'
assert_equal "$TEST_ROOT/backups" "$BACKUP_DIR" 'путь резервных копий'
assert_equal "$TEST_ROOT/logs" "$LOG_DIR" 'путь журнала'
assert_equal "$TEST_ROOT/clients" "$METADATA_DIR" 'путь метаданных'
assert_equal "$TEST_ROOT/archive" "$ARCHIVE_DIR" 'путь архива'
assert_equal "$TEST_ROOT/manager.lock" "$LOCK_FILE" 'путь lock-файла'
begin_test 'required keys and parser quoting branches are covered'
write_valid_config "${TEST_ROOT}/missing.conf"
remove_config_key "${TEST_ROOT}/missing.conf" WG_INTERFACE
WG_MANAGER_CONFIG="${TEST_ROOT}/missing.conf"
set +e
output=$(load_config 2>&1)
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'отсутствующий параметр'
assert_contains 'Отсутствует параметр' "$output" 'диагностика отсутствующего параметра'
write_valid_config "${TEST_ROOT}/quotes.conf" SERVER_ENDPOINT 'vpn.example.invalid"'
WG_MANAGER_CONFIG="${TEST_ROOT}/quotes.conf"
set +e
output=$(load_config 2>&1)
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'встроенные кавычки'
assert_contains 'Некорректные кавычки' "$output" 'диагностика кавычек'
write_valid_config "${TEST_ROOT}/ipv6-on.conf" USE_IPV6 1
WG_MANAGER_CONFIG="${TEST_ROOT}/ipv6-on.conf"
load_config
assert_equal 1 "$USE_IPV6" 'включённый IPv6'
write_valid_config "${TEST_ROOT}/whitespace.conf"
replace_config_line "${TEST_ROOT}/whitespace.conf" 'SERVER_PORT=' ' SERVER_PORT = 51820 '
printf '\n# Full-line comment\n' >>"${TEST_ROOT}/whitespace.conf"
WG_MANAGER_CONFIG="${TEST_ROOT}/whitespace.conf"
load_config
assert_equal 51820 "$SERVER_PORT" 'пробелы и комментарии'
write_valid_config "${TEST_ROOT}/unquoted.conf"
replace_config_line "${TEST_ROOT}/unquoted.conf" 'SERVER_PORT=' 'SERVER_PORT=51820'
WG_MANAGER_CONFIG="${TEST_ROOT}/unquoted.conf"
load_config
assert_equal 51820 "$SERVER_PORT" 'неквотированное значение'
begin_test 'runtime path defaults derive from SCRIPT_DIR'
unset WG_MANAGER_CONFIG WG_MANAGER_BACKUP_DIR WG_MANAGER_LOG_DIR WG_MANAGER_METADATA_DIR WG_MANAGER_ARCHIVE_DIR WG_MANAGER_LOCK_FILE
set_runtime_paths
assert_equal "${SCRIPT_DIR}/wg-manager.conf" "$CONFIG_PATH" 'конфигурация по умолчанию'
assert_equal "${SCRIPT_DIR}/backups" "$BACKUP_DIR" 'резервные копии по умолчанию'
assert_equal "${SCRIPT_DIR}/logs" "$LOG_DIR" 'журнал по умолчанию'
assert_equal "${SCRIPT_DIR}/state/clients" "$METADATA_DIR" 'метаданные по умолчанию'
assert_equal "${SCRIPT_DIR}/state/archive" "$ARCHIVE_DIR" 'архив по умолчанию'
assert_equal "${SCRIPT_DIR}/state/wg-manager.lock" "$LOCK_FILE" 'lock по умолчанию'
export WG_MANAGER_CONFIG="$valid_config"
export WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups"
export WG_MANAGER_LOG_DIR="${TEST_ROOT}/logs"
export WG_MANAGER_METADATA_DIR="${TEST_ROOT}/clients"
export WG_MANAGER_ARCHIVE_DIR="${TEST_ROOT}/archive"
export WG_MANAGER_LOCK_FILE="${TEST_ROOT}/manager.lock"
begin_test 'known command validates config then reports not implemented'
set +e
output=$("${PROJECT_DIR}/wg-manager.sh" status 2>&1)
status=$?
set -e
assert_equal "$EXIT_OPERATION" "$status" 'статус команды-заглушки'
assert_contains 'пока не реализована' "$output" 'сообщение заглушки'
begin_test 'invalid configurations are rejected without execution'
marker="${TEST_ROOT}/marker"
local invalid_file
write_valid_config "${TEST_ROOT}/duplicate.conf"
for invalid_file in unknown malformed; do write_valid_config "${TEST_ROOT}/${invalid_file}.conf"; done
write_valid_config "${TEST_ROOT}/substitution.conf" SERVER_ENDPOINT "\$(touch ${marker})"
write_valid_config "${TEST_ROOT}/backtick.conf" SERVER_ENDPOINT "\`touch ${marker}\`"
write_valid_config "${TEST_ROOT}/semicolon.conf" SERVER_ENDPOINT "vpn.example.invalid;touch ${marker}"
write_valid_config "${TEST_ROOT}/wildcard.conf" SERVER_ENDPOINT 'vpn*.example.invalid'
write_valid_config "${TEST_ROOT}/escape.conf" SERVER_ENDPOINT 'vpn\.example.invalid'
write_valid_config "${TEST_ROOT}/bad_ipv4.conf" SERVER_VPN_IP '192.0.999.1'
write_valid_config "${TEST_ROOT}/bad_port.conf" SERVER_PORT 0
write_valid_config "${TEST_ROOT}/bad_mode.conf" DEFAULT_CLIENT_MODE unsafe
write_valid_config "${TEST_ROOT}/bad_range.conf" RANGE_REVERSED 1
write_valid_config "${TEST_ROOT}/bad_path.conf" WG_CONF relative.conf
printf 'WG_INTERFACE="wg1"\n' >>"${TEST_ROOT}/duplicate.conf"
printf 'NOT_ALLOWED="x"\n' >>"${TEST_ROOT}/unknown.conf"
printf 'BROKEN LINE\n' >>"${TEST_ROOT}/malformed.conf"
for invalid_file in duplicate unknown malformed substitution backtick semicolon wildcard escape bad_ipv4 bad_port bad_mode bad_range bad_path; do
export WG_MANAGER_CONFIG="${TEST_ROOT}/${invalid_file}.conf"
set +e
output=$(load_config 2>&1)
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" "отклонение ${invalid_file}"
case $invalid_file in
duplicate) assert_contains 'Дублирующийся параметр' "$output" 'ветка duplicate' ;;
unknown) assert_contains 'Неизвестный параметр' "$output" 'ветка unknown' ;;
malformed) assert_contains 'Некорректное присваивание' "$output" 'ветка malformed' ;;
substitution|backtick|semicolon|wildcard|escape) assert_contains 'Недопустимый синтаксис' "$output" "ветка ${invalid_file}" ;;
bad_ipv4) assert_contains 'Некорректный IPv4' "$output" 'ветка IPv4' ;;
bad_port) assert_contains 'Некорректный порт' "$output" 'ветка port' ;;
bad_mode) assert_contains 'Некорректный режим' "$output" 'ветка mode' ;;
bad_range) assert_contains 'Некорректный диапазон' "$output" 'ветка range' ;;
bad_path) assert_contains 'Некорректный путь' "$output" 'ветка path' ;;
esac
done
assert_file_absent "$marker" 'конфигурация не выполняет текст'
begin_test 'semantic validation rejects one invalid value at a time'
local semantic_case semantic_output
begin_test 'IPv6 validator rejects malformed compression and accepts valid forms'
for invalid_value in ::: 2001:db8:::1; do
set +e
validate_ipv6 "$invalid_value"
status=$?
set -e
assert_failure "$status" "отклонён IPv6 ${invalid_value}"
done
for valid_value in :: 2001:db8::1 2001:db8:0:1:2:3:4:5; do
set +e
validate_ipv6 "$valid_value"
status=$?
set -e
assert_success "$status" "принят IPv6 ${valid_value}"
done
declare -a semantic_cases=(
'server_inside|SERVER_VPN_IP|192.0.2.100|Диапазон клиентов содержит'
'empty_dns|CLIENT_DNS||Некорректный список DNS'
'trailing_dns|CLIENT_DNS|192.0.2.53,|Некорректный список DNS'
'dotted_endpoint|SERVER_ENDPOINT|999.999.999.999|Некорректная конечная точка'
'bracketed_hostname|SERVER_ENDPOINT|[vpn.example.invalid]|Некорректная конечная точка'
'invalid_interface|WG_INTERFACE|bad/name|Некорректное имя интерфейса'
'invalid_client_path|CLIENT_DIR|relative|Некорректный путь'
'invalid_dns|CLIENT_DNS|192.0.2.53,,2001:db8::53|Некорректный список DNS'
'invalid_prefix|VPN_SUBNET_PREFIX|192.0|Некорректный префикс VPN'
'mismatched_prefix|VPN_SUBNET_PREFIX|192.0.3|Префикс VPN не совпадает'
'nonnumeric_port|SERVER_PORT|abc|Некорректный порт'
'nonnumeric_range|START_HOST|abc|Некорректный диапазон'
'out_of_range|START_HOST|0|Некорректный диапазон'
'invalid_ipv6|IPV6_PREFIX|gg::1|Некорректный IPv6'
'triple_ipv6_prefix|IPV6_PREFIX|:::|Некорректный IPv6'
'triple_ipv6_dns|CLIENT_DNS|192.0.2.53,:::|Некорректный адрес DNS'
'triple_ipv6_endpoint|SERVER_ENDPOINT|:::|Некорректная конечная точка'
'invalid_ipv6_mode|USE_IPV6|2|USE_IPV6 должен'
'invalid_retention|BACKUP_RETENTION|0|Некорректное число резервных копий'
'invalid_handshake|RECENT_HANDSHAKE_SECONDS|604801|Некорректный срок handshake'
)
for semantic_case in "${semantic_cases[@]}"; do
IFS='|' read -r invalid_file invalid_key invalid_value expected_text <<<"$semantic_case"
write_valid_config "${TEST_ROOT}/${invalid_file}.conf" "$invalid_key" "$invalid_value"
WG_MANAGER_CONFIG="${TEST_ROOT}/${invalid_file}.conf"
set +e
semantic_output=$(load_config 2>&1)
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" "отклонение ${invalid_file}"
assert_contains "$expected_text" "$semantic_output" "диагностика ${invalid_file}"
done
begin_test 'valid endpoint forms and split tunnel are accepted'
local valid_case valid_output
declare -a valid_cases=(
'documentation_ipv4|SERVER_ENDPOINT|192.0.2.10'
'plain_ipv6|SERVER_ENDPOINT|2001:db8::10'
'bracketed_ipv6|SERVER_ENDPOINT|[2001:db8::10]'
'split_tunnel|DEFAULT_CLIENT_MODE|split-tunnel'
)
for valid_case in "${valid_cases[@]}"; do
IFS='|' read -r invalid_file invalid_key invalid_value <<<"$valid_case"
write_valid_config "${TEST_ROOT}/${invalid_file}.conf" "$invalid_key" "$invalid_value"
WG_MANAGER_CONFIG="${TEST_ROOT}/${invalid_file}.conf"
set +e
valid_output=$(load_config 2>&1)
status=$?
set -e
assert_success "$status" "принимается ${invalid_file}"
assert_equal '' "$valid_output" "нет ошибки ${invalid_file}"
done
begin_test 'runtime path overrides reject unsafe values'
WG_MANAGER_CONFIG='relative.conf'
set +e
set_runtime_paths >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'относительный override отклонён'
WG_MANAGER_CONFIG=''
set +e
set_runtime_paths >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'пустой override отклонён'
WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/../escape"
set +e
set_runtime_paths >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'traversal override отклонён'
WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/[unsafe]"
set +e
set_runtime_paths >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'скобки в override отклонены'
write_valid_config "${TEST_ROOT}/bracket-path.conf" WG_CONF "${TEST_ROOT}/[unsafe]"
export WG_MANAGER_BACKUP_DIR="${TEST_ROOT}/backups"
WG_MANAGER_CONFIG="${TEST_ROOT}/bracket-path.conf"
set +e
output=$(load_config 2>&1)
status=$?
set -e
assert_equal "$EXIT_VALIDATION" "$status" 'скобки в пути конфигурации отклонены'
assert_contains 'Некорректный путь' "$output" 'диагностика скобок в пути'
export WG_MANAGER_CONFIG="$valid_config"
}
run_gum_tests() {
local copied_script="${TEST_ROOT}/manager-copy.sh" output status
cp -- "${PROJECT_DIR}/wg-manager.sh" "$copied_script"
chmod 700 "$copied_script"
mkdir -p "${TEST_ROOT}/bin" "${TEST_ROOT}/empty-path"
printf '#!/usr/bin/env bash\nexit 0\n' >"${TEST_ROOT}/override-gum"
chmod 700 "${TEST_ROOT}/override-gum"
printf '#!/usr/bin/env bash\nexit 0\n' >"${TEST_ROOT}/bin/gum"
chmod 700 "${TEST_ROOT}/bin/gum"
begin_test 'gum environment override has priority'
output=$(WG_MANAGER_GUM="${TEST_ROOT}/override-gum" PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script")
assert_equal "${TEST_ROOT}/override-gum" "$output" 'WG_MANAGER_GUM'
begin_test 'local gum binary precedes PATH'
output=$(env -u WG_MANAGER_GUM PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script")
assert_equal "${TEST_ROOT}/bin/gum" "$output" 'локальный gum'
begin_test 'PATH gum is used when local binary is absent'
rm -- "${TEST_ROOT}/bin/gum"
printf '#!/usr/bin/env bash\nexit 0\n' >"${TEST_ROOT}/empty-path/gum"
chmod 700 "${TEST_ROOT}/empty-path/gum"
output=$(env -u WG_MANAGER_GUM PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script")
assert_equal "${TEST_ROOT}/empty-path/gum" "$output" 'gum из PATH'
rm -- "${TEST_ROOT}/empty-path/gum"
begin_test 'gum completely absent reports dependency failure'
set +e
output=$(env -u WG_MANAGER_GUM PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script" 2>&1)
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'gum отсутствует'
assert_contains 'Не найден gum' "$output" 'диагностика отсутствующего gum'
mkdir -- "${TEST_ROOT}/directory-gum"
begin_test 'gum directory override is rejected'
set +e
output=$(WG_MANAGER_GUM="${TEST_ROOT}/directory-gum" PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script" 2>&1)
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'каталог WG_MANAGER_GUM'
mkdir -- "${TEST_ROOT}/bin/gum"
begin_test 'local gum directory is rejected'
set +e
output=$(env -u WG_MANAGER_GUM PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script" 2>&1)
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'каталог local gum'
begin_test 'unusable gum override fails without fallback'
set +e
output=$(WG_MANAGER_GUM="${TEST_ROOT}/missing-gum" PATH="${TEST_ROOT}/empty-path" /bin/bash "${TESTS_DIR}/resolve-gum.sh" "$copied_script" 2>&1)
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'ошибка WG_MANAGER_GUM'
assert_contains 'WG_MANAGER_GUM' "$output" 'сообщение WG_MANAGER_GUM'
}
run_dependency_tests() {
local output status code
local -a exit_codes=("$EXIT_USAGE" "$EXIT_VALIDATION" "$EXIT_OPERATION" "$EXIT_ROLLBACK" "$EXIT_PERMISSION" "$EXIT_DEPENDENCY")
declare -A seen_codes=()
begin_test 'stable exit categories are distinct'
assert_equal 0 "$EXIT_SUCCESS" 'EXIT_SUCCESS'
for code in "${exit_codes[@]}"; do
assert_failure "$code" "ненулевой код ${code}"
if [[ -v "seen_codes[$code]" ]]; then
record_failure "коды не различаются: ${code}"
else
seen_codes["$code"]=1
((TEST_ASSERTIONS += 1))
fi
done
begin_test 'required and optional dependency checks are controlled'
output=$(require_command wg)
assert_contains '/tests/mocks/bin/wg' "$output" 'обязательная команда через mock PATH'
output=$(optional_command gum)
assert_contains '/tests/mocks/bin/gum' "$output" 'необязательная команда через mock PATH'
set +e
require_command definitely-missing >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'отсутствующая обязательная команда'
set +e
optional_command definitely-missing >/dev/null 2>&1
status=$?
set -e
assert_equal "$EXIT_DEPENDENCY" "$status" 'отсутствующая необязательная команда'
}
run_debug_tests() {
local output status
begin_test 'disabled debug is a successful no-op'
unset WG_MANAGER_DEBUG
set +e
output=$(debug hidden 2>&1)
status=$?
set -e
assert_success "$status" 'отключённая отладка'
assert_equal '' "$output" 'отключённая отладка без вывода'
begin_test 'enabled debug uses stderr only'
: >"${TEST_ROOT}/debug.stdout"
: >"${TEST_ROOT}/debug.stderr"
WG_MANAGER_DEBUG=1 debug visible >"${TEST_ROOT}/debug.stdout" 2>"${TEST_ROOT}/debug.stderr"
assert_equal '' "$(<"${TEST_ROOT}/debug.stdout")" 'debug stdout пуст'
assert_contains 'Отладка: visible' "$(<"${TEST_ROOT}/debug.stderr")" 'debug stderr'
}
if [[ -z $test_group ]]; then
begin_test 'sourcing entry point does not execute main'
source_output=''
source_output=$(/bin/bash "${TESTS_DIR}/source-entry.sh" "${PROJECT_DIR}/wg-manager.sh")
assert_equal sourced "$source_output" 'безопасная загрузка'
begin_test 'help succeeds without root or dependencies'
set +e
help_output=$("${PROJECT_DIR}/wg-manager.sh" help 2>&1)
help_status=$?
set -e
assert_success "$help_status" 'команда help'
assert_contains 'Использование:' "$help_output" 'справка'
assert_equal "$help_output" "${help_output//$'\e'/}" 'help без ANSI'
begin_test 'version succeeds without config or dependencies'
set +e
version_output=$(env -u WG_MANAGER_CONFIG -u WG_MANAGER_GUM PATH=/usr/bin:/bin "${PROJECT_DIR}/wg-manager.sh" version 2>&1)
version_status=$?
set -e
assert_success "$version_status" 'команда version'
assert_contains 'WireGuard Manager 0.1.0' "$version_output" 'версия'
assert_equal "$version_output" "${version_output//$'\e'/}" 'version без ANSI'
begin_test 'extra arguments fail with usage status'
set +e
extra_output=$("${PROJECT_DIR}/wg-manager.sh" help extra 2>&1)
extra_status=$?
set -e
assert_equal "$EXIT_USAGE" "$extra_status" 'лишний аргумент'
assert_contains 'Ожидается одна команда' "$extra_output" 'диагностика лишнего аргумента'
begin_test 'unknown command fails'
set +e
unknown_output=$("${PROJECT_DIR}/wg-manager.sh" unknown 2>&1)
unknown_status=$?
set -e
assert_failure "$unknown_status" 'неизвестная команда'
assert_contains 'неизвестная команда' "$unknown_output" 'сообщение об ошибке'
begin_test 'help works outside project directory'
set +e
outside_output=$(cd /tmp && "${PROJECT_DIR}/wg-manager.sh" help 2>&1)
outside_status=$?
set -e
assert_success "$outside_status" 'запуск из /tmp'
assert_contains 'Использование:' "$outside_output" 'справка из /tmp'
begin_test 'mock guard accepts only explicitly supported empty invocation'
set +e
MOCK_ALLOW_EMPTY_INVOCATION=wg wg
expected_mock_status=$?
wg
unapproved_empty_status=$?
wg unexpected
unexpected_mock_status=$?
set -e
assert_success "$expected_mock_status" 'разрешённый вызов мока'
assert_failure "$unapproved_empty_status" 'неразрешённый пустой вызов мока'
assert_failure "$unexpected_mock_status" 'неожиданный вызов мока'
expected_mock_log=$'wg\nwg\nwg unexpected\n'
actual_mock_log=$(<"$TEST_MOCK_LOG")
actual_mock_log+=$'\n'
assert_equal "$expected_mock_log" "$actual_mock_log" 'точный журнал вызовов мока'
begin_test 'test writes stay below isolated root'
assert_file_exists "${TEST_ROOT}/wg0.conf" 'копия фикстуры'
assert_equal "$TEST_ROOT" "${TEST_MOCK_LOG%/*}" 'журнал находится в тестовом корне'
scan_test_sources_for_host_path() {
local forbidden_path='/etc'
forbidden_path+='/wireguard'
scan_path() {
local path line
for path in "$1"/*; do
if [[ -d "$path" ]]; then
scan_path "$path"
elif [[ -f "$path" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" == *"$forbidden_path"* ]]; then
record_failure "тестовый файл содержит запрещённый путь: ${path}"
return
fi
done <"$path"
fi
done
}
scan_path "$TESTS_DIR"
}
scan_test_sources_for_host_path
fi
if [[ $test_group == config ]]; then
run_config_tests
else
run_config_tests
run_gum_tests
run_dependency_tests
run_debug_tests
fi
finish_tests