128 lines
4 KiB
Python
128 lines
4 KiB
Python
|
|
#!/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())
|