mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
- Add 42 tests for ContextBuilder (context.py: 0→42 tests) - Add 37 tests for SubagentManager lifecycle (subagent.py: 2→37 tests) - Add 42 unit tests for AutoCompact in isolation - Split monolithic test_runner.py (3313 lines) into 9 focused files: test_runner_core, test_runner_hooks, test_runner_errors, test_runner_safety, test_runner_persistence, test_runner_governance, test_runner_tool_execution, test_runner_injections, test_loop_runner_integration - Add 3 config passthrough tests (temperature/max_tokens/reasoning_effort) - Fix fragile patch.object(__init__) in test_stop_preserves_context - Create shared conftest.py with make_provider/make_loop factories Total: 934 tests passing, 0 regressions
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
"""Shared fixtures and helpers for agent tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from nanobot.agent.loop import AgentLoop
|
|
from nanobot.bus.queue import MessageBus
|
|
from nanobot.providers.base import LLMProvider
|
|
|
|
|
|
def make_provider(
|
|
default_model: str = "test-model",
|
|
*,
|
|
max_tokens: int = 4096,
|
|
spec: bool = True,
|
|
) -> MagicMock:
|
|
"""Create a spec-limited LLM provider mock."""
|
|
mock_type = MagicMock(spec=LLMProvider) if spec else MagicMock()
|
|
provider = mock_type
|
|
provider.get_default_model.return_value = default_model
|
|
provider.generation = SimpleNamespace(
|
|
max_tokens=max_tokens,
|
|
temperature=0.1,
|
|
reasoning_effort=None,
|
|
)
|
|
provider.estimate_prompt_tokens.return_value = (10_000, "test")
|
|
return provider
|
|
|
|
|
|
def make_loop(
|
|
tmp_path: Path,
|
|
*,
|
|
model: str = "test-model",
|
|
context_window_tokens: int = 128_000,
|
|
session_ttl_minutes: int = 0,
|
|
max_messages: int = 120,
|
|
unified_session: bool = False,
|
|
mcp_servers: dict | None = None,
|
|
tools_config=None,
|
|
model_presets: dict | None = None,
|
|
hooks: list | None = None,
|
|
provider: MagicMock | None = None,
|
|
patch_deps: bool = False,
|
|
) -> AgentLoop:
|
|
"""Create a real AgentLoop for testing.
|
|
|
|
Args:
|
|
patch_deps: If True, patch ContextBuilder/SessionManager/SubagentManager
|
|
during construction (needed when workspace has no real files).
|
|
"""
|
|
bus = MessageBus()
|
|
if provider is None:
|
|
provider = make_provider(default_model=model)
|
|
|
|
kwargs = dict(
|
|
bus=bus,
|
|
provider=provider,
|
|
workspace=tmp_path,
|
|
model=model,
|
|
context_window_tokens=context_window_tokens,
|
|
session_ttl_minutes=session_ttl_minutes,
|
|
max_messages=max_messages,
|
|
unified_session=unified_session,
|
|
)
|
|
if mcp_servers is not None:
|
|
kwargs["mcp_servers"] = mcp_servers
|
|
if tools_config is not None:
|
|
kwargs["tools_config"] = tools_config
|
|
if model_presets is not None:
|
|
kwargs["model_presets"] = model_presets
|
|
if hooks is not None:
|
|
kwargs["hooks"] = hooks
|
|
|
|
if patch_deps:
|
|
with patch("nanobot.agent.loop.ContextBuilder"), \
|
|
patch("nanobot.agent.loop.SessionManager"), \
|
|
patch("nanobot.agent.loop.SubagentManager") as MockSubMgr:
|
|
MockSubMgr.return_value.cancel_by_session = AsyncMock(return_value=0)
|
|
return AgentLoop(**kwargs)
|
|
return AgentLoop(**kwargs)
|
|
|
|
|
|
@pytest.fixture
|
|
def loop_factory(tmp_path):
|
|
"""Fixture providing a factory for creating AgentLoop instances."""
|
|
def _factory(**kwargs):
|
|
return make_loop(tmp_path, **kwargs)
|
|
return _factory
|