#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" FUNCTIONS_FILE="$REPO_ROOT/bash/.bash_functions" pass_count=0 fail() { echo "FAIL: $*" >&2 exit 1 } ok() { pass_count=$((pass_count + 1)) echo "ok - $*" } assert_eq() { local expected="$1" actual="$2" msg="$3" [ "$expected" = "$actual" ] || fail "$msg (ожидалось '$expected', получено '$actual')" } # Изолированный fake-CLI на PATH: печатает env и аргументы, отдаёт заданный код. make_fake_bin() { local dir dir="$(mktemp -d /tmp/proxy-launchers-test.XXXXXX)" cat >"$dir/fakecli" <<'EOF' #!/usr/bin/env bash echo "HTTP_PROXY=${HTTP_PROXY:-}" echo "NO_PROXY=${NO_PROXY:-}" for a in "$@"; do echo "ARG=$a"; done exit "${FAKE_EXIT:-0}" EOF chmod +x "$dir/fakecli" echo "$dir" } # --- Каждый кейс в отдельном subshell, чтобы env/функции не протекали --- # 1. Прокси недоступен → бинарник НЕ вызывается, ненулевой код. ( source "$FUNCTIONS_FILE" _proxy_reachable() { return 1; } bindir="$(make_fake_bin)"; export PATH="$bindir:$PATH" out="$(_proxy_run fakecli 2>&1)"; rc=$? [ "$rc" -ne 0 ] || fail "недоступный прокси: ожидался ненулевой код" case "$out" in *ARG=*) fail "недоступный прокси: бинарник не должен запускаться";; esac case "$out" in *"недоступен"*) ;; *) fail "недоступный прокси: нет понятного сообщения";; esac ) && ok "недоступный прокси → fail-fast без запуска бинарника" # 2. proxy-env виден дочернему процессу и НЕ протекает в родительский shell. ( source "$FUNCTIONS_FILE" _proxy_reachable() { return 0; } bindir="$(make_fake_bin)"; export PATH="$bindir:$PATH" unset HTTP_PROXY NO_PROXY || true out="$(_proxy_run fakecli)" case "$out" in *"HTTP_PROXY=http://hproxy.local:12334"*) ;; *) fail "child: HTTP_PROXY не выставлен";; esac case "$out" in *"NO_PROXY=hproxy.local,localhost,127.0.0.1,::1,[::1],.concept-software.ru"*) ;; *) fail "child: NO_PROXY неверен";; esac [ -z "${HTTP_PROXY:-}" ] || fail "leak: HTTP_PROXY протёк в родительский shell" [ -z "${NO_PROXY:-}" ] || fail "leak: NO_PROXY протёк в родительский shell" ) && ok "proxy-env в дочернем процессе, без протечки в shell" # 3. Проброс кода возврата дочернего процесса. ( source "$FUNCTIONS_FILE" _proxy_reachable() { return 0; } bindir="$(make_fake_bin)"; export PATH="$bindir:$PATH" FAKE_EXIT=42 _proxy_run fakecli >/dev/null; rc=$? assert_eq 42 "$rc" "код возврата дочернего процесса не проброшен" ) && ok "проброс кода возврата дочернего процесса" # 4. Аргументы с пробелами и `--` доходят без искажений. ( source "$FUNCTIONS_FILE" _proxy_reachable() { return 0; } bindir="$(make_fake_bin)"; export PATH="$bindir:$PATH" out="$(_proxy_run fakecli "a b" -- --flag)" case "$out" in *$'ARG=a b\n'*) ;; *) fail "arg с пробелом потерян";; esac case "$out" in *"ARG=--"*) ;; *) fail "разделитель -- потерян";; esac case "$out" in *"ARG=--flag"*) ;; *) fail "флаг после -- потерян";; esac ) && ok "аргументы с пробелами и -- проброшены без искажений" # 5. Команда не в PATH → отдельное сообщение, код 127. ( source "$FUNCTIONS_FILE" _proxy_reachable() { return 0; } out="$(_proxy_run definitely-no-such-cli-xyz 2>&1)"; rc=$? assert_eq 127 "$rc" "отсутствующая команда: ожидался код 127" case "$out" in *"не найдена"*) ;; *) fail "отсутствующая команда: нет понятного сообщения";; esac ) && ok "команда не в PATH → код 127 с сообщением" # 6. Регресс proxy_on: точный контракт экспортируемых переменных. ( source "$FUNCTIONS_FILE" curl() { echo "1.2.3.4"; } # без сети unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY \ http_proxy https_proxy all_proxy no_proxy || true proxy_on >/dev/null np="hproxy.local,localhost,127.0.0.1,::1,[::1],.concept-software.ru" assert_eq "http://hproxy.local:12334" "${HTTP_PROXY}" "proxy_on HTTP_PROXY" assert_eq "http://hproxy.local:12334" "${HTTPS_PROXY}" "proxy_on HTTPS_PROXY" assert_eq "socks5://hproxy.local:12334" "${ALL_PROXY}" "proxy_on ALL_PROXY" assert_eq "$np" "${NO_PROXY}" "proxy_on NO_PROXY" assert_eq "${HTTP_PROXY}" "${http_proxy}" "proxy_on http_proxy дубль" assert_eq "${HTTPS_PROXY}" "${https_proxy}" "proxy_on https_proxy дубль" assert_eq "${ALL_PROXY}" "${all_proxy}" "proxy_on all_proxy дубль" assert_eq "$np" "${no_proxy}" "proxy_on no_proxy дубль" ) && ok "регресс proxy_on: контракт переменных не изменился" # 7. shellcheck (если доступен). if command -v shellcheck >/dev/null 2>&1; then shellcheck -s bash "$FUNCTIONS_FILE" || fail "shellcheck нашёл проблемы в bash/.bash_functions" ok "shellcheck чист" else echo "skip - shellcheck не установлен" fi echo "PASS: $pass_count проверок пройдено"