qonto-assistant/scripts/smoke_rest_api.py

128 lines
4 KiB
Python
Raw Normal View History

#!/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())