diff --git a/README.md b/README.md index 8453134..5360c6c 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ pytest ## Версионирование -- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.7.1`. +- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.7.2`. - Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`). - Docker-образы тегируются версией (Блок 2), события аналитики получают `bot_version`. - Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`. diff --git a/common/analytics/client.py b/common/analytics/client.py index eccdd91..db02ace 100644 --- a/common/analytics/client.py +++ b/common/analytics/client.py @@ -47,9 +47,9 @@ Writer = Callable[[list[EventRow], MessageCounts], Awaitable[None]] async def _write_db(events: list[EventRow], messages: MessageCounts) -> None: """Записать батч в Postgres.""" - async with get_pool().connection() as conn: + async with get_pool().connection() as conn, conn.cursor() as cur: if events: - await conn.executemany( + await cur.executemany( """ INSERT INTO analytics.events (event_time, event_type, user_id, username, chat_id, payload, bot_version, prod) @@ -71,7 +71,7 @@ async def _write_db(events: list[EventRow], messages: MessageCounts) -> None: ) for (day, user_id, chat_id), (username, count) in messages.items(): - await conn.execute( + await cur.execute( """ INSERT INTO analytics.messages_daily (day, user_id, username, chat_id, count) VALUES (%s, %s, %s, %s, %s) diff --git a/common/version.py b/common/version.py index 8239d83..7d2bc47 100644 --- a/common/version.py +++ b/common/version.py @@ -7,4 +7,4 @@ from __future__ import annotations -__version__ = "0.7.1" +__version__ = "0.7.2" diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 4303585..6d399e1 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -60,6 +60,51 @@ class FakeWriter: self.messages[key] = list(value) +class FakeCursor: + """Курсор psycopg: запоминает вызовы.""" + + def __init__(self) -> None: + self.calls: list[tuple[str, Any, Any]] = [] + + async def executemany(self, query: str, rows: list[Any]) -> None: + self.calls.append(("executemany", query, rows)) + + async def execute(self, query: str, params: Any) -> None: + self.calls.append(("execute", query, params)) + + async def __aenter__(self) -> FakeCursor: + return self + + async def __aexit__(self, *args: Any) -> None: + return None + + +class FakeConnection: + """Соединение psycopg с курсором.""" + + def __init__(self) -> None: + self.cursor_obj = FakeCursor() + + def cursor(self) -> FakeCursor: + return self.cursor_obj + + async def __aenter__(self) -> FakeConnection: + return self + + async def __aexit__(self, *args: Any) -> None: + return None + + +class FakePool: + """Пул с одним соединением.""" + + def __init__(self) -> None: + self.conn = FakeConnection() + + def connection(self) -> FakeConnection: + return self.conn + + def _settings(**overrides: Any) -> Settings: return Settings(_env_file=None, bot_token="1:a", **overrides) @@ -230,6 +275,26 @@ async def test_disabled_analytics_ignores_events() -> None: assert fake.messages == {} +# --- запись в Postgres --- + + +async def test_write_db_uses_cursor_executemany(monkeypatch: pytest.MonkeyPatch) -> None: + from common.analytics import client as analytics_client + + pool = FakePool() + monkeypatch.setattr(analytics_client, "get_pool", lambda: pool) + + await analytics_client._write_db( + [[datetime.now(UTC), "command", 1, "vasya", 100, {"command": "инфо"}, "0.7.2", False]], + {(datetime.now(UTC).date(), 1, 100): ["vasya", 3]}, + ) + + calls = pool.conn.cursor_obj.calls + assert calls[0][0] == "executemany" + assert calls[1][0] == "execute" + assert "messages_daily" in calls[1][1] + + # --- деградация без Postgres --- diff --git a/tests/test_version.py b/tests/test_version.py index 4581ecc..aa702b1 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -7,4 +7,4 @@ from common import __version__ def test_version_is_set(): assert __version__ - assert __version__ == "0.7.1" + assert __version__ == "0.7.2"