Files
polygon/tests/compare_test.py
T

262 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""
compare_test.py — Polygon ↔ Nubes API (v2, трёхкатегорийная классификация).
Сравнивает ТОЛЬКО сервисы полигона (из терраформа) с реальным API.
Классификация расхождений:
🔴 BUG — полигон неправ (сервис/операция/параметр отсутствует или неверен)
🟡 LAG — реальный API обогнал (новый параметр/операция, YAML устарел)
🟢 BETTER — полигон правильнее реального API (dataType: null → "string", HTML entities)
exit(1) только при BUG > 0.
"""
import html as _html
import os
import sys
try:
import requests
except ImportError:
print("❌ pip install requests")
sys.exit(1)
TOKENS_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "secrets")
UA = "Mozilla/5.0"
TIMEOUT = 15
REAL = {
"dev": "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc",
"test": "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc",
"prod": "https://lk-api-gateway.ngcloud.ru/api/v1/svc",
}
POLYGON = {
"dev": "https://polygon.pythonk8s.dev.nubes.ru/dev/api/v1/svc",
"test": "https://polygon.pythonk8s.dev.nubes.ru/test/api/v1/svc",
"prod": "https://polygon.pythonk8s.dev.nubes.ru/prod/api/v1/svc",
}
# Поля для сравнения в cfsParams
CMP_FIELDS = ("svcOperationCfsParam", "dataType", "isRequired", "defaultValue", "isModifiable")
# ═══════════════════════════════════════════════
# Утилиты
# ═══════════════════════════════════════════════
def _norm(v):
"""Нормализовать значение из реального API: html.unescape для строк."""
if isinstance(v, str):
return _html.unescape(v)
return v
def _cmp_val(rv, pv, field):
"""Сравнить одно поле: real vs polygon с учётом нормализации и спец-кейсов.
Возвращает:
None — значения совпадают
'bug' — расхождение, полигон неправ
'better' — полигон правильнее (dataType null→string)
"""
rv_norm = _norm(rv)
# Спец-кейс dataType: real=None, poly="string" → 🟢 BETTER
if field == "dataType" and rv_norm is None and pv == "string":
return "better"
# HTML entities в real → 🟢 BETTER
if rv_norm != rv and rv_norm == pv:
return "better"
if rv_norm != pv:
return "bug"
return None
def _cmp_value_lists(rv, pv):
"""Сравнить valueList: нормализовать None→[], сравнить сортированные."""
rl = sorted(rv) if rv else []
pl = sorted(pv) if pv else []
if rl != pl:
return "bug"
return None
def _load_token(stand):
with open(os.path.join(TOKENS_DIR, f"{stand}.token")) as f:
return f.read().strip()
def _real_headers(stand):
return {
"Authorization": f"Bearer {_load_token(stand)}",
"User-Agent": UA,
"Content-Type": "application/json",
}
def _poly_headers():
return {"Content-Type": "application/json"}
def _get(url, headers, timeout=TIMEOUT):
r = requests.get(url, headers=headers, timeout=timeout)
assert r.status_code == 200, f"{r.status_code}: {r.text[:200]}"
return r.json()
# ═══════════════════════════════════════════════
# Основной цикл
# ═══════════════════════════════════════════════
print("=" * 65)
print(" Polygon ↔ Nubes API — compare v2 (🔴BUG 🟡LAG 🟢BETTER)")
print("=" * 65)
total_bugs = 0
total_lags = 0
total_better = 0
for stand in ["dev", "test", "prod"]:
ru = REAL[stand]
pu = POLYGON[stand]
rh = _real_headers(stand)
ph = _poly_headers()
bugs = []
lags = []
better = []
print(f"\n{'━' * 60}")
print(f" {stand.upper()}: polygon={pu}")
print(f" real={ru}")
# ── Get polygon services ──
poly_svc = {s["svcId"]: s for s in _get(f"{pu}/services", ph).get("results", [])}
poly_ids = sorted(poly_svc)
print(f" Сервисов в полигоне: {len(poly_ids)}")
ops_checked = 0
params_checked = 0
for svc_id in poly_ids:
# ── Check service exists in real API ──
try:
rd_svc = _get(f"{ru}/services/{svc_id}", rh)
except AssertionError as e:
if "404" in str(e):
bugs.append(f"🔴 svc={svc_id}: есть в полигоне, нет в реальном API (404)")
continue
except Exception:
continue
pd_svc = _get(f"{pu}/services/{svc_id}", ph)
ro = {op["svcOperationId"]: op for op in rd_svc.get("svc", {}).get("operations", [])}
po = {op["svcOperationId"]: op for op in pd_svc.get("svc", {}).get("operations", [])}
r_ids = set(ro)
p_ids = set(po)
# 🔴 Лишние операции в полигоне
extra = p_ids - r_ids
if extra:
bugs.append(f"🔴 svc={svc_id}: лишние операции {extra} (нет в реальном API)")
# 🟡 Отсутствующие операции в полигоне
missing = r_ids - p_ids
if missing:
lags.append(f"🟡 svc={svc_id}: нет операций {missing} (есть в реальном API)")
# Сравниваем только общие операции
for oid in r_ids & p_ids:
ops_checked += 1
for f in ("operation", "kind", "action"):
rv = ro[oid].get(f)
pv = po[oid].get(f)
if rv != pv:
bugs.append(f"🔴 svc={svc_id} op={oid} {f}: '{rv}' ≠ '{pv}'")
# ── cfsParams ──
try:
rd_par = _get(f"{ru}/instanceOperations/default/{oid}", rh)
except Exception:
continue
pd_par = _get(f"{pu}/instanceOperations/default/{oid}", ph)
rp = {p["svcOperationCfsParamId"]: p for p in rd_par.get("svcOperation", {}).get("cfsParams", [])}
pp = {p["svcOperationCfsParamId"]: p for p in pd_par.get("svcOperation", {}).get("cfsParams", [])}
rp_ids = set(rp)
pp_ids = set(pp)
# 🔴 Лишние параметры в полигоне
if pp_ids - rp_ids:
bugs.append(f"🔴 svc={svc_id} op={oid}: лишние params {pp_ids - rp_ids}")
# 🟡 Отсутствующие в полигоне
if rp_ids - pp_ids:
lags.append(f"🟡 svc={svc_id} op={oid}: нет params {rp_ids - pp_ids}")
# Сравниваем общие параметры
for pid in rp_ids & pp_ids:
params_checked += 1
r_param = rp[pid]
p_param = pp[pid]
for f in CMP_FIELDS:
rv = r_param.get(f)
pv = p_param.get(f)
result = _cmp_val(rv, pv, f)
if result == "bug":
bugs.append(f"🔴 svc={svc_id} op={oid} param={pid} {f}: '{rv}' ≠ '{pv}'")
elif result == "better":
better.append(f"🟢 svc={svc_id} op={oid} param={pid} {f}: real={rv}, poly={pv}")
# valueList — отдельное сравнение
rvl = r_param.get("valueList")
pvl = p_param.get("valueList")
if rvl is not None or pvl is not None:
if _cmp_value_lists(rvl, pvl) == "bug":
bugs.append(f"🔴 svc={svc_id} op={oid} param={pid} valueList: {rvl}{pvl}")
# ── Report ──
print(f" Проверено: {ops_checked} операций, {params_checked} параметров")
print(f" 🔴 BUGS: {len(bugs)}")
print(f" 🟡 LAGS: {len(lags)}")
print(f" 🟢 BETTER: {len(better)}")
for b in bugs[:8]:
print(f" {b}")
if len(bugs) > 8:
print(f" ... +{len(bugs)-8}")
for l in lags[:5]:
print(f" {l}")
if len(lags) > 5:
print(f" ... +{len(lags)-5}")
for b in better[:3]:
print(f" {b}")
if len(better) > 3:
print(f" ... +{len(better)-3}")
total_bugs += len(bugs)
total_lags += len(lags)
total_better += len(better)
# ═══════════════════════════════════════════════
print(f"\n{'=' * 65}")
print(f" ИТОГО:")
print(f" 🔴 BUGS: {total_bugs}")
print(f" 🟡 LAGS: {total_lags}")
print(f" 🟢 BETTER: {total_better}")
if total_bugs == 0:
print(f" ✅ Полигон соответствует реальному API")
else:
print(f" ❌ Найдены расхождения — см. выше")
print(f"{'=' * 65}")
sys.exit(1 if total_bugs > 0 else 0)