Files
nanobot/tests/test_idle_heartbeat_integration.py
code-server c6b68f0b6b fix: update tests to handle signed visibility markers
Updated existing tests to work with cryptographic visibility markers:

1. test_agent_loop_metadata.py:
   - Updated test_suppress_mode_adds_hidden_prefix to verify [HIDDEN:signature] format
   - Added validation for 8-character hex signature
   - Updated test_normal_mode_no_hidden_prefix to check for [HIDDEN: prefix

2. test_idle_heartbeat_integration.py:
   - Updated test_idle_heartbeat_end_to_end to search for [HIDDEN: prefix
   - Added signature format validation (8-char hex)
   - Updated docstring to reflect signed markers

All 86 tests now pass (excluding OAuth tests as specified).
The changes maintain backwards compatibility while enforcing
the new cryptographic signing requirement.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 23:42:28 +00:00

111 lines
3.8 KiB
Python

# tests/test_idle_heartbeat_integration.py
import pytest
import asyncio
from pathlib import Path
from datetime import datetime, timedelta
from nanobot.agent.loop import AgentLoop
from nanobot.bus.queue import MessageBus
from nanobot.heartbeat.service import HeartbeatService
from nanobot.session.manager import SessionManager
from nanobot.providers.base import LLMProvider, LLMResponse
from unittest.mock import AsyncMock, MagicMock
@pytest.mark.asyncio
async def test_idle_heartbeat_end_to_end(tmp_path):
"""
Integration test: heartbeat triggers when idle, runs in main session,
output is suppressed, session contains [HIDDEN:signature] content.
"""
workspace = tmp_path / "test-integration"
workspace.mkdir()
# Use test-specific session key
test_session_key = "telegram:test_integration"
# Create HEARTBEAT.md with content
heartbeat_file = workspace / "HEARTBEAT.md"
heartbeat_file.write_text("# Test Task\n- Check something")
# Create mock provider
provider = MagicMock(spec=LLMProvider)
provider.chat = AsyncMock(return_value=LLMResponse(
content="Heartbeat executed successfully",
tool_calls=[] # has_tool_calls is a property, not a parameter
))
provider.get_default_model = MagicMock(return_value="test-model")
provider.thinking_budget = 0
# Create components
bus = MessageBus()
sessions = SessionManager(workspace)
# Override sessions_dir to use tmp_path for test isolation
sessions.sessions_dir = tmp_path / "sessions"
sessions.sessions_dir.mkdir()
loop = AgentLoop(
bus=bus,
provider=provider,
workspace=workspace,
session_manager=sessions
)
# Create session with old user message
session = sessions.get_or_create(test_session_key)
old_timestamp = (datetime.now() - timedelta(minutes=31)).isoformat()
session.messages.append({
"role": "user",
"content": "Old user message",
"timestamp": old_timestamp
})
sessions.save(session)
# Create heartbeat callback
async def on_heartbeat(prompt: str, metadata=None):
return await loop.process_direct(
prompt,
session_key=test_session_key,
channel="telegram",
chat_id="test_integration",
metadata=metadata,
)
# Create heartbeat service
heartbeat = HeartbeatService(
workspace=workspace,
on_heartbeat=on_heartbeat,
interval_s=1,
enabled=True,
session_manager=sessions,
target_session_key=test_session_key,
idle_threshold_s=30 * 60,
)
# Trigger heartbeat
await heartbeat._tick()
# Reload session from disk
sessions._cache.clear() # Clear cache to force reload
session = sessions.get_or_create(test_session_key)
# Verify:
# 1. Session has new messages
assert len(session.messages) > 1
# 2. Find the heartbeat response (assistant message with signed visibility marker)
heartbeat_messages = [
m for m in session.messages
if m.get("role") == "assistant" and "[HIDDEN:" in m.get("content", "")
]
assert len(heartbeat_messages) == 1, "Expected exactly 1 [HIDDEN:signature] heartbeat message"
# 3. Verify content is prefixed with [HIDDEN:signature]
heartbeat_msg = heartbeat_messages[0]
assert heartbeat_msg["content"].startswith("[HIDDEN:")
# Verify signature format (8-char hex)
content = heartbeat_msg["content"]
prefix_end = content.index("]")
signature = content[8:prefix_end] # Skip "[HIDDEN:" to get signature
assert len(signature) == 8, f"Expected 8-char signature, got {len(signature)}"
assert all(c in "0123456789abcdef" for c in signature), "Signature should be hex"
assert "Heartbeat executed successfully" in heartbeat_msg["content"]