mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-07-30 15:26:14 +00:00
246 lines
6.8 KiB
Python
Executable File
246 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Integration test runner script.
|
|
|
|
This script:
|
|
1. Starts fresh Docker containers using compose.yml
|
|
2. Waits for services to be ready
|
|
3. Runs integration tests
|
|
4. Cleans up containers afterward
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
from rich.console import Console
|
|
|
|
PROJECT_ROOT = Path(__file__).parent.parent
|
|
COMPOSE_FILE = PROJECT_ROOT / "compose.testing.yml"
|
|
|
|
console = Console()
|
|
|
|
|
|
def log(message: str, style: str = "") -> None:
|
|
"""Print styled log message."""
|
|
console.print(message, style=style)
|
|
|
|
|
|
def run_command(
|
|
cmd: list[str], check: bool = True, capture_output: bool = False
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run a command and return the result."""
|
|
log(f"Running: {' '.join(cmd)}", "cyan")
|
|
return subprocess.run(
|
|
cmd, check=check, capture_output=capture_output, text=True, cwd=PROJECT_ROOT
|
|
)
|
|
|
|
|
|
async def wait_for_service(
|
|
url: str, service_name: str, endpoint: str = "", timeout: int = 60
|
|
) -> bool:
|
|
"""Wait for a service to be ready."""
|
|
log(f"Waiting for {service_name} at {url}...", "yellow")
|
|
|
|
start_time = time.time()
|
|
async with httpx.AsyncClient() as client:
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
full_url = f"{url}{endpoint}" if endpoint else url
|
|
response = await client.get(full_url, timeout=5.0)
|
|
if response.status_code == 200:
|
|
log(f"✅ {service_name} ready", "green")
|
|
return True
|
|
except Exception:
|
|
pass
|
|
|
|
await asyncio.sleep(2)
|
|
|
|
log(f"❌ {service_name} at {url} not ready after {timeout}s", "red")
|
|
return False
|
|
|
|
|
|
async def wait_for_mint(url: str, timeout: int = 60) -> bool:
|
|
"""Wait for mint to be ready."""
|
|
return await wait_for_service(url, "Cashu Mint", "/v1/info", timeout)
|
|
|
|
|
|
def cleanup_docker() -> None:
|
|
"""Clean up Docker containers and volumes."""
|
|
log("🧹 Cleaning up Docker containers and volumes...", "yellow")
|
|
|
|
try:
|
|
# Stop and remove containers
|
|
run_command(
|
|
["docker-compose", "-f", str(COMPOSE_FILE), "down", "-v"], check=False
|
|
)
|
|
|
|
# Remove any orphaned containers
|
|
run_command(["docker", "container", "prune", "-f"], check=False)
|
|
|
|
# Remove unused volumes (be careful with this)
|
|
run_command(["docker", "volume", "prune", "-f"], check=False)
|
|
|
|
log("✅ Docker cleanup completed", "green")
|
|
except Exception as e:
|
|
log(f"⚠️ Docker cleanup failed: {e}", "yellow")
|
|
|
|
|
|
def start_services() -> None:
|
|
"""Start Docker services with fresh state."""
|
|
log("🚀 Starting Docker services...", "blue")
|
|
|
|
# Ensure we start with clean state
|
|
cleanup_docker()
|
|
|
|
# Start services
|
|
run_command(
|
|
[
|
|
"docker-compose",
|
|
"-f",
|
|
str(COMPOSE_FILE),
|
|
"up",
|
|
"-d",
|
|
"--force-recreate", # Recreate containers even if config hasn't changed
|
|
"--renew-anon-volumes", # Recreate anonymous volumes
|
|
]
|
|
)
|
|
|
|
log("✅ Docker services started", "green")
|
|
|
|
|
|
def run_tests() -> bool:
|
|
"""Run the integration tests."""
|
|
log("🧪 Running integration tests...", "blue")
|
|
|
|
env = os.environ.copy()
|
|
env["RUN_INTEGRATION_TESTS"] = "1"
|
|
env["USE_LOCAL_SERVICES"] = "1" # Use local Docker services
|
|
|
|
# Run only integration tests
|
|
cmd = [
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"tests/integration/",
|
|
"-v",
|
|
"--tb=short",
|
|
"--color=yes",
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(cmd, env=env, cwd=PROJECT_ROOT)
|
|
if result.returncode == 0:
|
|
log("✅ Integration tests passed", "green")
|
|
return True
|
|
else:
|
|
log("❌ Integration tests failed", "red")
|
|
return False
|
|
except Exception as e:
|
|
log(f"❌ Failed to run tests: {e}", "red")
|
|
return False
|
|
|
|
|
|
def check_dependencies() -> bool:
|
|
"""Check that required dependencies are available."""
|
|
log("🔍 Checking dependencies...", "blue")
|
|
|
|
# Check Docker
|
|
try:
|
|
run_command(["docker", "--version"], capture_output=True)
|
|
log("✅ Docker found", "green")
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
log("❌ Docker not found. Please install Docker.", "red")
|
|
return False
|
|
|
|
# Check Docker Compose
|
|
try:
|
|
run_command(["docker-compose", "--version"], capture_output=True)
|
|
log("✅ Docker Compose found", "green")
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
log("❌ Docker Compose not found. Please install Docker Compose.", "red")
|
|
return False
|
|
|
|
# Check pytest
|
|
try:
|
|
run_command([sys.executable, "-m", "pytest", "--version"], capture_output=True)
|
|
log("✅ pytest found", "green")
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
log("❌ pytest not found. Please install pytest.", "red")
|
|
return False
|
|
|
|
# Check compose file exists
|
|
if not COMPOSE_FILE.exists():
|
|
log(f"❌ Compose file not found: {COMPOSE_FILE}", "red")
|
|
return False
|
|
else:
|
|
log("✅ Compose file found", "green")
|
|
|
|
return True
|
|
|
|
|
|
async def main() -> int:
|
|
"""Main function."""
|
|
log("🎯 Starting integration test runner", "bold blue")
|
|
|
|
try:
|
|
# Check dependencies
|
|
if not check_dependencies():
|
|
sys.exit(1)
|
|
|
|
# Start services
|
|
start_services()
|
|
|
|
# Wait for services to be ready
|
|
services_ready = await asyncio.gather(
|
|
wait_for_mint("http://localhost:3338"),
|
|
wait_for_service("http://localhost:3000", "Mock OpenAI", "/"),
|
|
wait_for_service("http://localhost:8000", "Router", "/"),
|
|
return_exceptions=True,
|
|
)
|
|
|
|
if not all(services_ready):
|
|
failed_services = [
|
|
service
|
|
for service, ready in zip(
|
|
["Mint", "Mock OpenAI", "Router"], services_ready
|
|
)
|
|
if not ready
|
|
]
|
|
raise RuntimeError(
|
|
f"Services failed to start: {', '.join(failed_services)}"
|
|
)
|
|
|
|
# Run tests
|
|
success = run_tests()
|
|
|
|
if success:
|
|
log(
|
|
"🎉 Integration tests completed successfully!",
|
|
"bold green",
|
|
)
|
|
return 0
|
|
else:
|
|
log("💥 Integration tests failed!", "bold red")
|
|
return 1
|
|
|
|
except KeyboardInterrupt:
|
|
log("⏹️ Interrupted by user", "yellow")
|
|
return 1
|
|
|
|
except Exception as e:
|
|
log(f"💥 Unexpected error: {e}", "red")
|
|
return 1
|
|
|
|
finally:
|
|
# Always cleanup
|
|
cleanup_docker()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit_code = asyncio.run(main())
|
|
sys.exit(exit_code)
|