"""Simple smoke test for Jarvis Cognitive API. Run with: python tests/smoke_api.py The script starts the FastAPI server, checks the health endpoint, performs a sample /ask call, and reports pass/fail to stdout. """ from __future__ import annotations import json import os import signal import subprocess import sys import time from contextlib import contextmanager from pathlib import Path import requests API_BASE_URL = os.environ.get("JARVIS_API_BASE_URL", "http://127.0.0.1:8000") HEALTH_ENDPOINT = f"{API_BASE_URL}/" ASK_ENDPOINT = f"{API_BASE_URL}/ask" READY_TIMEOUT_SECONDS = 30 POLL_INTERVAL_SECONDS = 1 def ensure_port_free() -> None: try: response = requests.get(HEALTH_ENDPOINT, timeout=2) except requests.RequestException: return msg = ( "An API server already responded at " f"{HEALTH_ENDPOINT}. Stop it before running the smoke test." ) raise RuntimeError(msg) def _venv_python(project_root: Path) -> str | None: # Prefer repository local venv if present win = os.name == "nt" cand = project_root / ".venv" / ("Scripts" if win else "bin") / ("python.exe" if win else "python") return str(cand) if cand.exists() else None @contextmanager def launch_api_server(): project_root = Path(__file__).resolve().parents[1] api_script = project_root / "api" / "api_server.py" env = os.environ.copy() py = _venv_python(project_root) or sys.executable process = subprocess.Popen( [py, str(api_script)], cwd=str(project_root), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) try: yield process finally: if process.poll() is None: if os.name == "nt": process.terminate() else: process.send_signal(signal.SIGTERM) try: process.wait(timeout=10) except subprocess.TimeoutExpired: process.kill() def wait_for_health(timeout: int = READY_TIMEOUT_SECONDS) -> None: deadline = time.time() + timeout while time.time() < deadline: try: response = requests.get(HEALTH_ENDPOINT, timeout=2) if response.status_code == 200: return except requests.RequestException: pass time.sleep(POLL_INTERVAL_SECONDS) raise TimeoutError( f"API did not become ready within {timeout} seconds at {HEALTH_ENDPOINT}." ) def run_smoke_test() -> None: ensure_port_free() with launch_api_server() as process: wait_for_health() health = requests.get(HEALTH_ENDPOINT, timeout=5) payload = {"query": "Cos'e la virtu stoica?", "session_id": "smoke_test"} answer_response = requests.post(ASK_ENDPOINT, json=payload, timeout=60) if health.status_code != 200: raise AssertionError(f"Health check failed: status {health.status_code}") try: data = answer_response.json() except json.JSONDecodeError as exc: raise AssertionError("/ask did not return JSON") from exc answer_text = data.get("answer", "") if not answer_text.strip(): raise AssertionError("/ask returned empty answer") if process.poll() is not None: raise AssertionError("API server exited unexpectedly during the test") print("Smoke test passed ") if __name__ == "__main__": try: run_smoke_test() except Exception as exc: # noqa: BLE001 print(f"Smoke test failed: {exc}", file=sys.stderr) sys.exit(1)