Skip to content

Commit efbaa67

Browse files
committed
chore(mcp): Add more tests
1 parent fb4a994 commit efbaa67

File tree

7 files changed

+2027
-0
lines changed

7 files changed

+2027
-0
lines changed

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def mock_gitguardian_client():
2424
mock_client.get_source_by_name = AsyncMock(return_value=None)
2525
mock_client.list_source_incidents = AsyncMock(return_value={"data": [], "total_count": 0})
2626
mock_client.paginate_all = AsyncMock(return_value=[])
27+
mock_client.list_honeytokens = AsyncMock(return_value={"honeytokens": []})
2728

2829
# Patch get_client() to return our mock - this prevents the singleton from creating a real client
2930
with patch("gg_api_core.utils.get_client", return_value=mock_client):
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
from unittest.mock import AsyncMock, patch, MagicMock
2+
import subprocess
3+
4+
import pytest
5+
from gg_api_core.tools.find_current_source_id import find_current_source_id
6+
7+
8+
class TestFindCurrentSourceId:
9+
"""Tests for the find_current_source_id tool."""
10+
11+
@pytest.mark.asyncio
12+
async def test_find_current_source_id_exact_match(self, mock_gitguardian_client):
13+
"""
14+
GIVEN: A git repository with a remote URL
15+
WHEN: Finding the source_id with an exact match in GitGuardian
16+
THEN: The source_id and full source information are returned
17+
"""
18+
# Mock git command to return a remote URL
19+
with patch("subprocess.run") as mock_run:
20+
mock_run.return_value = MagicMock(
21+
stdout="https://github.com/GitGuardian/gg-mcp.git\n",
22+
returncode=0,
23+
)
24+
25+
# Mock the client response with exact match
26+
mock_response = {
27+
"id": "source_123",
28+
"full_name": "GitGuardian/gg-mcp",
29+
"url": "https://github.com/GitGuardian/gg-mcp",
30+
"monitored": True,
31+
}
32+
mock_gitguardian_client.get_source_by_name = AsyncMock(
33+
return_value=mock_response
34+
)
35+
36+
# Call the function
37+
result = await find_current_source_id()
38+
39+
# Verify git command was called
40+
mock_run.assert_called_once_with(
41+
["git", "config", "--get", "remote.origin.url"],
42+
capture_output=True,
43+
text=True,
44+
check=True,
45+
timeout=5,
46+
)
47+
48+
# Verify client was called with parsed repository name
49+
mock_gitguardian_client.get_source_by_name.assert_called_once_with(
50+
"GitGuardian/gg-mcp", return_all_on_no_match=True
51+
)
52+
53+
# Verify response
54+
assert result["repository_name"] == "GitGuardian/gg-mcp"
55+
assert result["source_id"] == "source_123"
56+
assert "message" in result
57+
58+
@pytest.mark.asyncio
59+
async def test_find_current_source_id_multiple_candidates(
60+
self, mock_gitguardian_client
61+
):
62+
"""
63+
GIVEN: A git repository URL that matches multiple sources
64+
WHEN: Finding the source_id
65+
THEN: All candidate sources are returned for user selection
66+
"""
67+
# Mock git command
68+
with patch("subprocess.run") as mock_run:
69+
mock_run.return_value = MagicMock(
70+
stdout="https://github.com/GitGuardian/test-repo.git\n",
71+
returncode=0,
72+
)
73+
74+
# Mock the client response with multiple candidates
75+
mock_response = [
76+
{
77+
"id": "source_1",
78+
"full_name": "GitGuardian/test-repo",
79+
"url": "https://github.com/GitGuardian/test-repo",
80+
"monitored": True,
81+
},
82+
{
83+
"id": "source_2",
84+
"full_name": "GitGuardian/test-repo-fork",
85+
"url": "https://github.com/GitGuardian/test-repo-fork",
86+
"monitored": False,
87+
},
88+
]
89+
mock_gitguardian_client.get_source_by_name = AsyncMock(
90+
return_value=mock_response
91+
)
92+
93+
# Call the function
94+
result = await find_current_source_id()
95+
96+
# Verify response
97+
assert result["repository_name"] == "GitGuardian/test-repo"
98+
assert "candidates" in result
99+
assert len(result["candidates"]) == 2
100+
assert "message" in result
101+
assert "suggestion" in result
102+
103+
@pytest.mark.asyncio
104+
async def test_find_current_source_id_no_match_with_fallback(
105+
self, mock_gitguardian_client
106+
):
107+
"""
108+
GIVEN: No match for full repository name but repo name alone matches
109+
WHEN: Finding the source_id with fallback search
110+
THEN: The source_id from fallback search is returned
111+
"""
112+
# Mock git command
113+
with patch("subprocess.run") as mock_run:
114+
mock_run.return_value = MagicMock(
115+
stdout="https://github.com/OrgName/repo-name.git\n",
116+
returncode=0,
117+
)
118+
119+
# Mock the client to return None first, then a match on fallback
120+
call_count = 0
121+
122+
async def mock_get_source(name, return_all_on_no_match=False):
123+
nonlocal call_count
124+
call_count += 1
125+
if call_count == 1:
126+
return [] # No match for full name
127+
else:
128+
return {
129+
"id": "source_fallback",
130+
"name": "repo-name",
131+
"url": "https://github.com/OrgName/repo-name",
132+
} # Match on repo name only
133+
134+
mock_gitguardian_client.get_source_by_name = mock_get_source
135+
136+
# Call the function
137+
result = await find_current_source_id()
138+
139+
# Verify response
140+
assert result["repository_name"] == "OrgName/repo-name"
141+
assert result["source_id"] == "source_fallback"
142+
143+
@pytest.mark.asyncio
144+
async def test_find_current_source_id_no_match_at_all(
145+
self, mock_gitguardian_client
146+
):
147+
"""
148+
GIVEN: No sources match the repository in GitGuardian
149+
WHEN: Finding the source_id
150+
THEN: An error is returned indicating repository not found
151+
"""
152+
# Mock git command
153+
with patch("subprocess.run") as mock_run:
154+
mock_run.return_value = MagicMock(
155+
stdout="https://github.com/Unknown/repo.git\n",
156+
returncode=0,
157+
)
158+
159+
# Mock the client to return empty results
160+
mock_gitguardian_client.get_source_by_name = AsyncMock(return_value=[])
161+
162+
# Call the function
163+
result = await find_current_source_id()
164+
165+
# Verify response
166+
assert result["repository_name"] == "Unknown/repo"
167+
assert "error" in result
168+
assert "not found in GitGuardian" in result["error"]
169+
170+
@pytest.mark.asyncio
171+
async def test_find_current_source_id_not_a_git_repo(
172+
self, mock_gitguardian_client
173+
):
174+
"""
175+
GIVEN: The current directory is not a git repository
176+
WHEN: Attempting to find the source_id
177+
THEN: An error is returned
178+
"""
179+
# Mock git command to raise an error
180+
with patch("subprocess.run") as mock_run:
181+
mock_run.side_effect = subprocess.CalledProcessError(
182+
128, "git", stderr="not a git repository"
183+
)
184+
185+
# Call the function
186+
result = await find_current_source_id()
187+
188+
# Verify error response
189+
assert "error" in result
190+
assert "Not a git repository" in result["error"]
191+
192+
@pytest.mark.asyncio
193+
async def test_find_current_source_id_git_timeout(self, mock_gitguardian_client):
194+
"""
195+
GIVEN: The git command times out
196+
WHEN: Attempting to find the source_id
197+
THEN: An error is returned
198+
"""
199+
# Mock git command to timeout
200+
with patch("subprocess.run") as mock_run:
201+
mock_run.side_effect = subprocess.TimeoutExpired("git", 5)
202+
203+
# Call the function
204+
result = await find_current_source_id()
205+
206+
# Verify error response
207+
assert "error" in result
208+
assert "timed out" in result["error"]
209+
210+
@pytest.mark.asyncio
211+
async def test_find_current_source_id_invalid_url(self, mock_gitguardian_client):
212+
"""
213+
GIVEN: A git URL that cannot be parsed
214+
WHEN: Attempting to find the source_id
215+
THEN: An error is returned
216+
"""
217+
# Mock git command to return invalid URL
218+
with patch("subprocess.run") as mock_run:
219+
mock_run.return_value = MagicMock(
220+
stdout="invalid-url-format\n",
221+
returncode=0,
222+
)
223+
224+
# Call the function
225+
result = await find_current_source_id()
226+
227+
# Verify error response
228+
assert "error" in result
229+
assert "Could not parse repository URL" in result["error"]
230+
231+
@pytest.mark.asyncio
232+
async def test_find_current_source_id_gitlab_url(self, mock_gitguardian_client):
233+
"""
234+
GIVEN: A GitLab repository URL
235+
WHEN: Finding the source_id
236+
THEN: The URL is correctly parsed and source_id is returned
237+
"""
238+
# Mock git command with GitLab URL
239+
with patch("subprocess.run") as mock_run:
240+
mock_run.return_value = MagicMock(
241+
stdout="https://gitlab.com/company/project.git\n",
242+
returncode=0,
243+
)
244+
245+
# Mock the client response
246+
mock_response = {
247+
"id": "source_gitlab",
248+
"full_name": "company/project",
249+
"url": "https://gitlab.com/company/project",
250+
}
251+
mock_gitguardian_client.get_source_by_name = AsyncMock(
252+
return_value=mock_response
253+
)
254+
255+
# Call the function
256+
result = await find_current_source_id()
257+
258+
# Verify response
259+
assert result["repository_name"] == "company/project"
260+
assert result["source_id"] == "source_gitlab"
261+
262+
@pytest.mark.asyncio
263+
async def test_find_current_source_id_ssh_url(self, mock_gitguardian_client):
264+
"""
265+
GIVEN: A git SSH URL
266+
WHEN: Finding the source_id
267+
THEN: The SSH URL is correctly parsed and source_id is returned
268+
"""
269+
# Mock git command with SSH URL
270+
with patch("subprocess.run") as mock_run:
271+
mock_run.return_value = MagicMock(
272+
stdout="git@github.com:GitGuardian/gg-mcp.git\n",
273+
returncode=0,
274+
)
275+
276+
# Mock the client response
277+
mock_response = {
278+
"id": "source_ssh",
279+
"full_name": "GitGuardian/gg-mcp",
280+
}
281+
mock_gitguardian_client.get_source_by_name = AsyncMock(
282+
return_value=mock_response
283+
)
284+
285+
# Call the function
286+
result = await find_current_source_id()
287+
288+
# Verify response
289+
assert result["repository_name"] == "GitGuardian/gg-mcp"
290+
assert result["source_id"] == "source_ssh"
291+
292+
@pytest.mark.asyncio
293+
async def test_find_current_source_id_client_error(self, mock_gitguardian_client):
294+
"""
295+
GIVEN: The GitGuardian client raises an exception
296+
WHEN: Attempting to find the source_id
297+
THEN: An error is returned
298+
"""
299+
# Mock git command
300+
with patch("subprocess.run") as mock_run:
301+
mock_run.return_value = MagicMock(
302+
stdout="https://github.com/GitGuardian/test.git\n",
303+
returncode=0,
304+
)
305+
306+
# Mock the client to raise an exception
307+
mock_gitguardian_client.get_source_by_name = AsyncMock(
308+
side_effect=Exception("API error")
309+
)
310+
311+
# Call the function
312+
result = await find_current_source_id()
313+
314+
# Verify error response
315+
assert "error" in result
316+
assert "Failed to find source_id" in result["error"]

0 commit comments

Comments
 (0)