-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconftest.py
More file actions
167 lines (132 loc) · 6.23 KB
/
Copy pathconftest.py
File metadata and controls
167 lines (132 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""
tests/conftest.py — Shared pytest fixtures for the langgraph-agent-stack test suite.
All fixtures use mocks so that no real LLM API calls are made during tests.
The FastAPI TestClient is wired to a patched application that replaces
the legacy pack class (via ``get_legacy_pack_cls`` dependency override) and
``ResearchAgent`` with MagicMock instances.
"""
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
from agents.models import AnalysisReport, ResearchResult
from tests.legacy_pack_override import override_legacy_pack_cls
# ---------------------------------------------------------------------------
# Domain-object fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def mock_research_result() -> ResearchResult:
"""Return a real ``ResearchResult`` instance for testing.
Scope: function — each test gets a fresh copy to avoid shared state.
"""
return ResearchResult(
query="What is quantum computing?",
summary="Quantum computing uses qubits to perform computations.",
findings=[
"Quantum computers leverage superposition and entanglement.",
"Current hardware is still in the NISQ era.",
],
sources=[
"https://example.com/quantum",
"https://news.example.com/quantum-computing",
],
confidence=0.85,
metadata={"agent": "ResearchAgent", "thread_id": "test-thread-001"},
)
@pytest.fixture()
def mock_analysis_report() -> AnalysisReport:
"""Return a real ``AnalysisReport`` instance for testing.
Scope: function — each test gets a fresh copy to avoid shared state.
"""
return AnalysisReport(
query="What is quantum computing?",
executive_summary=(
"Quantum computing represents a paradigm shift in computational power, "
"leveraging quantum mechanics to solve problems intractable for classical "
"computers."
),
key_insights=[
"Qubits enable superposition, exponentially expanding the solution space.",
"Error correction remains the dominant engineering challenge.",
],
patterns=[
"Rapid hardware iteration across multiple qubit modalities.",
"Growing investment from both public and private sectors.",
],
implications=[
"Cryptographic systems based on integer factorisation will need replacement.",
"Drug discovery and material science stand to benefit most in the near term.",
],
confidence=0.82,
research_summary="Quantum computing uses qubits to perform computations.",
metadata={"run_id": "test-run-001", "agent": "AnalystAgent"},
)
# ---------------------------------------------------------------------------
# TestClient fixture — agents fully mocked, no Anthropic calls
# ---------------------------------------------------------------------------
def _make_mock_stream_events(report: AnalysisReport):
"""Build an async generator function mimicking ``MultiAgentGraph.stream_events``."""
async def stream_events(query: str):
yield {"type": "phase_started", "phase": "research"}
yield {"type": "phase_completed", "phase": "research"}
yield {"type": "phase_started", "phase": "analysis"}
yield {"type": "phase_completed", "phase": "analysis"}
yield {"type": "pipeline_completed", "report": report.to_dict()}
return stream_events
@pytest.fixture(scope="function")
def test_client(
mock_research_result: ResearchResult,
mock_analysis_report: AnalysisReport,
) -> Generator[TestClient, None, None]:
"""
Return a FastAPI TestClient with MultiAgentGraph and ResearchAgent mocked.
A fresh mock is constructed for every test function so that call counts
and side effects are isolated between tests.
Patches applied:
- ``api.main.MultiAgentGraph`` — ``run()`` returns ``mock_analysis_report``,
``stream_events()`` yields real-time pipeline events.
- ``api.main.ResearchAgent`` — ``run_structured()`` returns ``mock_research_result``.
- ``api.main._rate_limiter`` — replaced with a permissive limiter (max 10 000
requests) so individual endpoint tests are never blocked.
"""
from core.security import RateLimiter
permissive_limiter = RateLimiter(max_requests=10_000, window_seconds=60.0)
mock_graph_instance = MagicMock()
mock_graph_instance.run.return_value = mock_analysis_report
mock_graph_instance.stream_events = _make_mock_stream_events(mock_analysis_report)
mock_graph_instance.__enter__ = MagicMock(return_value=mock_graph_instance)
mock_graph_instance.__exit__ = MagicMock(return_value=False)
mock_researcher_instance = MagicMock()
mock_researcher_instance.run_structured.return_value = mock_research_result
mock_graph_cls = MagicMock(return_value=mock_graph_instance)
mock_researcher_cls = MagicMock(return_value=mock_researcher_instance)
mock_llm = MagicMock(spec=True)
mock_checkpointer = MagicMock()
with (
override_legacy_pack_cls(mock_graph_cls),
patch("api.endpoints.pipeline.ResearchAgent", mock_researcher_cls),
patch("api.state.rate_limiter", permissive_limiter),
patch("api.state.get_shared_llm", return_value=mock_llm),
patch("api.state.get_shared_checkpointer", return_value=mock_checkpointer),
):
from api.main import app
with TestClient(app) as client:
yield client
# ---------------------------------------------------------------------------
# Settings fixture (unit tests that need a Settings instance)
# ---------------------------------------------------------------------------
@pytest.fixture(scope="session")
def test_settings():
"""
Return a Settings instance configured for testing.
Uses an in-memory SQLite database so no files are written to disk.
"""
from core.config import Settings
return Settings(
llm_provider="anthropic",
anthropic_api_key="sk-ant-test123456789012345",
memory_backend="sqlite",
sqlite_path=":memory:",
environment="development",
)