test(agent): expand coverage and refactor test structure
- 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
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user