Files
LangBot/tests/unit_tests/telemetry/test_heartbeat.py
RockChinQ b7d8332cb0 feat(telemetry): include instance_create_ts in heartbeat payload
Load the instance creation timestamp from data/labels/instance_id.json
(backfilling+persisting it for instances created before the field existed),
expose it as constants.instance_create_ts, and include it in the heartbeat
payload so Space can anchor Time-To-Value / onboarding analytics on real
install time rather than first-heartbeat.

Verified: py_compile, ruff, pytest tests/unit_tests/telemetry/ (37 passed).
2026-06-13 11:13:18 -04:00

106 lines
3.3 KiB
Python

"""Unit tests for telemetry heartbeat payload (pkg/telemetry/heartbeat.py)."""
from __future__ import annotations
import json
import pytest
from unittest.mock import AsyncMock, Mock
from importlib import import_module
def get_heartbeat_module():
return import_module('langbot.pkg.telemetry.heartbeat')
def make_app():
ap = Mock()
ap.instance_config = Mock()
ap.instance_config.data = {
'database': {'use': 'postgresql'},
'vdb': {'use': 'chroma'},
'box': {'enabled': True, 'backend': 'nsjail'},
}
# persistence counts
result = Mock()
result.scalar.return_value = 3
ap.persistence_mgr = Mock()
ap.persistence_mgr.execute_async = AsyncMock(return_value=result)
# box service
ap.box_service = Mock()
ap.box_service.enabled = True
ap.box_service.available = False
ap.box_service.shares_filesystem_with_box = False
# platform manager with one enabled bot
bot = Mock()
bot.enable = True
bot.adapter = Mock()
bot.adapter.__class__.__name__ = 'TelegramAdapter'
ap.platform_mgr = Mock()
ap.platform_mgr.bots = [bot]
# plugin connector
ap.plugin_connector = Mock()
ap.plugin_connector.list_plugins = AsyncMock(return_value=[{}, {}])
# skills
ap.skill_mgr = Mock()
ap.skill_mgr.skills = {'a': {}, 'b': {}, 'c': {}}
return ap
class TestBuildHeartbeatPayload:
@pytest.mark.asyncio
async def test_payload_shape(self):
heartbeat = get_heartbeat_module()
ap = make_app()
payload = await heartbeat.build_heartbeat_payload(ap)
assert payload['event_type'] == 'instance_heartbeat'
assert payload['query_id'] == ''
assert 'instance_create_ts' in payload
assert 'timestamp' in payload
f = payload['features']
assert f['database'] == 'postgresql'
assert f['vdb'] == 'chroma'
assert f['box'] == {
'enabled': True,
'available': False,
'backend': 'nsjail',
'shares_fs': False,
}
assert f['adapters'] == ['TelegramAdapter']
assert f['bot_count'] == 1
assert f['plugin_count'] == 2
assert f['skill_count'] == 3
assert f['pipeline_count'] == 3
assert f['mcp_server_count'] == 3
assert f['knowledge_base_count'] == 3
@pytest.mark.asyncio
async def test_payload_is_json_serializable(self):
heartbeat = get_heartbeat_module()
payload = await heartbeat.build_heartbeat_payload(make_app())
json.dumps(payload)
@pytest.mark.asyncio
async def test_count_failure_yields_minus_one(self):
heartbeat = get_heartbeat_module()
ap = make_app()
ap.persistence_mgr.execute_async = AsyncMock(side_effect=RuntimeError('db down'))
payload = await heartbeat.build_heartbeat_payload(ap)
assert payload['features']['pipeline_count'] == -1
@pytest.mark.asyncio
async def test_no_user_content_fields(self):
"""The heartbeat must never carry message content / credentials keys."""
heartbeat = get_heartbeat_module()
payload = await heartbeat.build_heartbeat_payload(make_app())
flat = json.dumps(payload).lower()
for forbidden in ('api_key', 'password', 'token', 'message_content'):
assert forbidden not in flat