Skip to content

Commit 0509a31

Browse files
author
Compyle Bot
committed
Auto-commit: Agent tool execution
1 parent 5f2d1f9 commit 0509a31

File tree

1 file changed

+308
-0
lines changed

1 file changed

+308
-0
lines changed

src/tests/test_overview.py

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
"""Tests for the get_repo_overview tool."""
2+
3+
import pytest
4+
from unittest.mock import AsyncMock, patch, MagicMock
5+
6+
from mcp.server.fastmcp import Context
7+
8+
from tools.overview import get_repo_overview
9+
from core.client import CodeAliveContext
10+
import httpx
11+
12+
13+
@pytest.mark.asyncio
14+
@patch("tools.overview.get_api_key_from_context")
15+
async def test_get_repo_overview_success(mock_get_api_key):
16+
"""Test successful retrieval of repository overview."""
17+
# Mock API key
18+
mock_get_api_key.return_value = "test-api-key"
19+
20+
# Mock response
21+
mock_response = MagicMock()
22+
mock_response.json.return_value = [
23+
{
24+
"name": "test-repo",
25+
"overview": "# Purpose\nTest repository\n## Responsibilities\n- Task 1"
26+
}
27+
]
28+
mock_response.status_code = 200
29+
30+
# Mock client
31+
mock_client = AsyncMock()
32+
mock_client.get = AsyncMock(return_value=mock_response)
33+
34+
# Mock context
35+
mock_context = MagicMock(spec=CodeAliveContext)
36+
mock_context.client = mock_client
37+
mock_context.base_url = "https://app.codealive.ai"
38+
39+
mock_ctx = MagicMock(spec=Context)
40+
mock_ctx.request_context.lifespan_context = mock_context
41+
42+
# Call tool
43+
result = await get_repo_overview(mock_ctx, ["test-repo"])
44+
45+
# Assertions
46+
assert '<repository name="test-repo">' in result
47+
assert '<overview>' in result
48+
assert '# Purpose' in result
49+
assert 'Test repository' in result
50+
assert '## Responsibilities' in result
51+
assert '- Task 1' in result
52+
53+
# Verify API call
54+
mock_client.get.assert_called_once()
55+
call_args = mock_client.get.call_args
56+
assert call_args.kwargs['headers'] == {"Authorization": "Bearer test-api-key"}
57+
assert "https://app.codealive.ai/api/overview" in call_args.args[0]
58+
59+
60+
@pytest.mark.asyncio
61+
@patch("tools.overview.get_api_key_from_context")
62+
async def test_get_repo_overview_multiple_repos(mock_get_api_key):
63+
"""Test retrieval of multiple repository overviews."""
64+
mock_get_api_key.return_value = "test-api-key"
65+
66+
# Mock response with 3 repositories
67+
mock_response = MagicMock()
68+
mock_response.json.return_value = [
69+
{"name": "repo-1", "overview": "Overview 1"},
70+
{"name": "repo-2", "overview": "Overview 2"},
71+
{"name": "repo-3", "overview": "Overview 3"}
72+
]
73+
mock_response.status_code = 200
74+
75+
mock_client = AsyncMock()
76+
mock_client.get = AsyncMock(return_value=mock_response)
77+
78+
mock_context = MagicMock(spec=CodeAliveContext)
79+
mock_context.client = mock_client
80+
mock_context.base_url = "https://app.codealive.ai"
81+
82+
mock_ctx = MagicMock(spec=Context)
83+
mock_ctx.request_context.lifespan_context = mock_context
84+
85+
result = await get_repo_overview(mock_ctx, ["repo-1", "repo-2", "repo-3"])
86+
87+
# Verify 3 repository blocks
88+
assert result.count('<repository') == 3
89+
assert '<repository name="repo-1">' in result
90+
assert '<repository name="repo-2">' in result
91+
assert '<repository name="repo-3">' in result
92+
assert 'Overview 1' in result
93+
assert 'Overview 2' in result
94+
assert 'Overview 3' in result
95+
96+
97+
@pytest.mark.asyncio
98+
@patch("tools.overview.get_api_key_from_context")
99+
async def test_get_repo_overview_no_data_sources(mock_get_api_key):
100+
"""Test retrieval without specifying data sources (all repos)."""
101+
mock_get_api_key.return_value = "test-api-key"
102+
103+
mock_response = MagicMock()
104+
mock_response.json.return_value = [
105+
{"name": "all-repo-1", "overview": "Overview 1"},
106+
{"name": "all-repo-2", "overview": "Overview 2"}
107+
]
108+
mock_response.status_code = 200
109+
110+
mock_client = AsyncMock()
111+
mock_client.get = AsyncMock(return_value=mock_response)
112+
113+
mock_context = MagicMock(spec=CodeAliveContext)
114+
mock_context.client = mock_client
115+
mock_context.base_url = "https://app.codealive.ai"
116+
117+
mock_ctx = MagicMock(spec=Context)
118+
mock_ctx.request_context.lifespan_context = mock_context
119+
120+
result = await get_repo_overview(mock_ctx, None)
121+
122+
# Verify API called without Names[] params
123+
mock_client.get.assert_called_once()
124+
call_args = mock_client.get.call_args
125+
# When data_sources is None, params should be empty dict
126+
assert call_args.kwargs['params'] == {}
127+
128+
# Verify returns overviews for all repos
129+
assert '<repository name="all-repo-1">' in result
130+
assert '<repository name="all-repo-2">' in result
131+
132+
133+
@pytest.mark.asyncio
134+
@patch("tools.overview.get_api_key_from_context")
135+
async def test_get_repo_overview_empty_result(mock_get_api_key):
136+
"""Test handling of empty API response."""
137+
mock_get_api_key.return_value = "test-api-key"
138+
139+
# Empty array response
140+
mock_response = MagicMock()
141+
mock_response.json.return_value = []
142+
mock_response.status_code = 200
143+
144+
mock_client = AsyncMock()
145+
mock_client.get = AsyncMock(return_value=mock_response)
146+
147+
mock_context = MagicMock(spec=CodeAliveContext)
148+
mock_context.client = mock_client
149+
mock_context.base_url = "https://app.codealive.ai"
150+
151+
mock_ctx = MagicMock(spec=Context)
152+
mock_ctx.request_context.lifespan_context = mock_context
153+
154+
result = await get_repo_overview(mock_ctx, ["nonexistent"])
155+
156+
# Should return empty root element
157+
assert '<repository_overviews' in result
158+
assert '</repository_overviews>' in result
159+
# Should not contain any repository elements
160+
assert '<repository name=' not in result
161+
162+
163+
@pytest.mark.asyncio
164+
@patch("tools.overview.handle_api_error")
165+
@patch("tools.overview.get_api_key_from_context")
166+
async def test_get_repo_overview_api_error(mock_get_api_key, mock_handle_error):
167+
"""Test handling of API errors."""
168+
mock_get_api_key.return_value = "test-api-key"
169+
mock_handle_error.return_value = "Error: API failed"
170+
171+
# Mock client that raises HTTPError
172+
mock_client = AsyncMock()
173+
mock_client.get = AsyncMock(side_effect=httpx.HTTPError("Connection failed"))
174+
175+
mock_context = MagicMock(spec=CodeAliveContext)
176+
mock_context.client = mock_client
177+
mock_context.base_url = "https://app.codealive.ai"
178+
179+
mock_ctx = MagicMock(spec=Context)
180+
mock_ctx.request_context.lifespan_context = mock_context
181+
182+
result = await get_repo_overview(mock_ctx, ["test-repo"])
183+
184+
# Verify error handling
185+
assert result == "Error: API failed"
186+
mock_handle_error.assert_called_once()
187+
# Verify handle_api_error called with correct parameters
188+
call_args = mock_handle_error.call_args
189+
assert call_args.args[0] == mock_ctx
190+
assert isinstance(call_args.args[1], httpx.HTTPError)
191+
assert call_args.args[2] == "get repository overview"
192+
193+
194+
@pytest.mark.asyncio
195+
@patch("tools.overview.get_api_key_from_context")
196+
async def test_get_repo_overview_auth_header(mock_get_api_key):
197+
"""Test that Authorization header is correctly set."""
198+
mock_get_api_key.return_value = "my-secret-key"
199+
200+
mock_response = MagicMock()
201+
mock_response.json.return_value = [{"name": "test", "overview": "Test"}]
202+
mock_response.status_code = 200
203+
204+
mock_client = AsyncMock()
205+
mock_client.get = AsyncMock(return_value=mock_response)
206+
207+
mock_context = MagicMock(spec=CodeAliveContext)
208+
mock_context.client = mock_client
209+
mock_context.base_url = "https://app.codealive.ai"
210+
211+
mock_ctx = MagicMock(spec=Context)
212+
mock_ctx.request_context.lifespan_context = mock_context
213+
214+
await get_repo_overview(mock_ctx, ["test"])
215+
216+
# Verify correct Authorization header
217+
mock_client.get.assert_called_once()
218+
call_args = mock_client.get.call_args
219+
assert call_args.kwargs['headers'] == {"Authorization": "Bearer my-secret-key"}
220+
221+
222+
@pytest.mark.asyncio
223+
@patch("tools.overview.normalize_data_source_names")
224+
@patch("tools.overview.get_api_key_from_context")
225+
async def test_get_repo_overview_data_source_normalization(mock_get_api_key, mock_normalize):
226+
"""Test that data sources are normalized (Claude Desktop serialization handling)."""
227+
mock_get_api_key.return_value = "test-api-key"
228+
# Simulate normalization converting string to list
229+
mock_normalize.return_value = ["normalized-repo"]
230+
231+
mock_response = MagicMock()
232+
mock_response.json.return_value = [{"name": "normalized-repo", "overview": "Test"}]
233+
mock_response.status_code = 200
234+
235+
mock_client = AsyncMock()
236+
mock_client.get = AsyncMock(return_value=mock_response)
237+
238+
mock_context = MagicMock(spec=CodeAliveContext)
239+
mock_context.client = mock_client
240+
mock_context.base_url = "https://app.codealive.ai"
241+
242+
mock_ctx = MagicMock(spec=Context)
243+
mock_ctx.request_context.lifespan_context = mock_context
244+
245+
# Call with string (simulating Claude Desktop serialization issue)
246+
await get_repo_overview(mock_ctx, "string-data-source")
247+
248+
# Verify normalize_data_source_names was called
249+
mock_normalize.assert_called_once_with("string-data-source")
250+
251+
252+
@pytest.mark.asyncio
253+
@patch("tools.overview.get_api_key_from_context")
254+
async def test_get_repo_overview_markdown_preservation(mock_get_api_key):
255+
"""Test that markdown formatting is preserved in XML output."""
256+
mock_get_api_key.return_value = "test-api-key"
257+
258+
# Mock response with rich markdown
259+
markdown_content = """# Purpose
260+
This is the **main** repository.
261+
262+
## Responsibilities
263+
- Handle *authentication*
264+
- Process `orders`
265+
266+
### Code Example
267+
```python
268+
def example():
269+
return True
270+
```
271+
272+
## Links
273+
See [documentation](https://example.com) for more.
274+
"""
275+
276+
mock_response = MagicMock()
277+
mock_response.json.return_value = [
278+
{"name": "rich-markdown-repo", "overview": markdown_content}
279+
]
280+
mock_response.status_code = 200
281+
282+
mock_client = AsyncMock()
283+
mock_client.get = AsyncMock(return_value=mock_response)
284+
285+
mock_context = MagicMock(spec=CodeAliveContext)
286+
mock_context.client = mock_client
287+
mock_context.base_url = "https://app.codealive.ai"
288+
289+
mock_ctx = MagicMock(spec=Context)
290+
mock_ctx.request_context.lifespan_context = mock_context
291+
292+
result = await get_repo_overview(mock_ctx, ["rich-markdown-repo"])
293+
294+
# Verify all markdown formatting is preserved
295+
assert '# Purpose' in result
296+
assert 'This is the **main** repository.' in result
297+
assert '## Responsibilities' in result
298+
assert '- Handle *authentication*' in result
299+
assert '- Process `orders`' in result
300+
assert '### Code Example' in result
301+
assert '```python' in result
302+
assert 'def example():' in result
303+
assert '## Links' in result
304+
assert 'See [documentation](https://example.com)' in result
305+
306+
# Verify no HTML escaping of markdown content (should not have &lt; etc)
307+
assert '&lt;' not in result or '```' in result # XML entities only in markdown might be ok
308+
assert '&gt;' not in result or '```' in result

0 commit comments

Comments
 (0)