feat(antigravity): выход через прокси вместо патча бинарника
Вход через терминал прошёл: agy запустился в изолированном каталоге ag-5 и опознал аккаунт владельца. Отказал Google: «Eligibility check failed: not currently available in your location». Проверка смотрит на адрес выхода. У владельца есть узлы 3x-ui в разных странах. Поэтому обход делается выходом через разрешённую страну, а не патчем чужого бинарника: ничего не ломается при обновлении Antigravity и не выполняется сторонний код. Адрес задаётся общий в настройках и отдельный на профиль — разным аккаунтам может требоваться разная страна. Применяется к запросу каталога моделей, к вызовам моделей и к сценарию входа в терминале. Пишутся и заглавные, и строчные имена переменных: Go читает HTTPS_PROXY, многие библиотеки — https_proxy; ALL_PROXY нужен для socks5. Адрес проверяется по существу, а не по схеме: приписать socks5:// можно чему угодно, и тогда мусор выглядел бы принятым, а обращения провайдера молча ломались бы без внятной причины. 695 passed, 2 skipped; ruff чисто; релизный гейт пройден. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
28f35f863f
commit
fa7bbef8af
9 changed files with 265 additions and 15 deletions
|
|
@ -18,7 +18,7 @@ namespace HermesHubSetup
|
||||||
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
|
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
|
||||||
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
|
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
|
||||||
// какого кода собран установщик.
|
// какого кода собран установщик.
|
||||||
public const string BuildCommit = "884a632";
|
public const string BuildCommit = "28f35f8";
|
||||||
public const string MIN_HERMES_VERSION = "0.20.0";
|
public const string MIN_HERMES_VERSION = "0.20.0";
|
||||||
public const string MAX_TESTED_HERMES = "0.20.4";
|
public const string MAX_TESTED_HERMES = "0.20.4";
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -205,15 +205,20 @@ def discover_models(profile_id: str | None = None) -> dict[str, str]:
|
||||||
from antigravity_provider.router.adapters.antigravity_adapter import get_profile_env_dir
|
from antigravity_provider.router.adapters.antigravity_adapter import get_profile_env_dir
|
||||||
|
|
||||||
profile_dir = get_profile_env_dir(target_profile_id)
|
profile_dir = get_profile_env_dir(target_profile_id)
|
||||||
env = build_safe_subprocess_env(
|
|
||||||
overrides = {
|
overrides = {
|
||||||
"USERPROFILE": str(profile_dir),
|
"USERPROFILE": str(profile_dir),
|
||||||
"HOME": str(profile_dir),
|
"HOME": str(profile_dir),
|
||||||
"HOMEPATH": str(profile_dir),
|
"HOMEPATH": str(profile_dir),
|
||||||
}
|
}
|
||||||
)
|
# Проверка доступности у Google смотрит на адрес: без выхода через
|
||||||
|
# разрешённую страну она отвечает «not currently available in your
|
||||||
|
# location», и каталог моделей получить нельзя.
|
||||||
|
overrides.update(proxy_env_overrides(resolve_provider_proxy(target_profile_id)))
|
||||||
|
env = build_safe_subprocess_env(overrides=overrides)
|
||||||
else:
|
else:
|
||||||
env = build_safe_subprocess_env()
|
env = build_safe_subprocess_env(
|
||||||
|
overrides=proxy_env_overrides(resolve_provider_proxy())
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
@ -718,6 +723,53 @@ BLOCKED_SECRET_PATTERNS: tuple[str, ...] = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_provider_proxy(profile_id: str | None = None) -> str:
|
||||||
|
"""Адрес прокси для обращений провайдера: сначала профиль, затем общий.
|
||||||
|
|
||||||
|
Google отказывает по местоположению: «not currently available in your
|
||||||
|
location». Проверка смотрит на адрес, поэтому вопрос решается выходом
|
||||||
|
через нужную страну, а не правкой чужого бинарника.
|
||||||
|
|
||||||
|
Настройка на профиль важна не для красоты: у владельца несколько
|
||||||
|
выходных узлов в разных странах, и разным аккаунтам может требоваться
|
||||||
|
разный.
|
||||||
|
"""
|
||||||
|
if profile_id:
|
||||||
|
try:
|
||||||
|
from antigravity_provider.router.profile_manager import ProfileAuthManager
|
||||||
|
|
||||||
|
auth = ProfileAuthManager.load_profile_auth("antigravity", profile_id) or {}
|
||||||
|
own = str(auth.get("proxy_url") or "").strip()
|
||||||
|
if own:
|
||||||
|
return own
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("Прокси профиля %s не прочитан: %s", profile_id, exc)
|
||||||
|
try:
|
||||||
|
from antigravity_provider.router.settings_service import get_hub_settings
|
||||||
|
|
||||||
|
return str(get_hub_settings().get("provider_proxy_url") or "").strip()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("Общая настройка прокси не прочитана: %s", exc)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_env_overrides(proxy_url: str) -> dict[str, str]:
|
||||||
|
"""Переменные окружения для прокси. Пустой адрес — пустой набор.
|
||||||
|
|
||||||
|
Пишем и заглавные, и строчные имена: Go читает HTTPS_PROXY, curl и
|
||||||
|
большинство библиотек — https_proxy. ALL_PROXY нужен для socks5.
|
||||||
|
"""
|
||||||
|
url = (proxy_url or "").strip()
|
||||||
|
if not url:
|
||||||
|
return {}
|
||||||
|
names = ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY")
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for name in names:
|
||||||
|
out[name] = url
|
||||||
|
out[name.lower()] = url
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def detect_graphical_session() -> tuple[dict[str, str], list[str]]:
|
def detect_graphical_session() -> tuple[dict[str, str], list[str]]:
|
||||||
"""Найти графический сеанс владельца. Вернуть (переменные, что проверено).
|
"""Найти графический сеанс владельца. Вернуть (переменные, что проверено).
|
||||||
|
|
||||||
|
|
@ -816,6 +868,19 @@ def write_login_helper(profile_dir: Path, agy_exe: str, profile_id: str) -> Path
|
||||||
"USERPROFILE=" + shlex.quote(str(profile_dir)),
|
"USERPROFILE=" + shlex.quote(str(profile_dir)),
|
||||||
"HOMEPATH=" + shlex.quote(str(profile_dir)),
|
"HOMEPATH=" + shlex.quote(str(profile_dir)),
|
||||||
"export HOME USERPROFILE HOMEPATH",
|
"export HOME USERPROFILE HOMEPATH",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Проверка доступности у Google смотрит на адрес выхода. Без прокси вход
|
||||||
|
# завершается «Eligibility check failed: not currently available in your
|
||||||
|
# location» — аккаунт при этом опознан верно, дело только в стране.
|
||||||
|
proxy = resolve_provider_proxy(profile_id)
|
||||||
|
if proxy:
|
||||||
|
for name, value in proxy_env_overrides(proxy).items():
|
||||||
|
lines.append(name + "=" + shlex.quote(value))
|
||||||
|
lines.append("export " + " ".join(sorted(proxy_env_overrides(proxy))))
|
||||||
|
lines.append('echo "Выход через прокси: ' + proxy.replace('"', "") + '"')
|
||||||
|
|
||||||
|
lines += [
|
||||||
"cd " + shlex.quote(str(profile_dir)) + " || exit 1",
|
"cd " + shlex.quote(str(profile_dir)) + " || exit 1",
|
||||||
'echo "Вход Antigravity в слот ' + profile_id + '"',
|
'echo "Вход Antigravity в слот ' + profile_id + '"',
|
||||||
'echo "Каталог профиля: $HOME"',
|
'echo "Каталог профиля: $HOME"',
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,21 @@ class AntigravityAdapter(BaseProviderAdapter):
|
||||||
def invoke(self, profile: RouterProfileConfig, request: Dict[str, Any]) -> Dict[str, Any]:
|
def invoke(self, profile: RouterProfileConfig, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
profile_dir = get_profile_env_dir(profile.profile_id)
|
profile_dir = get_profile_env_dir(profile.profile_id)
|
||||||
# Isolate USERPROFILE and HOME while strictly stripping non-Antigravity provider secrets
|
# Isolate USERPROFILE and HOME while strictly stripping non-Antigravity provider secrets
|
||||||
custom_env = build_safe_subprocess_env(
|
|
||||||
overrides = {
|
overrides = {
|
||||||
"USERPROFILE": str(profile_dir),
|
"USERPROFILE": str(profile_dir),
|
||||||
"HOME": str(profile_dir),
|
"HOME": str(profile_dir),
|
||||||
"HOMEPATH": str(profile_dir),
|
"HOMEPATH": str(profile_dir),
|
||||||
}
|
}
|
||||||
|
# Тот же прокси, что и при входе: иначе аккаунт подключён, а запросы
|
||||||
|
# упираются в проверку доступности по стране.
|
||||||
|
from antigravity_provider.agy_subprocess import (
|
||||||
|
proxy_env_overrides,
|
||||||
|
resolve_provider_proxy,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
overrides.update(proxy_env_overrides(resolve_provider_proxy(profile.profile_id)))
|
||||||
|
custom_env = build_safe_subprocess_env(overrides=overrides)
|
||||||
|
|
||||||
# If profile specifies a preferred model and request has generic or no model
|
# If profile specifies a preferred model and request has generic or no model
|
||||||
req = dict(request)
|
req = dict(request)
|
||||||
model = req.get("model", "")
|
model = req.get("model", "")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ along with Obsidian shared memory validation and canonical structure setup.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
@ -36,6 +38,11 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
||||||
"compression_threshold_percent": 75.0,
|
"compression_threshold_percent": 75.0,
|
||||||
"compression_keep_recent_messages": 3,
|
"compression_keep_recent_messages": 3,
|
||||||
"compression_enabled": True,
|
"compression_enabled": True,
|
||||||
|
# Прокси для обращений провайдеров, у которых проверка доступности
|
||||||
|
# смотрит на адрес. Google отказал владельцу: «not currently available
|
||||||
|
# in your location». Задаётся адресом вида socks5://127.0.0.1:1080 или
|
||||||
|
# http://127.0.0.1:8080; пустое значение означает «без прокси».
|
||||||
|
"provider_proxy_url": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,6 +64,36 @@ def get_settings_file() -> Path:
|
||||||
return get_hermes_home() / "hub_settings.json"
|
return get_hermes_home() / "hub_settings.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_proxy_url(raw: Any) -> str:
|
||||||
|
"""Привести адрес прокси к рабочему виду или отбросить.
|
||||||
|
|
||||||
|
Проверяем не только схему, но и сам адрес: приписать socks5:// можно
|
||||||
|
чему угодно, и тогда мусор выглядел бы принятым, а все обращения
|
||||||
|
провайдера молча ломались бы без внятной причины.
|
||||||
|
"""
|
||||||
|
value = str(raw or "").strip()
|
||||||
|
if not value:
|
||||||
|
return ""
|
||||||
|
if "://" not in value:
|
||||||
|
value = "socks5://" + value
|
||||||
|
try:
|
||||||
|
parsed = urlparse(value)
|
||||||
|
except ValueError:
|
||||||
|
return ""
|
||||||
|
if parsed.scheme not in ("http", "https", "socks5", "socks5h"):
|
||||||
|
return ""
|
||||||
|
host = parsed.hostname or ""
|
||||||
|
if not host or not re.fullmatch(r"[A-Za-z0-9._\-\[\]:]+", host):
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
port = parsed.port
|
||||||
|
except ValueError:
|
||||||
|
return ""
|
||||||
|
if port is not None and not (1 <= port <= 65535):
|
||||||
|
return ""
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def get_hub_settings() -> Dict[str, Any]:
|
def get_hub_settings() -> Dict[str, Any]:
|
||||||
"""Load settings from hub_settings.json merged with standard defaults, cached by mtime."""
|
"""Load settings from hub_settings.json merged with standard defaults, cached by mtime."""
|
||||||
global _SETTINGS_CACHE, _SETTINGS_CACHE_MTIME, _SETTINGS_CACHE_PATH
|
global _SETTINGS_CACHE, _SETTINGS_CACHE_MTIME, _SETTINGS_CACHE_PATH
|
||||||
|
|
@ -139,6 +176,10 @@ def get_hub_settings() -> Dict[str, Any]:
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
merged["compression_threshold_percent"] = 75.0
|
merged["compression_threshold_percent"] = 75.0
|
||||||
|
|
||||||
|
# Адрес прокси проверяем на вид, а не принимаем что попало: неверная
|
||||||
|
# схема тихо ломает все обращения провайдера, и причина не видна.
|
||||||
|
merged["provider_proxy_url"] = _normalize_proxy_url(merged.get("provider_proxy_url"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
merged["compression_keep_recent_messages"] = max(1, min(20, int(merged.get("compression_keep_recent_messages", 3))))
|
merged["compression_keep_recent_messages"] = max(1, min(20, int(merged.get("compression_keep_recent_messages", 3))))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
|
|
|
||||||
|
|
@ -1727,6 +1727,18 @@ function renderSettingsView() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Прокси провайдеров: пустая строка — осмысленное значение «без прокси»,
|
||||||
|
// поэтому отличаем её от «сервер не передал».
|
||||||
|
const proxyInput = document.getElementById('setting-provider-proxy-url');
|
||||||
|
if (proxyInput) {
|
||||||
|
if (s.provider_proxy_url !== undefined) {
|
||||||
|
proxyInput.value = s.provider_proxy_url || '';
|
||||||
|
proxyInput.placeholder = 'без прокси';
|
||||||
|
} else {
|
||||||
|
proxyInput.placeholder = 'Н/Д: не передан сервером';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Quota Interval
|
// Quota Interval
|
||||||
const quotaIntervalSel = document.getElementById('setting-quota-interval');
|
const quotaIntervalSel = document.getElementById('setting-quota-interval');
|
||||||
if (quotaIntervalSel) {
|
if (quotaIntervalSel) {
|
||||||
|
|
@ -1829,7 +1841,12 @@ async function saveHubServerSettings() {
|
||||||
const defaultRoleSel = document.getElementById('setting-default-role');
|
const defaultRoleSel = document.getElementById('setting-default-role');
|
||||||
const themeSel = document.getElementById('setting-theme');
|
const themeSel = document.getElementById('setting-theme');
|
||||||
|
|
||||||
|
const proxyInputSave = document.getElementById('setting-provider-proxy-url');
|
||||||
|
|
||||||
const newSettings = {};
|
const newSettings = {};
|
||||||
|
// Пустое поле — это выбор «без прокси», а не отсутствие значения:
|
||||||
|
// отправляем его тоже, иначе прокси нельзя было бы убрать.
|
||||||
|
if (proxyInputSave) newSettings.provider_proxy_url = proxyInputSave.value.trim();
|
||||||
if (hostInput && hostInput.value.trim()) newSettings.web_api_host = hostInput.value.trim();
|
if (hostInput && hostInput.value.trim()) newSettings.web_api_host = hostInput.value.trim();
|
||||||
if (portInput && portInput.value) newSettings.web_api_port = Number(portInput.value);
|
if (portInput && portInput.value) newSettings.web_api_port = Number(portInput.value);
|
||||||
if (tokenInput && tokenInput.value.trim()) newSettings.web_api_token = tokenInput.value.trim();
|
if (tokenInput && tokenInput.value.trim()) newSettings.web_api_token = tokenInput.value.trim();
|
||||||
|
|
|
||||||
|
|
@ -455,6 +455,13 @@
|
||||||
<input type="password" id="setting-server-token-input" class="input-text" placeholder="Задать новый токен...">
|
<input type="password" id="setting-server-token-input" class="input-text" placeholder="Задать новый токен...">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label class="setting-label" for="setting-provider-proxy-url">Прокси для провайдеров</label>
|
||||||
|
<div class="setting-desc">Google отказывает по местоположению: «not currently available in your location». Выход через разрешённую страну снимает отказ. Пример: <code>socks5://127.0.0.1:1080</code>. Пусто — без прокси. Отдельному аккаунту можно задать свой адрес.</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control"><input id="setting-provider-proxy-url" class="input-text" type="text" placeholder="Н/Д: загрузка" aria-label="Адрес прокси для обращений провайдеров"></div>
|
||||||
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
<label class="setting-label" for="setting-account-check-interval">Проверка аккаунтов и моделей</label>
|
<label class="setting-label" for="setting-account-check-interval">Проверка аккаунтов и моделей</label>
|
||||||
|
|
|
||||||
113
tests/test_provider_proxy.py
Normal file
113
tests/test_provider_proxy.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""Обход проверки доступности по стране — через прокси, а не патч бинарника.
|
||||||
|
|
||||||
|
Вход владельца через терминал прошёл: agy запустился в изолированном каталоге и
|
||||||
|
опознал аккаунт. Отказал Google: «Eligibility check failed: Your current account
|
||||||
|
is not eligible for Antigravity, because it is not currently available in your
|
||||||
|
location». Проверка смотрит на адрес выхода.
|
||||||
|
|
||||||
|
Правильный ответ — выйти через разрешённую страну. У владельца несколько узлов
|
||||||
|
в разных странах, поэтому адрес задаётся и общий, и на каждый профиль отдельно.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from antigravity_provider.agy_subprocess import (
|
||||||
|
proxy_env_overrides,
|
||||||
|
resolve_provider_proxy,
|
||||||
|
write_login_helper,
|
||||||
|
)
|
||||||
|
from antigravity_provider.router.settings_service import _normalize_proxy_url
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"given, expected",
|
||||||
|
[
|
||||||
|
("socks5://127.0.0.1:1080", "socks5://127.0.0.1:1080"),
|
||||||
|
("127.0.0.1:1080", "socks5://127.0.0.1:1080"),
|
||||||
|
("http://proxy.example.com:8080", "http://proxy.example.com:8080"),
|
||||||
|
("socks5h://10.0.0.5:1080", "socks5h://10.0.0.5:1080"),
|
||||||
|
("", ""),
|
||||||
|
(" ", ""),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_valid_addresses_are_kept(given, expected):
|
||||||
|
assert _normalize_proxy_url(given) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"junk",
|
||||||
|
["мусор!!", "не адрес вовсе", "ftp://1.2.3.4:21", "socks5://1.2.3.4:99999", "://"],
|
||||||
|
)
|
||||||
|
def test_junk_is_rejected_not_dressed_up_with_a_scheme(junk):
|
||||||
|
"""Приписать socks5:// можно чему угодно.
|
||||||
|
|
||||||
|
Если бы мусор проходил, он выглядел бы принятым, а все обращения провайдера
|
||||||
|
молча ломались бы без внятной причины.
|
||||||
|
"""
|
||||||
|
assert _normalize_proxy_url(junk) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_covers_upper_lower_and_socks():
|
||||||
|
env = proxy_env_overrides("socks5://127.0.0.1:1080")
|
||||||
|
|
||||||
|
# Go читает HTTPS_PROXY, curl и многие библиотеки — https_proxy,
|
||||||
|
# ALL_PROXY нужен для socks5.
|
||||||
|
assert set(env) == {
|
||||||
|
"HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY",
|
||||||
|
"http_proxy", "https_proxy", "all_proxy",
|
||||||
|
}
|
||||||
|
assert set(env.values()) == {"socks5://127.0.0.1:1080"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_proxy_means_no_variables():
|
||||||
|
assert proxy_env_overrides("") == {}
|
||||||
|
assert proxy_env_overrides(" ") == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_address_wins_over_the_common_one():
|
||||||
|
"""Узлы в разных странах: разным аккаунтам может требоваться разный выход."""
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.router.profile_manager.ProfileAuthManager.load_profile_auth",
|
||||||
|
return_value={"proxy_url": "socks5://nl.example:1080"},
|
||||||
|
), patch(
|
||||||
|
"antigravity_provider.router.settings_service.get_hub_settings",
|
||||||
|
return_value={"provider_proxy_url": "socks5://de.example:1080"},
|
||||||
|
):
|
||||||
|
assert resolve_provider_proxy("ag-1") == "socks5://nl.example:1080"
|
||||||
|
|
||||||
|
|
||||||
|
def test_common_address_is_used_when_profile_has_none():
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.router.profile_manager.ProfileAuthManager.load_profile_auth",
|
||||||
|
return_value={},
|
||||||
|
), patch(
|
||||||
|
"antigravity_provider.router.settings_service.get_hub_settings",
|
||||||
|
return_value={"provider_proxy_url": "socks5://de.example:1080"},
|
||||||
|
):
|
||||||
|
assert resolve_provider_proxy("ag-1") == "socks5://de.example:1080"
|
||||||
|
|
||||||
|
|
||||||
|
def test_login_script_exports_the_proxy(tmp_path):
|
||||||
|
with patch(
|
||||||
|
"antigravity_provider.agy_subprocess.resolve_provider_proxy",
|
||||||
|
return_value="socks5://127.0.0.1:1080",
|
||||||
|
):
|
||||||
|
body = write_login_helper(tmp_path, "/usr/local/bin/agy", "ag-1").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "HTTPS_PROXY=" in body
|
||||||
|
assert "export " in body and "ALL_PROXY" in body
|
||||||
|
assert "socks5://127.0.0.1:1080" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_login_script_without_proxy_mentions_none(tmp_path):
|
||||||
|
with patch("antigravity_provider.agy_subprocess.resolve_provider_proxy", return_value=""):
|
||||||
|
body = write_login_helper(tmp_path, "/usr/local/bin/agy", "ag-1").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "PROXY" not in body
|
||||||
Loading…
Reference in a new issue