test(hub): MCP — опрос и потолок batch_wait, точные ассерты refresh/sync_log
_client() в batch_wait создаётся один раз перед циклом. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
parent
29f6b30752
commit
f3ac1eb5c2
@ -141,8 +141,9 @@ def batch_status(id: int) -> str:
|
|||||||
def batch_wait(id: int, timeout_minutes: int = 10) -> str:
|
def batch_wait(id: int, timeout_minutes: int = 10) -> str:
|
||||||
"""Ждать завершения пакета (до 60 мин), вернуть итог по командам."""
|
"""Ждать завершения пакета (до 60 мин), вернуть итог по командам."""
|
||||||
deadline = time.monotonic() + min(max(int(timeout_minutes), 1), 60) * 60
|
deadline = time.monotonic() + min(max(int(timeout_minutes), 1), 60) * 60
|
||||||
|
client = _client()
|
||||||
while True:
|
while True:
|
||||||
b = _client().get(f"/batches/{id}")
|
b = client.get(f"/batches/{id}")
|
||||||
if b["status"] in ("done", "cancelled") or time.monotonic() >= deadline:
|
if b["status"] in ("done", "cancelled") or time.monotonic() >= deadline:
|
||||||
return fmt_batch(b) + ("" if b["status"] in ("done", "cancelled") else "\n(таймаут ожидания, пакет ещё выполняется)")
|
return fmt_batch(b) + ("" if b["status"] in ("done", "cancelled") else "\n(таймаут ожидания, пакет ещё выполняется)")
|
||||||
time.sleep(POLL_SECONDS)
|
time.sleep(POLL_SECONDS)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -52,9 +54,8 @@ def test_tools_roundtrip(live):
|
|||||||
assert "Повтор команды" in mcp_server.command_retry(cmd["id"])
|
assert "Повтор команды" in mcp_server.command_retry(cmd["id"])
|
||||||
assert "cancelled" in mcp_server.batch_cancel(batch_id + 1)
|
assert "cancelled" in mcp_server.batch_cancel(batch_id + 1)
|
||||||
r = mcp_server.refresh(full=True, wait=False)
|
r = mcp_server.refresh(full=True, wait=False)
|
||||||
assert "Обновление" in r or "#" in r
|
assert re.fullmatch(r"пакет #\d+ «Обновление» поставлен", r)
|
||||||
assert "онлайн" in mcp_server.agent_status()
|
assert "онлайн" in mcp_server.agent_status()
|
||||||
assert "A-1" in mcp_server.sync_log(limit=3) or "keys" in mcp_server.sync_log(limit=3)
|
|
||||||
assert "120" in mcp_server.settings_get("pacing")
|
assert "120" in mcp_server.settings_get("pacing")
|
||||||
assert "settings_version" in mcp_server.settings_set("pacing", json.dumps({"refresh": 1}))
|
assert "settings_version" in mcp_server.settings_set("pacing", json.dumps({"refresh": 1}))
|
||||||
assert mcp_server.settings_set("tokens", '"x"').startswith("ошибка хаба 422")
|
assert mcp_server.settings_set("tokens", '"x"').startswith("ошибка хаба 422")
|
||||||
@ -72,3 +73,76 @@ def test_formatters():
|
|||||||
|
|
||||||
def test_settings_set_bad_json(live):
|
def test_settings_set_bad_json(live):
|
||||||
assert mcp_server.settings_set("pacing", "{не json").startswith("значение не JSON")
|
assert mcp_server.settings_set("pacing", "{не json").startswith("значение не JSON")
|
||||||
|
|
||||||
|
|
||||||
|
def _batch(status):
|
||||||
|
return {"id": 7, "title": "т", "author": "claude", "created_at": "2026-10-11", "status": status,
|
||||||
|
"remaining_seconds": 0, "counts": {status: 1}, "commands": []}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeClient:
|
||||||
|
def __init__(self, statuses, runs=None):
|
||||||
|
self.statuses, self.calls, self.runs = list(statuses), 0, runs
|
||||||
|
|
||||||
|
def get(self, path, **params):
|
||||||
|
if path == "/sync-runs":
|
||||||
|
return {"runs": self.runs}
|
||||||
|
self.calls += 1
|
||||||
|
return _batch(self.statuses.pop(0) if len(self.statuses) > 1 else self.statuses[0])
|
||||||
|
|
||||||
|
|
||||||
|
def _fake_time(monkeypatch, step=1.0):
|
||||||
|
clock = {"now": 0.0, "sleeps": 0, "deadlines": []}
|
||||||
|
|
||||||
|
def monotonic():
|
||||||
|
clock["now"] += step
|
||||||
|
return clock["now"]
|
||||||
|
|
||||||
|
def sleep(_):
|
||||||
|
clock["sleeps"] += 1
|
||||||
|
|
||||||
|
monkeypatch.setattr(mcp_server, "time", SimpleNamespace(monotonic=monotonic, sleep=sleep))
|
||||||
|
return clock
|
||||||
|
|
||||||
|
|
||||||
|
def test_batch_wait_polls_until_done(monkeypatch):
|
||||||
|
fake = FakeClient(["running", "running", "done"])
|
||||||
|
monkeypatch.setattr(mcp_server, "_client", lambda: fake)
|
||||||
|
clock = _fake_time(monkeypatch)
|
||||||
|
out = mcp_server.batch_wait(7, timeout_minutes=10)
|
||||||
|
assert out == mcp_server.fmt_batch(_batch("done")) and "таймаут" not in out
|
||||||
|
assert fake.calls == 3 and clock["sleeps"] == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_batch_wait_timeout(monkeypatch):
|
||||||
|
fake = FakeClient(["running"])
|
||||||
|
monkeypatch.setattr(mcp_server, "_client", lambda: fake)
|
||||||
|
clock = _fake_time(monkeypatch, step=30.0) # 1 минута = 2 шага монотонных часов
|
||||||
|
out = mcp_server.batch_wait(7, timeout_minutes=1)
|
||||||
|
assert "таймаут ожидания" in out
|
||||||
|
assert 1 < fake.calls < 10 and clock["sleeps"] == fake.calls - 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("asked,minutes", [(0, 1), (1000, 60)])
|
||||||
|
def test_batch_wait_clamps_timeout(monkeypatch, asked, minutes):
|
||||||
|
fake = FakeClient(["running"])
|
||||||
|
monkeypatch.setattr(mcp_server, "_client", lambda: fake)
|
||||||
|
clock = _fake_time(monkeypatch, step=60.0) # каждый вызов часов = +1 минута
|
||||||
|
mcp_server.batch_wait(7, timeout_minutes=asked)
|
||||||
|
# первый вызов часов (1 мин) задаёт старт; дедлайн = старт + minutes; выход, когда now >= дедлайна
|
||||||
|
assert fake.calls == minutes
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_created_once_per_wait(monkeypatch):
|
||||||
|
fake, made = FakeClient(["running", "running", "done"]), []
|
||||||
|
monkeypatch.setattr(mcp_server, "_client", lambda: made.append(1) or fake)
|
||||||
|
_fake_time(monkeypatch)
|
||||||
|
mcp_server.batch_wait(7)
|
||||||
|
assert len(made) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_log_content(monkeypatch):
|
||||||
|
runs = [{"id": 3, "kind": "full", "query": "project = AWPCA", "started_at": "2026-10-11T10:00", "finished_at": None,
|
||||||
|
"issues_count": 12, "links_count": 5, "jira_calls": 4, "rate_limited": 1}]
|
||||||
|
monkeypatch.setattr(mcp_server, "_client", lambda: FakeClient(["done"], runs))
|
||||||
|
assert mcp_server.sync_log() == "#3 full project = AWPCA: 2026-10-11T10:00 → —, задач 12, связей 5, вызовов 4, 429: 1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user