#!/usr/bin/env python3 from __future__ import annotations import argparse import json import os import socket import subprocess import sys import time import urllib.error import urllib.request from pathlib import Path def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run a local HTTP smoke test for qonto-assistant.") parser.add_argument( "--python", default=sys.executable, help="Python interpreter used to start qonto_assistant.main", ) parser.add_argument( "--fixture-dir", default=str(Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "qonto"), help="Fixture directory containing organization.json and transactions.json", ) parser.add_argument( "--startup-timeout", type=float, default=15.0, help="Seconds to wait for the local API to start", ) return parser.parse_args() def free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind(("127.0.0.1", 0)) return int(sock.getsockname()[1]) def request_json(url: str, *, headers: dict[str, str] | None = None) -> dict[str, object]: request = urllib.request.Request(url, headers=headers or {}) with urllib.request.urlopen(request, timeout=5) as response: return json.load(response) def wait_for_health(base_url: str, *, timeout_seconds: float) -> dict[str, object]: deadline = time.monotonic() + timeout_seconds last_error: Exception | None = None while time.monotonic() < deadline: try: return request_json(f"{base_url}/v1/health") except Exception as exc: # noqa: BLE001 last_error = exc time.sleep(0.25) raise RuntimeError(f"Timed out waiting for {base_url}/v1/health: {last_error}") def main() -> int: args = parse_args() repo_root = Path(__file__).resolve().parents[1] port = free_port() base_url = f"http://127.0.0.1:{port}" env = os.environ.copy() env["PYTHONPATH"] = str(repo_root / "src") env["QONTO_ASSISTANT_HOST"] = "127.0.0.1" env["QONTO_ASSISTANT_PORT"] = str(port) env["QONTO_FIXTURE_DIR"] = str(Path(args.fixture_dir).resolve()) process = subprocess.Popen( # noqa: S603 [args.python, "-m", "qonto_assistant.main"], cwd=repo_root, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, ) try: health = wait_for_health(base_url, timeout_seconds=args.startup_timeout) headers = {"X-Actor-ID": "smoke", "X-Tenant-ID": "binky"} accounts = request_json(f"{base_url}/v1/accounts", headers=headers) snapshot_recent = request_json( f"{base_url}/v1/snapshot?window_days=31&page_size=50", headers=headers, ) snapshot_cost = request_json( f"{base_url}/v1/snapshot?window_days=90&page_size=50", headers=headers, ) assert health["status"] == "ok" assert accounts["organization"]["name"] == "Binky Hedgehog GmbH" assert accounts["accounts"][0]["iban_last4"] == "6810" assert snapshot_recent["cost_run_rate_hints"]["recurring_debits"] == [] assert snapshot_cost["cost_run_rate_hints"]["recurring_debits"][0]["label"] == "HUB31" print( json.dumps( { "health": health, "accounts": accounts, "snapshot_recent": snapshot_recent, "snapshot_cost": snapshot_cost, }, indent=2, ) ) return 0 finally: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() process.wait(timeout=5) if process.stdout is not None: output = process.stdout.read().strip() if output: sys.stderr.write(output + "\n") if __name__ == "__main__": raise SystemExit(main())