agnes-the-ai-analyst/tests/test_docker_full.py
ZdenekSrotyr f348296685 fix(tests): align docker-e2e health asserts with current /api/health shape
`/api/health` is the auth-free LB probe — returns `status` + `db_schema`
only. `version` lives in `/api/version` and the richer
`services.duckdb_state` lives in `/api/health/detailed` (auth-gated).
The two e2e asserts had drifted and broke nightly on main.
2026-05-03 11:21:19 +02:00

99 lines
3.4 KiB
Python

"""Docker E2E tests — requires a running docker-compose stack.
Run with: pytest tests/test_docker_full.py -m docker -v
Assumes docker compose is already up and healthy at DOCKER_TEST_URL.
"""
import os
import time
import httpx
import pytest
pytestmark = pytest.mark.docker
DOCKER_BASE_URL = os.environ.get("DOCKER_TEST_URL", "http://localhost:8000")
def _wait_for_healthy(url: str, timeout: int = 60) -> bool:
"""Poll GET /api/health until 200 or timeout."""
deadline = time.time() + timeout
while time.time() < deadline:
try:
resp = httpx.get(f"{url}/api/health", timeout=5)
if resp.status_code == 200:
return True
except Exception:
pass
time.sleep(1)
return False
@pytest.fixture(scope="module", autouse=True)
def require_docker():
"""Wait for the docker stack to be healthy before running tests."""
if not _wait_for_healthy(DOCKER_BASE_URL, timeout=60):
pytest.skip(
f"Docker stack at {DOCKER_BASE_URL} did not become healthy within 60s. "
"Start it with: docker compose up"
)
def test_app_health():
"""/api/health is the auth-free LB probe — status + db_schema only.
Version metadata moved to /api/version (see app/api/health.py)."""
resp = httpx.get(f"{DOCKER_BASE_URL}/api/health", timeout=10)
assert resp.status_code == 200
data = resp.json()
assert data.get("status") == "ok"
assert data.get("db_schema") == "ok"
ver = httpx.get(f"{DOCKER_BASE_URL}/api/version", timeout=10)
assert ver.status_code == 200
assert "version" in ver.json()
def test_app_returns_html_on_root():
"""GET / redirects unauthenticated callers — / always 302s to /login or /dashboard."""
resp = httpx.get(f"{DOCKER_BASE_URL}/", timeout=10, follow_redirects=False)
assert resp.status_code == 302
assert resp.headers.get("location") in ("/login", "/dashboard")
def test_bootstrap_creates_admin():
"""POST /auth/bootstrap creates the first admin user (409 if already done)."""
resp = httpx.post(
f"{DOCKER_BASE_URL}/auth/bootstrap",
json={"email": "admin@docker-test.local", "name": "Docker Admin", "password": "test1234"},
timeout=10,
)
# 200 = created, 409 = already bootstrapped — both are valid
assert resp.status_code in (200, 409)
def test_trigger_sync():
"""Login then POST /api/sync/trigger returns accepted."""
# First bootstrap or login to get a token
bootstrap = httpx.post(
f"{DOCKER_BASE_URL}/auth/bootstrap",
json={"email": "admin@docker-test.local", "name": "Docker Admin", "password": "test1234"},
timeout=10,
)
if bootstrap.status_code == 200:
token = bootstrap.json()["access_token"]
else:
# Already bootstrapped — log in
login = httpx.post(
f"{DOCKER_BASE_URL}/auth/token",
json={"email": "admin@docker-test.local", "password": "test1234"},
timeout=10,
)
if login.status_code != 200:
pytest.skip("Cannot obtain admin token — adjust credentials or bootstrap the stack")
token = login.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
resp = httpx.post(f"{DOCKER_BASE_URL}/api/sync/trigger", headers=headers, timeout=15)
# 200 = started, 202 = accepted/queued
assert resp.status_code in (200, 202)