#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" LAUNCHER="$REPO_ROOT/chatgpt/.local/bin/start-chatgpt-proxy" DESKTOP_FILE="$REPO_ROOT/chatgpt/.local/share/applications/chatgpt-proxy.desktop" SHELL_MODULE="$REPO_ROOT/proxy-client/.config/proxy-client/shell.bash" TEST_ROOT="$(mktemp -d /tmp/chatgpt-proxy-launcher-test.XXXXXX)" trap 'rm -rf "$TEST_ROOT"' EXIT pass_count=0 fail() { echo "FAIL: $*" >&2 exit 1 } ok() { pass_count=$((pass_count + 1)) echo "ok - $*" } assert_contains() { local file="$1" expected="$2" message="$3" grep -Fq -- "$expected" "$file" || fail "$message" } assert_not_exists() { local path="$1" message="$2" [ ! -e "$path" ] || fail "$message" } setup_fixture() { local fixture="$1" mkdir -p "$fixture/bin" "$fixture/home" "$fixture/proc" "$fixture/results" cat > "$fixture/bin/pgrep" <<'EOF' #!/usr/bin/env bash if [ "${FAKE_PGREP_STATUS:-1}" = 0 ]; then printf '%s\n' "${FAKE_PGREP_PIDS:-4242}" fi exit "${FAKE_PGREP_STATUS:-1}" EOF cat > "$fixture/bin/notify-send" <<'EOF' #!/usr/bin/env bash printf '<%s>\n' "$@" >> "$RESULTS_DIR/notifications" EOF cat > "$fixture/bin/proxy-client" <<'EOF' #!/usr/bin/env bash printf 'called\n' >> "$RESULTS_DIR/proxy-calls" if [ "${FAKE_PROXY_FAIL:-0}" = 1 ]; then echo 'тестовый прокси недоступен' >&2 exit 1 fi [ "${1:-}" = env ] || exit 99 cat <<'ENV' HTTP_PROXY=http://proxy.test:1234 HTTPS_PROXY=http://proxy.test:1234 ALL_PROXY=socks5://proxy.test:1234 NO_PROXY=proxy.test,localhost,127.0.0.1,::1,[::1],.example.test http_proxy=http://proxy.test:1234 https_proxy=http://proxy.test:1234 all_proxy=socks5://proxy.test:1234 no_proxy=proxy.test,localhost,127.0.0.1,::1,[::1],.example.test ENV EOF cat > "$fixture/bin/chatgpt" <<'EOF' #!/usr/bin/env bash printf '<%s>\n' "$@" > "$RESULTS_DIR/chatgpt-args" printf 'HTTP_PROXY=%s\nALL_PROXY=%s\nNO_PROXY=%s\n' \ "$HTTP_PROXY" "$ALL_PROXY" "$NO_PROXY" > "$RESULTS_DIR/chatgpt-env" EOF chmod +x "$fixture/bin/pgrep" "$fixture/bin/notify-send" \ "$fixture/bin/proxy-client" "$fixture/bin/chatgpt" } write_process() { local fixture="$1" pid="$2" shift 2 mkdir -p "$fixture/proc/$pid" printf '%s\0' "$@" > "$fixture/proc/$pid/cmdline" } run_launcher() { local fixture="$1" shift HOME="$fixture/home" \ PATH="$fixture/bin:/usr/bin:/bin" \ RESULTS_DIR="$fixture/results" \ PROXY_CLIENT_BIN="$fixture/bin/proxy-client" \ PROXY_CLIENT_SHELL="$SHELL_MODULE" \ CHATGPT_BIN="$fixture/bin/chatgpt" \ PROC_ROOT="$fixture/proc" \ "$LAUNCHER" "$@" } # Успешный запуск передаёт и окружение, и явные параметры Chromium. fixture="$TEST_ROOT/success" setup_fixture "$fixture" FAKE_PGREP_STATUS=1 run_launcher "$fixture" 'chatgpt://example/path' assert_contains "$fixture/results/chatgpt-args" \ '<--proxy-server=http://proxy.test:1234>' \ 'не передан сервер Chromium' assert_contains "$fixture/results/chatgpt-args" \ '<--proxy-bypass-list=proxy.test;localhost;127.0.0.1;::1;[::1];*.example.test>' \ 'неверно преобразован список обхода Chromium' assert_contains "$fixture/results/chatgpt-args" '' \ 'пользовательский аргумент потерян' assert_contains "$fixture/results/chatgpt-env" \ 'ALL_PROXY=socks5://proxy.test:1234' \ 'proxy-окружение не унаследовано' assert_not_exists "$fixture/results/notifications" \ 'успешный запуск показал ошибку' ok 'успешный запуск применяет proxy-client к Chromium и окружению' # Уже работающий через тот же прокси экземпляр получает повторный запрос: # Electron поднимает его существующее окно вместо ошибки «уже запущен». fixture="$TEST_ROOT/running-proxied" setup_fixture "$fixture" write_process "$fixture" 4242 \ '/usr/lib/chatgpt/ChatGPT --proxy-server=http://proxy.test:1234 --proxy-bypass-list=proxy.test;localhost;127.0.0.1;::1;[::1];*.example.test' FAKE_PGREP_STATUS=0 run_launcher "$fixture" 'chatgpt://existing' assert_contains "$fixture/results/chatgpt-args" '' \ 'повторный запрос не передан прокси-экземпляру' assert_not_exists "$fixture/results/notifications" \ 'работающий прокси-экземпляр ошибочно объявлен конфликтом' ok 'повторный прокси-запуск открывает существующий экземпляр' # Прямой экземпляр блокируется с точной инструкцией: встроенный Restart # сохранит прямой режим, поэтому приложение нужно завершить через системный лоток. fixture="$TEST_ROOT/running-direct" setup_fixture "$fixture" write_process "$fixture" 4242 /usr/lib/chatgpt/ChatGPT set +e FAKE_PGREP_STATUS=0 run_launcher "$fixture" >/dev/null 2>&1 status=$? set -e [ "$status" -eq 1 ] || fail "ожидался код 1 при прямом ChatGPT, получен $status" assert_not_exists "$fixture/results/chatgpt-args" \ 'прямой ChatGPT получил повторный запуск' assert_contains "$fixture/results/notifications" 'без прокси' \ 'уведомление не различает прямой экземпляр' assert_contains "$fixture/results/notifications" 'системном лотке' \ 'нет инструкции для полного завершения прямого экземпляра' ok 'прямой экземпляр блокируется с точной инструкцией' # Недоступный прокси не приводит к прямому запуску. fixture="$TEST_ROOT/unavailable" setup_fixture "$fixture" set +e FAKE_PGREP_STATUS=1 FAKE_PROXY_FAIL=1 run_launcher "$fixture" >/dev/null 2>&1 status=$? set -e [ "$status" -eq 1 ] || fail "ожидался код 1 при ошибке прокси, получен $status" assert_not_exists "$fixture/results/chatgpt-args" \ 'ChatGPT запущен напрямую после ошибки прокси' assert_contains "$fixture/results/notifications" 'proxy-client status' \ 'нет диагностической подсказки при ошибке прокси' ok 'ошибка прокси запрещает прямой запуск' # Ярлык остаётся отдельным и не перехватывает штатные обработчики ChatGPT. assert_contains "$DESKTOP_FILE" 'Name=ChatGPT Proxy' \ 'неверное имя ярлыка' assert_contains "$DESKTOP_FILE" 'Exec=start-chatgpt-proxy %U' \ 'ярлык не вызывает управляемый запускатель' if grep -q '^MimeType=' "$DESKTOP_FILE"; then fail 'прокси-ярлык не должен перехватывать штатные MIME-обработчики' fi [ "$(basename "$DESKTOP_FILE")" = chatgpt-proxy.desktop ] \ || fail 'прокси-ярлык подменяет системный chatgpt.desktop' ok 'ChatGPT Proxy остаётся отдельным пользовательским ярлыком' if command -v desktop-file-validate >/dev/null 2>&1; then desktop-file-validate "$DESKTOP_FILE" >/dev/null \ || fail 'desktop-file-validate нашёл ошибку' ok 'desktop-файл корректен' else echo 'skip - desktop-file-validate не установлен' fi if command -v shellcheck >/dev/null 2>&1; then shellcheck -s bash "$LAUNCHER" "$0" \ || fail 'shellcheck нашёл проблемы' ok 'shellcheck чист' else echo 'skip - shellcheck не установлен' fi echo "PASS: $pass_count проверок пройдено"