Skip to content

Commit 4443b7d

Browse files
Copilotpontemonti
andcommitted
Add comprehensive unit tests for send_chat_history method
Co-authored-by: pontemonti <7850950+pontemonti@users.noreply.github.com>
1 parent 38206e1 commit 4443b7d

2 files changed

Lines changed: 286 additions & 0 deletions

File tree

tests/tooling/services/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Unit tests for send_chat_history method in McpToolServerConfigurationService."""
5+
6+
from datetime import UTC, datetime
7+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
8+
9+
import pytest
10+
from microsoft_agents_a365.tooling.models import ChatHistoryMessage
11+
from microsoft_agents_a365.tooling.services import McpToolServerConfigurationService
12+
13+
14+
class TestSendChatHistory:
15+
"""Tests for send_chat_history method."""
16+
17+
@pytest.fixture
18+
def mock_turn_context(self):
19+
"""Create a mock TurnContext."""
20+
mock_context = Mock()
21+
mock_activity = Mock()
22+
mock_conversation = Mock()
23+
24+
mock_conversation.id = "conv-123"
25+
mock_activity.conversation = mock_conversation
26+
mock_activity.id = "msg-456"
27+
mock_activity.text = "Hello, how are you?"
28+
29+
mock_context.activity = mock_activity
30+
return mock_context
31+
32+
@pytest.fixture
33+
def chat_history_messages(self):
34+
"""Create sample chat history messages."""
35+
timestamp = datetime.now(UTC)
36+
return [
37+
ChatHistoryMessage("msg-1", "user", "Hello", timestamp),
38+
ChatHistoryMessage("msg-2", "assistant", "Hi there!", timestamp),
39+
]
40+
41+
@pytest.fixture
42+
def service(self):
43+
"""Create McpToolServerConfigurationService instance."""
44+
return McpToolServerConfigurationService()
45+
46+
@pytest.mark.asyncio
47+
async def test_send_chat_history_success(
48+
self, service, mock_turn_context, chat_history_messages
49+
):
50+
"""Test successful send_chat_history call."""
51+
# Arrange
52+
mock_response = AsyncMock()
53+
mock_response.status = 200
54+
mock_response.text = AsyncMock(return_value="OK")
55+
56+
# Mock aiohttp.ClientSession
57+
with patch("aiohttp.ClientSession") as mock_session:
58+
mock_session_instance = MagicMock()
59+
mock_post = AsyncMock()
60+
mock_post.__aenter__.return_value = mock_response
61+
mock_session_instance.post.return_value = mock_post
62+
mock_session.return_value.__aenter__.return_value = mock_session_instance
63+
64+
# Act
65+
result = await service.send_chat_history(mock_turn_context, chat_history_messages)
66+
67+
# Assert
68+
assert result.succeeded is True
69+
assert len(result.errors) == 0
70+
71+
@pytest.mark.asyncio
72+
async def test_send_chat_history_http_error(
73+
self, service, mock_turn_context, chat_history_messages
74+
):
75+
"""Test send_chat_history with HTTP error response."""
76+
# Arrange
77+
mock_response = AsyncMock()
78+
mock_response.status = 500
79+
mock_response.text = AsyncMock(return_value="Internal Server Error")
80+
81+
# Mock aiohttp.ClientSession
82+
with patch("aiohttp.ClientSession") as mock_session:
83+
mock_session_instance = MagicMock()
84+
mock_post = AsyncMock()
85+
mock_post.__aenter__.return_value = mock_response
86+
mock_session_instance.post.return_value = mock_post
87+
mock_session.return_value.__aenter__.return_value = mock_session_instance
88+
89+
# Act
90+
result = await service.send_chat_history(mock_turn_context, chat_history_messages)
91+
92+
# Assert
93+
assert result.succeeded is False
94+
assert len(result.errors) == 1
95+
assert "HTTP 500" in str(result.errors[0].message)
96+
97+
@pytest.mark.asyncio
98+
async def test_send_chat_history_with_options(
99+
self, service, mock_turn_context, chat_history_messages
100+
):
101+
"""Test send_chat_history with custom options."""
102+
# Arrange
103+
from microsoft_agents_a365.tooling.models import ToolOptions
104+
105+
options = ToolOptions(orchestrator_name="TestOrchestrator")
106+
107+
mock_response = AsyncMock()
108+
mock_response.status = 200
109+
mock_response.text = AsyncMock(return_value="OK")
110+
111+
# Mock aiohttp.ClientSession
112+
with patch("aiohttp.ClientSession") as mock_session:
113+
mock_session_instance = MagicMock()
114+
mock_post = AsyncMock()
115+
mock_post.__aenter__.return_value = mock_response
116+
mock_session_instance.post.return_value = mock_post
117+
mock_session.return_value.__aenter__.return_value = mock_session_instance
118+
119+
# Act
120+
result = await service.send_chat_history(
121+
mock_turn_context, chat_history_messages, options
122+
)
123+
124+
# Assert
125+
assert result.succeeded is True
126+
127+
def test_send_chat_history_validates_turn_context(self, service, chat_history_messages):
128+
"""Test that send_chat_history validates turn_context parameter."""
129+
# Act & Assert
130+
with pytest.raises(ValueError, match="turn_context cannot be empty or None"):
131+
import asyncio
132+
asyncio.run(service.send_chat_history(None, chat_history_messages))
133+
134+
def test_send_chat_history_validates_chat_history_messages(
135+
self, service, mock_turn_context
136+
):
137+
"""Test that send_chat_history validates chat_history_messages parameter."""
138+
# Act & Assert
139+
with pytest.raises(ValueError, match="chat_history_messages cannot be empty or None"):
140+
import asyncio
141+
asyncio.run(service.send_chat_history(mock_turn_context, None))
142+
143+
def test_send_chat_history_validates_activity(self, service, chat_history_messages):
144+
"""Test that send_chat_history validates turn_context.activity."""
145+
# Arrange
146+
mock_context = Mock()
147+
mock_context.activity = None
148+
149+
# Act & Assert
150+
with pytest.raises(ValueError, match="turn_context.activity cannot be None"):
151+
import asyncio
152+
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
153+
154+
def test_send_chat_history_validates_conversation_id(self, service, chat_history_messages):
155+
"""Test that send_chat_history validates conversation_id from activity."""
156+
# Arrange
157+
mock_context = Mock()
158+
mock_activity = Mock()
159+
mock_activity.conversation = None
160+
mock_activity.id = "msg-123"
161+
mock_activity.text = "Test message"
162+
mock_context.activity = mock_activity
163+
164+
# Act & Assert
165+
with pytest.raises(
166+
ValueError, match="conversation_id cannot be empty or None.*turn_context.activity.conversation.id"
167+
):
168+
import asyncio
169+
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
170+
171+
def test_send_chat_history_validates_message_id(self, service, chat_history_messages):
172+
"""Test that send_chat_history validates message_id from activity."""
173+
# Arrange
174+
mock_context = Mock()
175+
mock_activity = Mock()
176+
mock_conversation = Mock()
177+
mock_conversation.id = "conv-123"
178+
mock_activity.conversation = mock_conversation
179+
mock_activity.id = None
180+
mock_activity.text = "Test message"
181+
mock_context.activity = mock_activity
182+
183+
# Act & Assert
184+
with pytest.raises(
185+
ValueError, match="message_id cannot be empty or None.*turn_context.activity.id"
186+
):
187+
import asyncio
188+
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
189+
190+
def test_send_chat_history_validates_user_message(self, service, chat_history_messages):
191+
"""Test that send_chat_history validates user_message from activity."""
192+
# Arrange
193+
mock_context = Mock()
194+
mock_activity = Mock()
195+
mock_conversation = Mock()
196+
mock_conversation.id = "conv-123"
197+
mock_activity.conversation = mock_conversation
198+
mock_activity.id = "msg-123"
199+
mock_activity.text = None
200+
mock_context.activity = mock_activity
201+
202+
# Act & Assert
203+
with pytest.raises(
204+
ValueError, match="user_message cannot be empty or None.*turn_context.activity.text"
205+
):
206+
import asyncio
207+
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
208+
209+
@pytest.mark.asyncio
210+
async def test_send_chat_history_handles_client_error(
211+
self, service, mock_turn_context, chat_history_messages
212+
):
213+
"""Test send_chat_history handles aiohttp.ClientError."""
214+
# Arrange
215+
import aiohttp
216+
217+
# Mock aiohttp.ClientSession to raise ClientError
218+
with patch("aiohttp.ClientSession") as mock_session:
219+
mock_session_instance = MagicMock()
220+
mock_session_instance.post.side_effect = aiohttp.ClientError("Connection failed")
221+
mock_session.return_value.__aenter__.return_value = mock_session_instance
222+
223+
# Act
224+
result = await service.send_chat_history(mock_turn_context, chat_history_messages)
225+
226+
# Assert
227+
assert result.succeeded is False
228+
assert len(result.errors) == 1
229+
assert "Connection failed" in str(result.errors[0].message)
230+
231+
@pytest.mark.asyncio
232+
async def test_send_chat_history_handles_timeout(
233+
self, service, mock_turn_context, chat_history_messages
234+
):
235+
"""Test send_chat_history handles timeout."""
236+
# Mock aiohttp.ClientSession to raise TimeoutError
237+
with patch("aiohttp.ClientSession") as mock_session:
238+
mock_session_instance = AsyncMock()
239+
mock_session.return_value.__aenter__.return_value = mock_session_instance
240+
mock_session_instance.post.side_effect = TimeoutError()
241+
242+
# Act
243+
result = await service.send_chat_history(mock_turn_context, chat_history_messages)
244+
245+
# Assert
246+
assert result.succeeded is False
247+
assert len(result.errors) == 1
248+
249+
@pytest.mark.asyncio
250+
async def test_send_chat_history_sends_correct_payload(
251+
self, service, mock_turn_context, chat_history_messages
252+
):
253+
"""Test that send_chat_history sends the correct payload."""
254+
# Arrange
255+
mock_response = AsyncMock()
256+
mock_response.status = 200
257+
mock_response.text = AsyncMock(return_value="OK")
258+
259+
# Mock aiohttp.ClientSession
260+
with patch("aiohttp.ClientSession") as mock_session:
261+
mock_session_instance = MagicMock()
262+
mock_post = AsyncMock()
263+
mock_post.__aenter__.return_value = mock_response
264+
mock_session_instance.post.return_value = mock_post
265+
mock_session.return_value.__aenter__.return_value = mock_session_instance
266+
267+
# Act
268+
await service.send_chat_history(mock_turn_context, chat_history_messages)
269+
270+
# Assert
271+
# Verify post was called
272+
assert mock_session_instance.post.called
273+
call_args = mock_session_instance.post.call_args
274+
275+
# Verify the endpoint
276+
assert "real-time-threat-protection/chat-message" in call_args[0][0]
277+
278+
# Verify headers
279+
headers = call_args[1]["headers"]
280+
assert "User-Agent" in headers or "user-agent" in str(headers).lower()
281+
assert "Content-Type" in headers
282+
283+
# Verify data is JSON
284+
data = call_args[1]["data"]
285+
assert data is not None

0 commit comments

Comments
 (0)