"""Errors can be grouped, marked fixed, and reported as metrics.""" import asyncio import sys sys.path.insert(0, "/app") from db.database import init_db, close_db_pool from db.repository import Repository from services.admin_bot import AdminBotService, clamp_for_telegram, MAX_MEDIA_CAPTION, MAX_TEXT_MESSAGE SVC = "test.tracking_service" async def _cleanup(repo): pool = await repo._get_pool() async with pool.acquire() as conn: await conn.execute("DELETE FROM error_logs WHERE service_name LIKE 'test.tracking%';") async def _log(repo, service, etype, msg): pool = await repo._get_pool() async with pool.acquire() as conn: await conn.execute( "INSERT INTO error_logs (service_name, error_type, error_message) VALUES ($1,$2,$3);", service, etype, msg) def test_clamping(): # Short text is untouched. assert clamp_for_telegram("hello", True) == "hello" long_text = "x" * 5000 media = clamp_for_telegram(long_text, has_media=True) assert len(media) <= MAX_MEDIA_CAPTION, f"media caption still {len(media)} chars" assert media.endswith(""), "truncation marker missing" plain = clamp_for_telegram(long_text, has_media=False) assert len(plain) <= MAX_TEXT_MESSAGE, f"text message still {len(plain)} chars" # A message that exactly fits must not be altered. exact = "y" * MAX_MEDIA_CAPTION assert clamp_for_telegram(exact, True) == exact async def run_tests(): test_clamping() await init_db() repo = Repository() await _cleanup(repo) await _log(repo, SVC, "ValueError", "bad value one") await _log(repo, SVC, "ValueError", "bad value two") await _log(repo, SVC, "KeyError", "missing key") await _log(repo, SVC + "_b", "ValueError", "other service") groups = await repo.get_open_error_summary(limit=50) mine = {(g["service_name"], g["error_type"]): g for g in groups if g["service_name"].startswith("test.tracking")} assert len(mine) == 3, f"expected 3 groups, got {list(mine)}" assert mine[(SVC, "ValueError")]["occurrences"] == 2 assert mine[(SVC, "ValueError")]["last_message"] == "bad value two", "last_message should be the newest" # Resolving one group leaves the others untouched. fixed = await repo.resolve_errors(service_name=SVC, error_type="ValueError", note="fixed in tests") assert fixed == 2, f"expected 2 rows resolved, got {fixed}" groups = await repo.get_open_error_summary(limit=50) mine = {(g["service_name"], g["error_type"]) for g in groups if g["service_name"].startswith("test.tracking")} assert (SVC, "ValueError") not in mine assert (SVC, "KeyError") in mine and (SVC + "_b", "ValueError") in mine # Resolving again is a no-op, not a double count. assert await repo.resolve_errors(service_name=SVC, error_type="ValueError") == 0 # The gauge reflects what is still open. bot = AdminBotService(repo=repo, review_channel_id=0, admin_user_ids=[1]) total = await bot.refresh_error_metrics() assert total == await repo.count_open_errors(), "gauge total must match the open count" # The report renders and caches groups for the callback buttons. text, buttons = await bot._render_error_report() assert "KeyError" in text assert bot.error_group_cache, "groups must be cached for errfix callbacks" assert any(b.data.decode().startswith("errfix:") for row in buttons for b in row) await _cleanup(repo) await close_db_pool() print("All error-tracking tests passed successfully!") if __name__ == "__main__": asyncio.run(run_tests())