Files
call-center/scripts/live_smoke_gate12.py
T

252 lines
8.2 KiB
Python

from __future__ import annotations
import argparse
import asyncio
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
from typing import Any
import httpx
ROOT = Path(__file__).resolve().parents[1]
DATA_ROOT = ROOT / ".data_smoke"
SERVICE_SPECS = [
{"name": "auth", "module": "services.auth_service.app:app", "port": 18001},
{"name": "audit", "module": "services.audit_service.app:app", "port": 18002},
{"name": "customer", "module": "services.customer_service.app:app", "port": 18003},
{"name": "interaction", "module": "services.interaction_service.app:app", "port": 18004},
{"name": "routing", "module": "services.routing_service.app:app", "port": 18005},
{"name": "voice", "module": "services.voice_adapter_service.app:app", "port": 18006},
{"name": "telegram", "module": "services.telegram_adapter_service.app:app", "port": 18007},
{"name": "kb", "module": "services.kb_service.app:app", "port": 18008},
{"name": "reporting", "module": "services.reporting_service.app:app", "port": 18009},
{"name": "supervisor", "module": "services.supervisor_service.app:app", "port": 18010},
{"name": "gateway", "module": "gateway.app:app", "port": 18080},
]
async def wait_for_health(base_url: str, retries: int = 80, delay: float = 0.25) -> None:
async with httpx.AsyncClient(timeout=2) as client:
for _ in range(retries):
try:
response = await client.get(f"{base_url}/health")
if response.status_code == 200:
return
except Exception:
pass
await asyncio.sleep(delay)
raise RuntimeError(f"Service not healthy: {base_url}/health")
def start_services(database_url: str | None, data_dir: Path) -> list[subprocess.Popen]:
data_dir.mkdir(parents=True, exist_ok=True)
processes: list[subprocess.Popen] = []
for spec in SERVICE_SPECS:
env = os.environ.copy()
env["CC_DATA_DIR"] = str(data_dir)
if database_url:
env["DATABASE_URL"] = database_url
if spec["name"] == "gateway":
env["AUTH_SERVICE_URL"] = "http://127.0.0.1:18001"
env["AUDIT_SERVICE_URL"] = "http://127.0.0.1:18002"
env["CUSTOMER_SERVICE_URL"] = "http://127.0.0.1:18003"
env["INTERACTION_SERVICE_URL"] = "http://127.0.0.1:18004"
env["ROUTING_SERVICE_URL"] = "http://127.0.0.1:18005"
env["VOICE_ADAPTER_SERVICE_URL"] = "http://127.0.0.1:18006"
env["TELEGRAM_ADAPTER_SERVICE_URL"] = "http://127.0.0.1:18007"
env["KB_SERVICE_URL"] = "http://127.0.0.1:18008"
env["REPORTING_SERVICE_URL"] = "http://127.0.0.1:18009"
env["SUPERVISOR_SERVICE_URL"] = "http://127.0.0.1:18010"
cmd = [
sys.executable,
"-m",
"uvicorn",
spec["module"],
"--host",
"127.0.0.1",
"--port",
str(spec["port"]),
]
proc = subprocess.Popen(
cmd,
cwd=str(ROOT),
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
processes.append(proc)
return processes
def stop_services(processes: list[subprocess.Popen]) -> None:
for proc in processes:
if proc.poll() is not None:
continue
try:
proc.terminate()
except Exception:
pass
for proc in processes:
try:
proc.wait(timeout=2)
except Exception:
try:
proc.kill()
except Exception:
pass
async def run_gate_checks() -> dict[str, Any]:
for spec in SERVICE_SPECS:
await wait_for_health(f"http://127.0.0.1:{spec['port']}")
base = "http://127.0.0.1:18080"
admin = {"X-User": "admin", "X-Role": "admin"}
operator = {"X-User": "operator", "X-Role": "operator"}
supervisor = {"X-User": "supervisor", "X-Role": "supervisor"}
async with httpx.AsyncClient(base_url=base, timeout=10) as client:
health = await client.get("/health")
health.raise_for_status()
login = await client.post("/proxy/auth/auth/login", json={"username": "admin", "password": "admin123"})
login.raise_for_status()
queue = await client.post(
"/proxy/routing/queues",
headers=admin,
json={
"name": "Main Queue",
"description": "Smoke queue",
"rules": [
{"channel": "voice", "priority": 3, "strategy": "round_robin", "sla_seconds": 30},
{"channel": "telegram", "priority": 3, "strategy": "round_robin", "sla_seconds": 45},
],
},
)
queue.raise_for_status()
queue_id = queue.json()["queue_id"]
customer = await client.post(
"/proxy/customer/customers",
json={
"display_name": "Gate User",
"phones": ["+77010000011"],
"preferred_phone": "+77010000011",
"tags": ["smoke"],
},
)
customer.raise_for_status()
customer_id = customer.json()["customer_id"]
interaction = await client.post(
"/proxy/interaction/interactions",
headers=operator,
json={
"channel": "voice",
"subject": "Gate voice check",
"customer_id": customer_id,
"queue_id": queue_id,
"priority": 3,
},
)
interaction.raise_for_status()
interaction_id = interaction.json()["interaction_id"]
assign = await client.patch(
f"/proxy/interaction/interactions/{interaction_id}/assign",
headers=supervisor,
json={"assignee": "operator_a"},
)
assign.raise_for_status()
escalate = await client.post(
f"/proxy/interaction/interactions/{interaction_id}/escalate",
headers=operator,
json={"target_queue_id": "line2"},
)
escalate.raise_for_status()
close = await client.patch(
f"/proxy/interaction/interactions/{interaction_id}/status",
headers=operator,
json={"status": "closed"},
)
close.raise_for_status()
voice_event = await client.post(
"/proxy/voice/integrations/voice/events",
headers=operator,
json={
"event_type": "call.started",
"call_id": "smoke_call_1",
"interaction_id": interaction_id,
"payload": {"source": "live_smoke"},
},
)
voice_event.raise_for_status()
tg_event = await client.post(
"/proxy/telegram/integrations/telegram/webhook",
json={
"chat_id": "smoke_chat",
"text": "Smoke message",
"customer_external_id": None,
"payload": {"source": "live_smoke"},
},
)
tg_event.raise_for_status()
interactions = await client.get("/proxy/interaction/interactions")
interactions.raise_for_status()
rows = interactions.json()
return {
"gateway": health.json(),
"queue_id": queue_id,
"customer_id": customer_id,
"interaction_id": interaction_id,
"interactions_total": len(rows),
}
async def main() -> None:
parser = argparse.ArgumentParser(description="Live Gate 1/2 smoke")
parser.add_argument(
"--database-url",
default=os.getenv("DATABASE_URL", ""),
help="Optional DB URL override for started services",
)
args = parser.parse_args()
db_url = args.database_url.strip() or None
run_data_dir = DATA_ROOT / f"run_{int(time.time() * 1000)}"
processes = start_services(db_url, run_data_dir)
try:
result = await run_gate_checks()
print("Gate 1/2 live smoke passed")
print(result)
if db_url:
print(f"DB mode: {db_url}")
finally:
stop_services(processes)
try:
shutil.rmtree(run_data_dir)
except Exception:
pass
if __name__ == "__main__":
asyncio.run(main())