5c8c13891f
Build and deploy / build-deploy (push) Has been cancelled
- миграция checks: nullable input_tokens/output_tokens/total_tokens/usage_json (идемпотентно, старые строки остаются NULL; probe мигрирует сам — cron стартует раньше uvicorn) - probe.py и /api/check фиксируют usage ДО валидации контента: токены сохраняются при пустом/сломанном ответе; таймауты и ответы без usage = NULL, не нули - usage_json хранит сырой usage провайдера (OpenAI/Anthropic/camelCase форматы) - /api/checks и сегментная история несут токены; новый /api/token_stats: rolling 24h + all time, покрытие known/unknown, разбивка по провайдерам (провайдер из models с fallback на префикс model_id) - UI: статы «Токены 24h» и «Покрытие usage» в шапке, токены в тултипах сегментов и логе проверок - статика через __file__-относительный путь (работает и в /app/app, и локально) - тесты (30, реальные HTTP против мока OmniRoute): миграция/идемпотентность, NULL при missing/zero usage, usage при упавшем контенте, таймаут, агрегаты, оба пути записи (probe.run + /api/check)
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""Реальный smoke: uvicorn + мок OmniRoute, проверка живых HTTP-запросов."""
|
|
import json
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from conftest import OmniMock
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TMP = os.path.join(REPO, ".smoke")
|
|
os.makedirs(TMP, exist_ok=True)
|
|
|
|
mock = OmniMock()
|
|
mock.set_completions(usage={"prompt_tokens": 9, "completion_tokens": 4, "total_tokens": 13})
|
|
|
|
env = dict(os.environ)
|
|
env.update({
|
|
"DB_PATH": os.path.join(TMP, "arena.db"),
|
|
"OMNIROUTE_URL": mock.url,
|
|
"OMNIROUTE_API_KEY": "smoke",
|
|
"ARENA_CACHE_TEXT": "/nonexistent/a.json",
|
|
"ARENA_CACHE_AGENT": "/nonexistent/b.json",
|
|
})
|
|
|
|
proc = subprocess.Popen(
|
|
[os.path.join(REPO, ".venv/bin/uvicorn"), "main:app", "--host", "127.0.0.1", "--port", "18700"],
|
|
cwd=os.path.join(REPO, "app"), env=env,
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
|
|
)
|
|
|
|
# pip install uvicorn into venv if missing handled outside
|
|
try:
|
|
base = "http://127.0.0.1:18700"
|
|
for _ in range(50):
|
|
try:
|
|
urllib.request.urlopen(f"{base}/api/providers", timeout=1)
|
|
break
|
|
except Exception:
|
|
time.sleep(0.2)
|
|
|
|
# 1. UI отдаётся (статика через __file__-путь)
|
|
ui = urllib.request.urlopen(f"{base}/", timeout=5).read().decode()
|
|
assert "Arena Dashboard" in ui and "s-tokens-24h" in ui and "loadTokenStats" in ui, "UI не содержит token-элементов"
|
|
|
|
# 2. Ручная проверка с usage
|
|
chk = json.loads(urllib.request.urlopen(f"{base}/api/check/zai/test-model-a", timeout=10).read())
|
|
assert chk["input_tokens"] == 9 and chk["total_tokens"] == 13, chk
|
|
|
|
# 3. Агрегат
|
|
ts = json.loads(urllib.request.urlopen(f"{base}/api/token_stats", timeout=5).read())
|
|
assert ts["window_24h"]["known"] >= 1 and ts["window_24h"]["total_tokens"] >= 13, ts
|
|
print("SMOKE_OK", json.dumps({
|
|
"check_usage": [chk["input_tokens"], chk["output_tokens"], chk["total_tokens"]],
|
|
"token_stats_24h": ts["window_24h"],
|
|
"providers": [(p["provider"], p["window_24h"]["total_tokens"]) for p in ts["providers"]],
|
|
}, ensure_ascii=False))
|
|
finally:
|
|
proc.send_signal(signal.SIGTERM)
|
|
try:
|
|
out, _ = proc.communicate(timeout=10)
|
|
print("--- uvicorn log tail ---")
|
|
print("\n".join(out.splitlines()[-8:]))
|
|
except subprocess.TimeoutExpired:
|
|
proc.kill()
|
|
mock.close()
|