ea3c62d2f2
Security: apikey NUNCA mas vive en local DB ni se introduce por UI.
Se lee al arranque via getenv("AGENTS_API_KEY"), sourced typically con
`pass agentes/api-key` antes de lanzar el .exe.
Connection panel:
- Removed apikey TextInput (+show/hide button)
- Removed UI mask flag
- Replaced con indicador "loaded from env" verde / "missing" rojo
- Hint visible: "Launch with: AGENTS_API_KEY=$(pass agentes/api-key) <exe>"
Persistencia local:
- db_save_connection: solo base_url (blob de apikey ya no se cifra)
- db_load_connection: solo base_url
- No mas roundtrip a fn_secret en runtime de la UI (la funcion del
registry secret_store_cpp_infra sigue util para otras apps)
CLI:
- --connect-test <url> ahora lee apikey de AGENTS_API_KEY env var
- trim_url() en make_url + en CLI defensivo contra paste con CR/LF
- run_self_test sin cambios (secret_store roundtrip se mantiene)
E2E tests (tests/test_connect_e2e.py, 5 casos):
- test_connect_succeeds_with_valid_apikey
- test_connect_fails_without_apikey
- test_connect_fails_on_bad_host
- test_count_matches_direct_curl
- test_url_trim_robust_to_whitespace
Lanzar con:
AGENTS_API_KEY=$(pass agentes/api-key) \
python3 -m pytest -v tests/test_connect_e2e.py
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
137 lines
4.4 KiB
Python
137 lines
4.4 KiB
Python
"""
|
|
E2E connect tests for agents_dashboard against the live backend.
|
|
|
|
Runs the deployed .exe with --connect-test, which exercises the SAME
|
|
fn_http code path the GUI uses (curl spawn via CreateProcessW on Windows).
|
|
Verifies:
|
|
|
|
* /health → 200 sin auth
|
|
* /agents → 401 sin Bearer
|
|
* /agents → 200 + JSON array con Bearer
|
|
|
|
Skips if:
|
|
* AGENTS_API_KEY env var missing (run with `pass agentes/api-key`)
|
|
* Deployed exe not found
|
|
|
|
Run from repo root:
|
|
AGENTS_API_KEY=$(pass agentes/api-key) \\
|
|
python3 -m pytest -x -q projects/element_agents/apps/agents_dashboard/tests/test_connect_e2e.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
DEFAULT_EXE = Path("/mnt/c/Users/lucas/Desktop/apps/agents_dashboard/agents_dashboard.exe")
|
|
DEFAULT_URL = "https://agents.organic-machine.com"
|
|
|
|
|
|
def _exe() -> Path:
|
|
p = Path(os.environ.get("AGENTS_DASHBOARD_EXE", DEFAULT_EXE))
|
|
if not p.exists():
|
|
pytest.skip(f"deployed exe not found: {p}")
|
|
return p
|
|
|
|
|
|
def _url() -> str:
|
|
return os.environ.get("AGENTS_DASHBOARD_URL", DEFAULT_URL)
|
|
|
|
|
|
def _apikey() -> str:
|
|
k = os.environ.get("AGENTS_API_KEY", "").strip()
|
|
if not k:
|
|
pytest.skip("AGENTS_API_KEY env var missing (try `pass agentes/api-key`)")
|
|
return k
|
|
|
|
|
|
def _run_connect_test(url: str, env: dict | None = None) -> subprocess.CompletedProcess:
|
|
"""Invoke .exe --connect-test with WSLENV so env propagates to Windows binary."""
|
|
full_env = os.environ.copy()
|
|
if env:
|
|
full_env.update(env)
|
|
# WSLENV: comma/colon-separated list of env names to forward to Win32
|
|
keep = full_env.get("WSLENV", "")
|
|
add = "AGENTS_API_KEY"
|
|
full_env["WSLENV"] = f"{keep}:{add}" if keep else add
|
|
return subprocess.run(
|
|
[str(_exe()), "--connect-test", url],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
env=full_env,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_connect_succeeds_with_valid_apikey():
|
|
"""OK <N> on stdout, exit 0, when env + URL + apikey are valid."""
|
|
_apikey() # skip if missing
|
|
r = _run_connect_test(_url())
|
|
assert r.returncode == 0, f"exit={r.returncode}\nstdout={r.stdout}\nstderr={r.stderr}"
|
|
assert r.stdout.startswith("OK "), f"stdout=[{r.stdout!r}]"
|
|
n = int(r.stdout.strip().split()[1])
|
|
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.
|
|
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"},
|
|
)
|
|
assert r.returncode != 0
|
|
assert "AGENTS_API_KEY" in r.stderr, f"stderr=[{r.stderr!r}]"
|
|
|
|
|
|
def test_connect_fails_on_bad_host():
|
|
"""Transport-level failure on unresolvable host."""
|
|
_apikey()
|
|
r = _run_connect_test("https://this-host-does-not-exist-xyzzy.invalid")
|
|
assert r.returncode != 0
|
|
assert "FAIL" in r.stderr, f"stderr=[{r.stderr!r}]"
|
|
|
|
|
|
def test_count_matches_direct_curl():
|
|
"""N agents returned by the .exe must equal what /agents returns via curl."""
|
|
apikey = _apikey()
|
|
# Direct curl from WSL via Windows curl.exe (matches what fn_http uses).
|
|
curl_bin = shutil.which("curl.exe") or "/mnt/c/Windows/System32/curl.exe"
|
|
direct = subprocess.run(
|
|
[
|
|
curl_bin, "-fsS",
|
|
"-H", f"Authorization: Bearer {apikey}",
|
|
f"{_url()}/agents",
|
|
],
|
|
capture_output=True, text=True, timeout=15,
|
|
)
|
|
assert direct.returncode == 0, f"direct curl failed: {direct.stderr}"
|
|
expected = len(json.loads(direct.stdout))
|
|
|
|
r = _run_connect_test(_url())
|
|
assert r.returncode == 0
|
|
got = int(r.stdout.strip().split()[1])
|
|
assert got == expected, f"exe says {got} agents, curl says {expected}"
|
|
|
|
|
|
def test_url_trim_robust_to_whitespace():
|
|
"""URL with leading/trailing whitespace + CR/LF must still connect."""
|
|
_apikey()
|
|
# Wrap URL with whitespace + CRLF; trim_url() in main.cpp must strip.
|
|
url = " \r\n " + _url() + " \r\n\t"
|
|
r = _run_connect_test(url)
|
|
assert r.returncode == 0, f"trim failed: stderr={r.stderr!r}"
|
|
assert r.stdout.startswith("OK ")
|