"""Shared e2e harness: free ports, health waits, lfx spawn, migrate, sessions. Single place for logic duplicated between tests/conftest.py and scripts/smoke_e2e.py (~60 lines). Full real-LFX runs are kept. """ from __future__ import annotations import os import shutil import socket import subprocess import sys import time import urllib.request from pathlib import Path def free_port() -> int: sock = socket.socket() sock.bind(("127.0.0.1", 0)) port = sock.getsockname()[1] sock.close() return port def wait_healthy(url: str, deadline_s: float = 240.0) -> None: deadline = time.time() + deadline_s while time.time() < deadline: try: with urllib.request.urlopen(url + "/health", timeout=3) as response: if response.status == 200: return except OSError: time.sleep(1) raise RuntimeError(f"server did not become healthy: {url}") def spawn_lfx(flow_dir: Path, port: int) -> subprocess.Popen: bindir = Path(sys.executable).parent lfx_bin = shutil.which("lfx", path=str(bindir)) or "lfx" env = {**os.environ, "LANGFLOW_API_KEY": "", "LFX_API_KEY": "test-lfx-key"} return subprocess.Popen( [ lfx_bin, "serve", "--host", "127.0.0.1", "--port", str(port), "--flow-dir", str(flow_dir), str(flow_dir), ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env, ) def migrate_throwaway(backend_dir: Path) -> None: from alembic import command as alembic_command from alembic.config import Config cfg = Config() cfg.set_main_option("script_location", str(backend_dir / "migrations")) alembic_command.upgrade(cfg, "head")