Skip to content

Commit 76050f5

Browse files
author
Jesus Terrazas
committed
test additions
1 parent 1e191b4 commit 76050f5

4 files changed

Lines changed: 382 additions & 1 deletion

File tree

tests/RUNNING_TESTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ uv pip install pytest pytest-asyncio pytest-mock pytest-cov pytest-html wrapt
3232
uv pip install -e libraries/microsoft-agents-a365-runtime -e libraries/microsoft-agents-a365-notifications -e libraries/microsoft-agents-a365-observability-core -e libraries/microsoft-agents-a365-tooling
3333
3434
# Framework extension libraries
35-
uv pip install -e libraries/microsoft-agents-a365-observability-extensions-langchain -e libraries/microsoft-agents-a365-observability-extensions-openai -e libraries/microsoft-agents-a365-observability-extensions-semantickernel -e libraries/microsoft-agents-a365-observability-extensions-agentframework -e libraries/microsoft-agents-a365-tooling-extensions-agentframework -e libraries/microsoft-agents-a365-tooling-extensions-azureaifoundry -e libraries/microsoft-agents-a365-tooling-extensions-openai -e libraries/microsoft-agents-a365-tooling-extensions-semantickernel
35+
uv pip install -e libraries/microsoft-agents-a365-observability-extensions-langchain -e libraries/microsoft-agents-a365-observability-extensions-openai -e libraries/microsoft-agents-a365-observability-extensions-semantickernel -e libraries/microsoft-agents-a365-observability-extensions-agentframework -e libraries/microsoft-agents-a365-tooling-extensions-agentframework -e libraries/microsoft-agents-a365-tooling-extensions-azureaifoundry -e libraries/microsoft-agents-a365-tooling-extensions-openai -e libraries/microsoft-agents-a365-tooling-extensions-semantickernel -e libraries/microsoft-agents-a365-tooling-extensions-google
3636
```
3737

3838
---
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Unit tests for McpToolRegistrationService in Google ADK extension."""
5+
6+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
7+
8+
import pytest
9+
10+
11+
class TestMcpToolRegistrationServiceInit:
12+
"""Tests for McpToolRegistrationService initialization."""
13+
14+
def test_init_default_logger(self):
15+
"""Test initialization with default logger."""
16+
with patch(
17+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
18+
):
19+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
20+
21+
service = McpToolRegistrationService()
22+
23+
assert service._logger is not None
24+
assert service.config_service is not None
25+
assert service._connected_servers == []
26+
27+
def test_init_custom_logger(self):
28+
"""Test initialization with custom logger."""
29+
import logging
30+
31+
custom_logger = logging.getLogger("custom_test_logger")
32+
33+
with patch(
34+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
35+
):
36+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
37+
38+
service = McpToolRegistrationService(logger=custom_logger)
39+
40+
assert service._logger is custom_logger
41+
42+
def test_orchestrator_name(self):
43+
"""Test that orchestrator name is set correctly."""
44+
with patch(
45+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
46+
):
47+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
48+
49+
assert McpToolRegistrationService._orchestrator_name == "GoogleADK"
50+
51+
52+
class TestAddToolServersToAgent:
53+
"""Tests for add_tool_servers_to_agent method."""
54+
55+
@pytest.fixture
56+
def mock_agent(self):
57+
"""Create a mock Google ADK Agent."""
58+
mock = MagicMock()
59+
mock.name = "test-agent"
60+
mock.model = "gemini-pro"
61+
mock.description = "A test agent"
62+
mock.tools = []
63+
return mock
64+
65+
@pytest.fixture
66+
def mock_authorization(self):
67+
"""Create a mock Authorization object."""
68+
mock = AsyncMock()
69+
mock_token = MagicMock()
70+
mock_token.token = "test-token-123"
71+
mock.exchange_token = AsyncMock(return_value=mock_token)
72+
return mock
73+
74+
@pytest.fixture
75+
def mock_turn_context(self):
76+
"""Create a mock TurnContext."""
77+
mock = MagicMock()
78+
mock_activity = MagicMock()
79+
mock_conversation = MagicMock()
80+
mock_conversation.id = "conv-123"
81+
mock_activity.conversation = mock_conversation
82+
mock.activity = mock_activity
83+
return mock
84+
85+
@pytest.fixture
86+
def mock_server_config(self):
87+
"""Create a mock MCP server configuration."""
88+
mock = MagicMock()
89+
mock.mcp_server_name = "test-server"
90+
mock.mcp_server_unique_name = "https://test-server.example.com/mcp"
91+
return mock
92+
93+
@pytest.mark.asyncio
94+
async def test_add_tool_servers_exchanges_token_when_not_provided(
95+
self, mock_agent, mock_authorization, mock_turn_context
96+
):
97+
"""Test that token is exchanged when not provided."""
98+
with patch(
99+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
100+
) as mock_config_service_class, patch(
101+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Utility"
102+
) as mock_utility, patch(
103+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.get_mcp_platform_authentication_scope"
104+
) as mock_get_scope, patch(
105+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Agent"
106+
) as mock_agent_class:
107+
# Setup mocks
108+
mock_get_scope.return_value = ["https://test.scope/.default"]
109+
mock_utility.resolve_agent_identity.return_value = "agent-123"
110+
mock_utility.get_user_agent_header.return_value = "Agent365SDK/1.0"
111+
112+
mock_config_service = AsyncMock()
113+
mock_config_service.list_tool_servers = AsyncMock(return_value=[])
114+
mock_config_service_class.return_value = mock_config_service
115+
116+
mock_agent_class.return_value = mock_agent
117+
118+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
119+
120+
service = McpToolRegistrationService()
121+
122+
# Act
123+
await service.add_tool_servers_to_agent(
124+
agent=mock_agent,
125+
auth=mock_authorization,
126+
auth_handler_name="graph",
127+
context=mock_turn_context,
128+
)
129+
130+
# Assert
131+
mock_authorization.exchange_token.assert_called_once()
132+
133+
@pytest.mark.asyncio
134+
async def test_add_tool_servers_uses_provided_token(
135+
self, mock_agent, mock_authorization, mock_turn_context
136+
):
137+
"""Test that provided token is used instead of exchanging."""
138+
with patch(
139+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
140+
) as mock_config_service_class, patch(
141+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Utility"
142+
) as mock_utility, patch(
143+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Agent"
144+
) as mock_agent_class:
145+
# Setup mocks
146+
mock_utility.resolve_agent_identity.return_value = "agent-123"
147+
mock_utility.get_user_agent_header.return_value = "Agent365SDK/1.0"
148+
149+
mock_config_service = AsyncMock()
150+
mock_config_service.list_tool_servers = AsyncMock(return_value=[])
151+
mock_config_service_class.return_value = mock_config_service
152+
153+
mock_agent_class.return_value = mock_agent
154+
155+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
156+
157+
service = McpToolRegistrationService()
158+
159+
# Act
160+
await service.add_tool_servers_to_agent(
161+
agent=mock_agent,
162+
auth=mock_authorization,
163+
auth_handler_name="graph",
164+
context=mock_turn_context,
165+
auth_token="pre-existing-token",
166+
)
167+
168+
# Assert - exchange_token should NOT be called
169+
mock_authorization.exchange_token.assert_not_called()
170+
171+
@pytest.mark.asyncio
172+
async def test_add_tool_servers_creates_mcp_toolsets(
173+
self, mock_agent, mock_authorization, mock_turn_context, mock_server_config
174+
):
175+
"""Test that MCP toolsets are created for each server config."""
176+
with patch(
177+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
178+
) as mock_config_service_class, patch(
179+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Utility"
180+
) as mock_utility, patch(
181+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolset"
182+
) as mock_toolset_class, patch(
183+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Agent"
184+
) as mock_agent_class:
185+
# Setup mocks
186+
mock_utility.resolve_agent_identity.return_value = "agent-123"
187+
mock_utility.get_user_agent_header.return_value = "Agent365SDK/1.0"
188+
189+
mock_config_service = AsyncMock()
190+
mock_config_service.list_tool_servers = AsyncMock(return_value=[mock_server_config])
191+
mock_config_service_class.return_value = mock_config_service
192+
193+
mock_toolset = MagicMock()
194+
mock_toolset_class.return_value = mock_toolset
195+
196+
mock_agent_class.return_value = mock_agent
197+
198+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
199+
200+
service = McpToolRegistrationService()
201+
202+
# Act
203+
await service.add_tool_servers_to_agent(
204+
agent=mock_agent,
205+
auth=mock_authorization,
206+
auth_handler_name="graph",
207+
context=mock_turn_context,
208+
auth_token="test-token",
209+
)
210+
211+
# Assert
212+
mock_toolset_class.assert_called_once()
213+
assert mock_toolset in service._connected_servers
214+
215+
@pytest.mark.asyncio
216+
async def test_add_tool_servers_returns_new_agent(
217+
self, mock_agent, mock_authorization, mock_turn_context
218+
):
219+
"""Test that a new Agent instance is returned."""
220+
with patch(
221+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
222+
) as mock_config_service_class, patch(
223+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Utility"
224+
) as mock_utility, patch(
225+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Agent"
226+
) as mock_agent_class:
227+
# Setup mocks
228+
mock_utility.resolve_agent_identity.return_value = "agent-123"
229+
mock_utility.get_user_agent_header.return_value = "Agent365SDK/1.0"
230+
231+
mock_config_service = AsyncMock()
232+
mock_config_service.list_tool_servers = AsyncMock(return_value=[])
233+
mock_config_service_class.return_value = mock_config_service
234+
235+
new_agent = MagicMock()
236+
mock_agent_class.return_value = new_agent
237+
238+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
239+
240+
service = McpToolRegistrationService()
241+
242+
# Act
243+
result = await service.add_tool_servers_to_agent(
244+
agent=mock_agent,
245+
auth=mock_authorization,
246+
auth_handler_name="graph",
247+
context=mock_turn_context,
248+
auth_token="test-token",
249+
)
250+
251+
# Assert
252+
assert result == new_agent
253+
mock_agent_class.assert_called_once_with(
254+
name=mock_agent.name,
255+
model=mock_agent.model,
256+
description=mock_agent.description,
257+
tools=[],
258+
)
259+
260+
@pytest.mark.asyncio
261+
async def test_add_tool_servers_handles_toolset_creation_error(
262+
self, mock_agent, mock_authorization, mock_turn_context, mock_server_config
263+
):
264+
"""Test that errors during toolset creation are handled gracefully."""
265+
with patch(
266+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
267+
) as mock_config_service_class, patch(
268+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Utility"
269+
) as mock_utility, patch(
270+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolset"
271+
) as mock_toolset_class, patch(
272+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.Agent"
273+
) as mock_agent_class:
274+
# Setup mocks
275+
mock_utility.resolve_agent_identity.return_value = "agent-123"
276+
mock_utility.get_user_agent_header.return_value = "Agent365SDK/1.0"
277+
278+
mock_config_service = AsyncMock()
279+
mock_config_service.list_tool_servers = AsyncMock(return_value=[mock_server_config])
280+
mock_config_service_class.return_value = mock_config_service
281+
282+
# Make toolset creation fail
283+
mock_toolset_class.side_effect = Exception("Connection failed")
284+
285+
mock_agent_class.return_value = mock_agent
286+
287+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
288+
289+
service = McpToolRegistrationService()
290+
291+
# Act - should not raise
292+
result = await service.add_tool_servers_to_agent(
293+
agent=mock_agent,
294+
auth=mock_authorization,
295+
auth_handler_name="graph",
296+
context=mock_turn_context,
297+
auth_token="test-token",
298+
)
299+
300+
# Assert - should still return an agent, just without the failed toolset
301+
assert result is not None
302+
assert len(service._connected_servers) == 0
303+
304+
305+
class TestCleanup:
306+
"""Tests for cleanup method."""
307+
308+
@pytest.mark.asyncio
309+
async def test_cleanup_closes_connected_servers(self):
310+
"""Test that cleanup closes all connected servers."""
311+
with patch(
312+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
313+
):
314+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
315+
316+
service = McpToolRegistrationService()
317+
318+
# Add mock connected servers
319+
mock_toolset1 = AsyncMock()
320+
mock_toolset1.close = AsyncMock()
321+
mock_toolset2 = AsyncMock()
322+
mock_toolset2.close = AsyncMock()
323+
324+
service._connected_servers = [mock_toolset1, mock_toolset2]
325+
326+
# Act
327+
await service.cleanup()
328+
329+
# Assert
330+
mock_toolset1.close.assert_called_once()
331+
mock_toolset2.close.assert_called_once()
332+
assert service._connected_servers == []
333+
334+
@pytest.mark.asyncio
335+
async def test_cleanup_handles_close_errors(self):
336+
"""Test that cleanup handles errors during close gracefully."""
337+
with patch(
338+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
339+
):
340+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
341+
342+
service = McpToolRegistrationService()
343+
344+
# Add mock connected server that raises on close
345+
mock_toolset = AsyncMock()
346+
mock_toolset.close = AsyncMock(side_effect=Exception("Close failed"))
347+
348+
service._connected_servers = [mock_toolset]
349+
350+
# Act - should not raise
351+
await service.cleanup()
352+
353+
# Assert - list should still be cleared
354+
assert service._connected_servers == []
355+
356+
@pytest.mark.asyncio
357+
async def test_cleanup_handles_servers_without_close(self):
358+
"""Test that cleanup handles servers without close method."""
359+
with patch(
360+
"microsoft_agents_a365.tooling.extensions.google.services.mcp_tool_registration_service.McpToolServerConfigurationService"
361+
):
362+
from microsoft_agents_a365.tooling.extensions.google import McpToolRegistrationService
363+
364+
service = McpToolRegistrationService()
365+
366+
# Add mock connected server without close method
367+
mock_toolset = MagicMock(spec=[]) # Empty spec = no methods
368+
369+
service._connected_servers = [mock_toolset]
370+
371+
# Act - should not raise
372+
await service.cleanup()
373+
374+
# Assert - list should still be cleared
375+
assert service._connected_servers == []

0 commit comments

Comments
 (0)