Spaces:
Running
Running
sync: 155 file da Baida98/AI@1db7f191 (2026-08-15 12:08 UTC) [deploy-all]
Browse files- api/telegram_webhook.py +96 -195
- tests/test_telegram_benchmark_live.py +83 -0
api/telegram_webhook.py
CHANGED
|
@@ -1588,209 +1588,110 @@ async def _cmd_score(chat_id: int) -> None:
|
|
| 1588 |
await _tg_reply(chat_id, det[_TG_MAX:_TG_MAX*2][:_TG_MAX], keyboard=_BENCH_ACTION_KB)
|
| 1589 |
|
| 1590 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1591 |
async def _cmd_bench(chat_id: int, mode: str = "default") -> None:
|
| 1592 |
-
"""
|
| 1593 |
|
| 1594 |
-
|
| 1595 |
-
|
| 1596 |
-
|
|
|
|
| 1597 |
"""
|
| 1598 |
-
|
| 1599 |
-
|
| 1600 |
-
|
| 1601 |
-
|
| 1602 |
-
|
| 1603 |
-
|
| 1604 |
-
|
| 1605 |
-
|
| 1606 |
-
|
| 1607 |
-
"https://api.github.com/repos/Baida98/AI/actions/workflows/bench.yml/runs"
|
| 1608 |
-
"?status=completed&per_page=1",
|
| 1609 |
-
headers={"Authorization": f"Bearer {gh_token}",
|
| 1610 |
-
"Accept": "application/vnd.github.v3+json",
|
| 1611 |
-
"User-Agent": "AgenteAI-Bot"},
|
| 1612 |
-
)
|
| 1613 |
-
if _r.status_code == 200:
|
| 1614 |
-
_runs = _r.json().get("workflow_runs", [])
|
| 1615 |
-
if _runs:
|
| 1616 |
-
last_report = {
|
| 1617 |
-
"run_id": _runs[0]["id"],
|
| 1618 |
-
"run_url": _runs[0]["html_url"],
|
| 1619 |
-
"conclusion":_runs[0].get("conclusion","?"),
|
| 1620 |
-
"updated": _runs[0].get("updated_at",""),
|
| 1621 |
-
}
|
| 1622 |
-
except Exception as _exc:
|
| 1623 |
-
_logger.debug("bench fetch last run: %s", _exc)
|
| 1624 |
-
|
| 1625 |
-
# ── Trigger nuovo run via workflow_dispatch ───────────────────────────────
|
| 1626 |
-
run_url = "https://github.com/Baida98/AI/actions/workflows/bench.yml"
|
| 1627 |
-
if gh_token:
|
| 1628 |
-
try:
|
| 1629 |
-
import httpx as _hx
|
| 1630 |
-
async with _hx.AsyncClient(timeout=10.0) as _c:
|
| 1631 |
-
_r = await _c.post(
|
| 1632 |
-
"https://api.github.com/repos/Baida98/AI/actions/workflows/bench.yml/dispatches",
|
| 1633 |
-
json={"ref": "main", "inputs": {
|
| 1634 |
-
"mode": mode,
|
| 1635 |
-
"run_improve": "false",
|
| 1636 |
-
"force_update_baseline": "false",
|
| 1637 |
-
}},
|
| 1638 |
-
headers={"Authorization": f"Bearer {gh_token}",
|
| 1639 |
-
"Accept": "application/vnd.github.v3+json",
|
| 1640 |
-
"User-Agent": "AgenteAI-Bot"},
|
| 1641 |
-
)
|
| 1642 |
-
if _r.status_code == 204:
|
| 1643 |
-
_logger.info("[bench] workflow_dispatch OK (mode=%s)", mode)
|
| 1644 |
-
# Attendi 2s e leggi il run ID appena creato
|
| 1645 |
-
await asyncio.sleep(2.0)
|
| 1646 |
-
async with _hx.AsyncClient(timeout=8.0) as _c2:
|
| 1647 |
-
_r2 = await _c2.get(
|
| 1648 |
-
"https://api.github.com/repos/Baida98/AI/actions/workflows/"
|
| 1649 |
-
"bench.yml/runs?per_page=1",
|
| 1650 |
-
headers={"Authorization": f"Bearer {gh_token}",
|
| 1651 |
-
"Accept": "application/vnd.github.v3+json",
|
| 1652 |
-
"User-Agent": "AgenteAI-Bot"},
|
| 1653 |
-
)
|
| 1654 |
-
if _r2.status_code == 200:
|
| 1655 |
-
_rr = _r2.json().get("workflow_runs", [])
|
| 1656 |
-
if _rr:
|
| 1657 |
-
run_url = _rr[0]["html_url"]
|
| 1658 |
-
else:
|
| 1659 |
-
_logger.warning("[bench] workflow_dispatch status=%d", _r.status_code)
|
| 1660 |
-
except Exception as _exc:
|
| 1661 |
-
_logger.warning("[bench] workflow_dispatch error: %s", _exc)
|
| 1662 |
|
| 1663 |
-
|
| 1664 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1665 |
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1669 |
try:
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
|
| 1677 |
-
|
| 1678 |
-
|
| 1679 |
-
|
| 1680 |
-
|
| 1681 |
-
|
| 1682 |
-
|
| 1683 |
-
|
| 1684 |
-
|
| 1685 |
-
|
| 1686 |
-
|
| 1687 |
-
|
| 1688 |
-
|
| 1689 |
-
|
| 1690 |
-
|
| 1691 |
-
|
| 1692 |
-
|
| 1693 |
-
avg_mns = summary.get("avgManus", 71.2)
|
| 1694 |
-
cat_map: dict[str, list[float]] = {}
|
| 1695 |
-
for t in tasks:
|
| 1696 |
-
cat = (t.get("cat") or "other").replace("_", " ")[:14]
|
| 1697 |
-
cat_map.setdefault(cat, []).append(t.get("score", 0))
|
| 1698 |
-
if not cat_map:
|
| 1699 |
-
return ""
|
| 1700 |
-
labels = list(cat_map.keys())
|
| 1701 |
-
scores = [round(sum(v)/len(v)) for v in cat_map.values()]
|
| 1702 |
-
colors = ["#4CAF50" if s >= avg_rpl else "#FF9800" if s >= 50 else "#F44336" for s in scores]
|
| 1703 |
-
cfg = {
|
| 1704 |
-
"type": "horizontalBar",
|
| 1705 |
-
"data": {
|
| 1706 |
-
"labels": labels,
|
| 1707 |
-
"datasets": [
|
| 1708 |
-
{"label": "Agente AI", "data": scores,
|
| 1709 |
-
"backgroundColor": colors, "borderColor": colors, "borderWidth": 1},
|
| 1710 |
-
{"label": f"Replit {avg_rpl}",
|
| 1711 |
-
"data": [avg_rpl]*len(labels),
|
| 1712 |
-
"type": "line", "borderColor": "#2196F3", "borderDash": [5,3],
|
| 1713 |
-
"pointRadius": 0, "fill": False, "borderWidth": 2},
|
| 1714 |
-
{"label": f"Manus {avg_mns}",
|
| 1715 |
-
"data": [avg_mns]*len(labels),
|
| 1716 |
-
"type": "line", "borderColor": "#9C27B0", "borderDash": [5,3],
|
| 1717 |
-
"pointRadius": 0, "fill": False, "borderWidth": 2},
|
| 1718 |
-
],
|
| 1719 |
-
},
|
| 1720 |
-
"options": {
|
| 1721 |
-
"title": {"display": True,
|
| 1722 |
-
"text": f"Agente AI {avg_ai}% | Replit {avg_rpl}% | Manus {avg_mns}%"},
|
| 1723 |
-
"scales": {"xAxes": [{"ticks": {"min": 0, "max": 100, "stepSize": 20}}]},
|
| 1724 |
-
"legend": {"display": True, "position": "bottom"},
|
| 1725 |
-
"plugins": {"datalabels": {"display": False}},
|
| 1726 |
-
},
|
| 1727 |
-
}
|
| 1728 |
-
return ("https://quickchart.io/chart?c=" +
|
| 1729 |
-
_ul.quote(_j.dumps(cfg, separators=(",",":"))) +
|
| 1730 |
-
"&width=720&height=420&backgroundColor=white")
|
| 1731 |
-
|
| 1732 |
-
if bench_report:
|
| 1733 |
-
chart_url = _build_quickchart(bench_report)
|
| 1734 |
-
|
| 1735 |
-
summary = (bench_report or {}).get("summary", {})
|
| 1736 |
-
avg_ai = summary.get("avgScore")
|
| 1737 |
-
avg_rpl = summary.get("avgReplit")
|
| 1738 |
-
avg_mns = summary.get("avgManus")
|
| 1739 |
-
# ── Tabella ASCII con barre per caption Telegram ──────────────────────────
|
| 1740 |
-
def _text_table_bench(report: dict, rpl: float) -> str:
|
| 1741 |
-
tasks = report.get("tasks", [])
|
| 1742 |
-
cat_map: dict[str, list[float]] = {}
|
| 1743 |
-
for t in tasks:
|
| 1744 |
-
cat = (t.get("cat") or "other").replace("_", " ")[:12]
|
| 1745 |
-
cat_map.setdefault(cat, []).append(float(t.get("score", 0)))
|
| 1746 |
-
if not cat_map:
|
| 1747 |
-
return ""
|
| 1748 |
-
rows = []
|
| 1749 |
-
for cat, vals in sorted(cat_map.items(), key=lambda x: -sum(x[1]) / len(x[1])):
|
| 1750 |
-
sc = round(sum(vals) / len(vals))
|
| 1751 |
-
bar = "█" * round(sc / 10) + "░" * (10 - round(sc / 10))
|
| 1752 |
-
delta_rpl = sc - rpl
|
| 1753 |
-
vs = ("+" if delta_rpl >= 0 else "") + str(round(delta_rpl)) + "vsRpl"
|
| 1754 |
-
rows.append(f"{cat:<12} {bar} {sc:>3}% {vs}")
|
| 1755 |
-
return "\n".join(rows)
|
| 1756 |
-
|
| 1757 |
-
text_table = ""
|
| 1758 |
-
if bench_report and avg_rpl is not None:
|
| 1759 |
-
text_table = _text_table_bench(bench_report, float(avg_rpl))
|
| 1760 |
-
|
| 1761 |
-
score_line = ""
|
| 1762 |
-
if avg_ai is not None:
|
| 1763 |
-
score_line = (
|
| 1764 |
-
f"\n📈 <b>Score:</b> AI <b>{avg_ai}%</b>"
|
| 1765 |
-
+ (f" | Replit {avg_rpl}%" if avg_rpl else "")
|
| 1766 |
-
+ (f" | Manus {avg_mns}%" if avg_mns else "")
|
| 1767 |
-
+ "\n"
|
| 1768 |
)
|
|
|
|
| 1769 |
|
| 1770 |
-
|
| 1771 |
-
|
| 1772 |
-
|
| 1773 |
-
|
| 1774 |
-
|
| 1775 |
-
|
| 1776 |
-
|
| 1777 |
-
full = header + score_line + tbl + link
|
| 1778 |
-
return full[:1024]
|
| 1779 |
-
|
| 1780 |
-
if last_report:
|
| 1781 |
-
_conclusion = last_report.get("conclusion", "?")
|
| 1782 |
-
_em = "✅" if _conclusion == "success" else ("❌" if _conclusion == "failure" else "⚠️")
|
| 1783 |
-
_upd = last_report.get("updated", "")[:16].replace("T", " ")
|
| 1784 |
-
header = f"📊 <b>Benchmark avviato</b> — {_em} {_conclusion}\n🕐 {_upd} UTC"
|
| 1785 |
-
else:
|
| 1786 |
-
header = "📊 <b>Benchmark avviato</b> (benchmark-extended.mjs)"
|
| 1787 |
-
|
| 1788 |
-
caption = _build_bench_caption(header)
|
| 1789 |
-
|
| 1790 |
-
if chart_url:
|
| 1791 |
-
await _tg_photo(chat_id, chart_url, caption=caption, keyboard=_BENCH_ACTION_KB)
|
| 1792 |
-
else:
|
| 1793 |
-
await _tg_reply(chat_id, caption, keyboard=_BENCH_ACTION_KB)
|
| 1794 |
|
| 1795 |
|
| 1796 |
|
|
|
|
| 1588 |
await _tg_reply(chat_id, det[_TG_MAX:_TG_MAX*2][:_TG_MAX], keyboard=_BENCH_ACTION_KB)
|
| 1589 |
|
| 1590 |
|
| 1591 |
+
def _format_live_quality_benchmark(report: dict) -> str:
|
| 1592 |
+
"""Formatta solo risultati prodotti dal benchmark quality corrente."""
|
| 1593 |
+
timestamp = str(report.get("timestamp") or "")[:19].replace("T", " ")
|
| 1594 |
+
score = report.get("total_score", "N/A")
|
| 1595 |
+
results = report.get("results") if isinstance(report.get("results"), list) else []
|
| 1596 |
+
errors = report.get("errors") if isinstance(report.get("errors"), list) else []
|
| 1597 |
+
outcome = "✅" if report.get("ok") else "⚠️"
|
| 1598 |
+
lines = [
|
| 1599 |
+
f"📊 <b>Benchmark live Quality</b> — {outcome}",
|
| 1600 |
+
f"🕐 <code>{html.escape(timestamp or 'ora non disponibile')}</code>",
|
| 1601 |
+
f"📈 <b>Score live:</b> <code>{html.escape(str(score))}/100</code>",
|
| 1602 |
+
f"🧪 Categorie eseguite: <code>{len(results)}</code>",
|
| 1603 |
+
]
|
| 1604 |
+
if results:
|
| 1605 |
+
lines.append("\n<b>Risultati della run corrente:</b>")
|
| 1606 |
+
for item in results[:12]:
|
| 1607 |
+
label = html.escape(str(item.get("label") or item.get("id") or "categoria")[:42])
|
| 1608 |
+
value = item.get("score", "N/A")
|
| 1609 |
+
try:
|
| 1610 |
+
icon = "🟢" if float(value) >= 75 else "🟡" if float(value) >= 50 else "🔴"
|
| 1611 |
+
except (TypeError, ValueError):
|
| 1612 |
+
icon = "⚪"
|
| 1613 |
+
lines.append(f"{icon} <code>{str(value):>5}</code> {label}")
|
| 1614 |
+
if errors:
|
| 1615 |
+
lines.append(f"\n⚠️ <b>Errori della run:</b> <code>{len(errors)}</code>")
|
| 1616 |
+
for error in errors[:2]:
|
| 1617 |
+
lines.append("<i>" + html.escape(str(error)[:180]) + "</i>")
|
| 1618 |
+
lines.append("\n<i>Misura live del backend: nessun report storico o workflow GitHub è stato usato.</i>")
|
| 1619 |
+
return "\n".join(lines)[:3900]
|
| 1620 |
+
|
| 1621 |
+
|
| 1622 |
async def _cmd_bench(chat_id: int, mode: str = "default") -> None:
|
| 1623 |
+
"""Esegue il benchmark quality live del backend senza dipendere da GitHub Actions.
|
| 1624 |
|
| 1625 |
+
Il workflow storico ``bench.yml`` e il relativo runner non esistono più su main;
|
| 1626 |
+
usare il loro report come fallback produceva un falso "benchmark avviato". Il
|
| 1627 |
+
backend espone invece ``/api/benchmark/run-self``: è autenticato, restituisce la
|
| 1628 |
+
run corrente e non richiede CI o billing GitHub.
|
| 1629 |
"""
|
| 1630 |
+
internal_token = os.getenv("INTERNAL_TOKEN", "").strip()
|
| 1631 |
+
if not internal_token:
|
| 1632 |
+
await _tg_reply(
|
| 1633 |
+
chat_id,
|
| 1634 |
+
"⚠️ <b>Benchmark live non configurato.</b>\n"
|
| 1635 |
+
"Manca il token interno del backend: nessun report storico verrà mostrato.",
|
| 1636 |
+
keyboard=_BACK_KB,
|
| 1637 |
+
)
|
| 1638 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1639 |
|
| 1640 |
+
base_url = (
|
| 1641 |
+
os.getenv("BENCHMARK_SELF_URL")
|
| 1642 |
+
or os.getenv("BENCHMARK_BASE_URL")
|
| 1643 |
+
or os.getenv("BACKEND_URL")
|
| 1644 |
+
or "http://127.0.0.1:7860"
|
| 1645 |
+
).rstrip("/")
|
| 1646 |
+
await _tg_reply(
|
| 1647 |
+
chat_id,
|
| 1648 |
+
"🚀 <b>Benchmark live Quality avviato</b>\n"
|
| 1649 |
+
"<i>Misuro ora il backend corrente; attesa tipica 20–30 secondi.</i>",
|
| 1650 |
+
keyboard=_BACK_KB,
|
| 1651 |
+
)
|
| 1652 |
|
| 1653 |
+
try:
|
| 1654 |
+
timeout = httpx.Timeout(connect=5.0, read=90.0, write=15.0, pool=5.0)
|
| 1655 |
+
async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client:
|
| 1656 |
+
response = await client.post(
|
| 1657 |
+
f"{base_url}/api/benchmark/run-self",
|
| 1658 |
+
headers={"X-Internal-Token": internal_token},
|
| 1659 |
+
json={},
|
| 1660 |
+
)
|
| 1661 |
try:
|
| 1662 |
+
report = response.json()
|
| 1663 |
+
except ValueError:
|
| 1664 |
+
report = {}
|
| 1665 |
+
if response.status_code >= 400:
|
| 1666 |
+
detail = str(report.get("detail") or report.get("error") or response.text[:180] or "errore non specificato")
|
| 1667 |
+
await _tg_reply(
|
| 1668 |
+
chat_id,
|
| 1669 |
+
"❌ <b>Benchmark live non completato.</b>\n"
|
| 1670 |
+
f"HTTP <code>{response.status_code}</code>: <code>{html.escape(detail[:220])}</code>\n"
|
| 1671 |
+
"<i>Nessun report storico è stato usato come sostituto.</i>",
|
| 1672 |
+
keyboard=_BACK_KB,
|
| 1673 |
+
)
|
| 1674 |
+
return
|
| 1675 |
+
if not isinstance(report, dict):
|
| 1676 |
+
raise ValueError("Risposta benchmark non valida")
|
| 1677 |
+
except Exception as exc:
|
| 1678 |
+
_logger.warning("[bench-live] request error: %s", exc)
|
| 1679 |
+
await _tg_reply(
|
| 1680 |
+
chat_id,
|
| 1681 |
+
"❌ <b>Benchmark live non raggiungibile.</b>\n"
|
| 1682 |
+
f"<code>{html.escape(str(exc)[:220])}</code>\n"
|
| 1683 |
+
"<i>Nessun report storico è stato usato come sostituto.</i>",
|
| 1684 |
+
keyboard=_BACK_KB,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1685 |
)
|
| 1686 |
+
return
|
| 1687 |
|
| 1688 |
+
_BENCH_CACHE[chat_id] = {
|
| 1689 |
+
"mode": "quality-live",
|
| 1690 |
+
"requested_mode": mode,
|
| 1691 |
+
"run_url": "",
|
| 1692 |
+
"timestamp": report.get("timestamp"),
|
| 1693 |
+
}
|
| 1694 |
+
await _tg_reply(chat_id, _format_live_quality_benchmark(report), keyboard=_BENCH_ACTION_KB)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1695 |
|
| 1696 |
|
| 1697 |
|
tests/test_telegram_benchmark_live.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import unittest
|
| 3 |
+
from unittest.mock import AsyncMock, patch
|
| 4 |
+
|
| 5 |
+
from api.telegram_webhook import _cmd_bench
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class _Response:
|
| 9 |
+
def __init__(self, status_code=200, payload=None, text=""):
|
| 10 |
+
self.status_code = status_code
|
| 11 |
+
self._payload = payload if payload is not None else {}
|
| 12 |
+
self.text = text
|
| 13 |
+
|
| 14 |
+
def json(self):
|
| 15 |
+
return self._payload
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _Client:
|
| 19 |
+
last_post = None
|
| 20 |
+
response = _Response()
|
| 21 |
+
|
| 22 |
+
def __init__(self, **kwargs):
|
| 23 |
+
self.kwargs = kwargs
|
| 24 |
+
|
| 25 |
+
async def __aenter__(self):
|
| 26 |
+
return self
|
| 27 |
+
|
| 28 |
+
async def __aexit__(self, *_args):
|
| 29 |
+
return False
|
| 30 |
+
|
| 31 |
+
async def post(self, *args, **kwargs):
|
| 32 |
+
type(self).last_post = (args, kwargs)
|
| 33 |
+
return type(self).response
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class TelegramLiveBenchmarkTests(unittest.IsolatedAsyncioTestCase):
|
| 37 |
+
def setUp(self):
|
| 38 |
+
_Client.last_post = None
|
| 39 |
+
|
| 40 |
+
async def test_bench_uses_authenticated_self_benchmark_and_only_live_result(self):
|
| 41 |
+
_Client.response = _Response(payload={
|
| 42 |
+
"ok": True,
|
| 43 |
+
"total_score": 73,
|
| 44 |
+
"timestamp": "2026-08-15T12:00:00Z",
|
| 45 |
+
"results": [{"id": "DA", "label": "Data analysis", "score": 80}],
|
| 46 |
+
"errors": [],
|
| 47 |
+
})
|
| 48 |
+
replies = AsyncMock()
|
| 49 |
+
with patch.dict(
|
| 50 |
+
os.environ,
|
| 51 |
+
{"INTERNAL_TOKEN": "internal-test", "BENCHMARK_SELF_URL": "http://backend"},
|
| 52 |
+
clear=False,
|
| 53 |
+
), patch("httpx.AsyncClient", _Client), patch("api.telegram_webhook._tg_reply", replies):
|
| 54 |
+
await _cmd_bench(123)
|
| 55 |
+
|
| 56 |
+
args, kwargs = _Client.last_post
|
| 57 |
+
self.assertEqual(args[0], "http://backend/api/benchmark/run-self")
|
| 58 |
+
self.assertEqual(kwargs["headers"], {"X-Internal-Token": "internal-test"})
|
| 59 |
+
self.assertEqual(kwargs["json"], {})
|
| 60 |
+
self.assertEqual(replies.await_count, 2)
|
| 61 |
+
final_text = replies.await_args_list[-1].args[1]
|
| 62 |
+
self.assertIn("Benchmark live Quality", final_text)
|
| 63 |
+
self.assertIn("73/100", final_text)
|
| 64 |
+
self.assertNotIn("GitHub Actions", final_text)
|
| 65 |
+
|
| 66 |
+
async def test_bench_does_not_substitute_historical_report_after_live_failure(self):
|
| 67 |
+
_Client.response = _Response(status_code=503, payload={"detail": "unavailable"})
|
| 68 |
+
replies = AsyncMock()
|
| 69 |
+
with patch.dict(
|
| 70 |
+
os.environ,
|
| 71 |
+
{"INTERNAL_TOKEN": "internal-test", "BENCHMARK_SELF_URL": "http://backend"},
|
| 72 |
+
clear=False,
|
| 73 |
+
), patch("httpx.AsyncClient", _Client), patch("api.telegram_webhook._tg_reply", replies):
|
| 74 |
+
await _cmd_bench(123)
|
| 75 |
+
|
| 76 |
+
final_text = replies.await_args_list[-1].args[1]
|
| 77 |
+
self.assertIn("Benchmark live non completato", final_text)
|
| 78 |
+
self.assertIn("Nessun report storico", final_text)
|
| 79 |
+
self.assertNotIn("Score: AI", final_text)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
if __name__ == "__main__":
|
| 83 |
+
unittest.main()
|