feat(apikey): auto-fetch from pass agentes/api-key when env empty
Launching from the App Hub (or any double-click) no longer needs AGENTS_API_KEY manually injected. Order of resolution: 1. AGENTS_API_KEY env var → apikey_source = "env" 2. `pass agentes/api-key` shell → apikey_source = "pass" 3. neither → apikey_source = "missing" On Windows the fallback shells via `wsl.exe -e sh -c "pass ... | head -n1"` so the secret stays in the WSL user's GnuPG keychain (never copied to a Windows file). On Linux it's a direct popen of `pass ...`. Failure mode: GPG agent locked → empty output → "missing" state in UI with a "Retry pass" button (user runs `pass agentes/api-key` once to unlock the agent, clicks Retry, app refetches without restart). Connection panel shows the active source: ✓ loaded from AGENTS_API_KEY env var ✓ loaded via `pass agentes/api-key` ⚠ apikey not found (env empty + pass failed) --connect-test uses the same two-tier resolution so e2e exercises the production code path. E2E: renamed test_connect_fails_without_apikey → test_connect_falls_back_to_pass_when_env_empty. Verifies that with empty env, the .exe still returns OK N. Skips if `pass` is locked. All 24 tests passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,18 +82,31 @@ def test_connect_succeeds_with_valid_apikey():
|
||||
assert n > 0, f"expected at least 1 agent, got {n}"
|
||||
|
||||
|
||||
def test_connect_fails_without_apikey():
|
||||
"""FAIL on stderr, exit 1, when AGENTS_API_KEY is empty."""
|
||||
# Force-empty AGENTS_API_KEY; bypass WSLENV by clearing it too.
|
||||
def test_connect_falls_back_to_pass_when_env_empty():
|
||||
"""When AGENTS_API_KEY env is empty, the .exe must fetch apikey via
|
||||
`wsl.exe pass agentes/api-key` (or `pass` on Linux). This is what makes
|
||||
launching from the App Hub work without manual env injection.
|
||||
|
||||
Skipped if `pass agentes/api-key` itself can't be read (GPG locked).
|
||||
"""
|
||||
# Verify pass is unlocked before testing the fallback
|
||||
pass_check = subprocess.run(
|
||||
["pass", "agentes/api-key"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
if pass_check.returncode != 0 or not pass_check.stdout.strip():
|
||||
pytest.skip("pass agentes/api-key not readable (GPG locked?)")
|
||||
|
||||
# Force-empty AGENTS_API_KEY + bypass WSLENV propagation
|
||||
r = subprocess.run(
|
||||
[str(_exe()), "--connect-test", _url()],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
env={**os.environ, "AGENTS_API_KEY": "", "WSLENV": "AGENTS_API_KEY"},
|
||||
env={**os.environ, "AGENTS_API_KEY": "", "WSLENV": ""},
|
||||
)
|
||||
assert r.returncode != 0
|
||||
assert "AGENTS_API_KEY" in r.stderr, f"stderr=[{r.stderr!r}]"
|
||||
assert r.returncode == 0, f"pass fallback failed: stdout={r.stdout!r} stderr={r.stderr!r}"
|
||||
assert r.stdout.startswith("OK "), f"stdout=[{r.stdout!r}]"
|
||||
|
||||
|
||||
def test_connect_fails_on_bad_host():
|
||||
|
||||
Reference in New Issue
Block a user