Skip to content

Commit e93e24e

Browse files
pontemontiJohan Brobergclaude
authored
Code review fixes/pr 105 (#114)
* Fix inconsistent string representation for failed result with no errors Improves the __str__ method of OperationResult to return "Failed" instead of "Failed : " when there are no errors. Also removes the extra space before the colon for consistency. Addresses code review Comment 1. Co-Authored-By: Claude <noreply@anthropic.com> * Return defensive copy of errors list to protect singleton The errors property now returns a copy of the internal errors list to protect the singleton instance returned by success() from accidental modification. Updated docstring to document this behavior. Addresses code review Comment 2. Co-Authored-By: Claude <noreply@anthropic.com> * Use explicit None check for timestamp validation Changed timestamp validation from falsy check (if not self.timestamp) to explicit None check (if self.timestamp is None) for safer and more intentional validation behavior. Updated error message and test accordingly. Addresses code review Comment 3. Co-Authored-By: Claude <noreply@anthropic.com> * Move local imports to top of file Moved OperationError, OperationResult, ChatMessageRequest, and get_chat_history_endpoint imports from inside the send_chat_history method to the top of the file with the other imports. Removed the misleading comment about circular dependencies as there is no cycle in the import graph. Addresses code review Comment 4. Co-Authored-By: Claude <noreply@anthropic.com> * Change endpoint URL log level from INFO to DEBUG Detailed operational information like endpoint URLs should be logged at DEBUG level rather than INFO level. INFO level is reserved for higher-level operation status messages. Addresses code review Comment 5. Co-Authored-By: Claude <noreply@anthropic.com> * Use consistent async test pattern for validation tests Converted validation tests from synchronous methods using asyncio.run() to async methods with @pytest.mark.asyncio decorator for consistency with the other tests in the test suite. Addresses code review Comment 7. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Johan Broberg <johanb@microsoft.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent a79f8d9 commit e93e24e

6 files changed

Lines changed: 39 additions & 40 deletions

File tree

libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/operation_result.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ def errors(self) -> List[OperationError]:
4747
Get the list of errors that occurred during the operation.
4848
4949
Returns:
50-
List[OperationError]: List of operation errors.
50+
List[OperationError]: A copy of the list of operation errors.
51+
The returned list is a defensive copy to protect the singleton
52+
instance returned by success() from accidental modification.
5153
"""
52-
return self._errors
54+
return list(self._errors)
5355

5456
@staticmethod
5557
def success() -> "OperationResult":
@@ -88,4 +90,4 @@ def __str__(self) -> str:
8890
return "Succeeded"
8991
else:
9092
error_messages = ", ".join(str(error.message) for error in self._errors)
91-
return f"Failed : {error_messages}"
93+
return f"Failed: {error_messages}" if error_messages else "Failed"

libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/models/chat_history_message.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ def __post_init__(self):
3838
raise ValueError("role cannot be empty")
3939
if not self.content:
4040
raise ValueError("content cannot be empty")
41-
if not self.timestamp:
42-
raise ValueError("timestamp cannot be empty")
41+
if self.timestamp is None:
42+
raise ValueError("timestamp cannot be None")
4343

4444
def to_dict(self):
4545
"""

libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@
3131
from microsoft_agents.hosting.core import TurnContext
3232

3333
# Local imports
34-
from ..models import ChatHistoryMessage, MCPServerConfig, ToolOptions
34+
from ..models import ChatHistoryMessage, ChatMessageRequest, MCPServerConfig, ToolOptions
3535
from ..utils import Constants
36-
from ..utils.utility import get_tooling_gateway_for_digital_worker, build_mcp_server_url
36+
from ..utils.utility import (
37+
get_tooling_gateway_for_digital_worker,
38+
build_mcp_server_url,
39+
get_chat_history_endpoint,
40+
)
3741

3842
# Runtime Imports
43+
from microsoft_agents_a365.runtime import OperationError, OperationResult
3944
from microsoft_agents_a365.runtime.utility import Utility as RuntimeUtility
4045

4146

@@ -520,12 +525,6 @@ async def send_chat_history(
520525
Raises:
521526
ValueError: If required parameters are invalid or empty.
522527
"""
523-
# Import here to avoid circular dependency
524-
from microsoft_agents_a365.runtime import OperationError, OperationResult
525-
526-
from ..models import ChatMessageRequest
527-
from ..utils.utility import get_chat_history_endpoint
528-
529528
# Validate input parameters
530529
if not turn_context:
531530
raise ValueError("turn_context cannot be empty or None")
@@ -560,7 +559,7 @@ async def send_chat_history(
560559
# Get the endpoint URL
561560
endpoint = get_chat_history_endpoint()
562561

563-
self._logger.info(f"Sending chat history to endpoint: {endpoint}")
562+
self._logger.debug(f"Sending chat history to endpoint: {endpoint}")
564563

565564
# Create the request payload
566565
request = ChatMessageRequest(

tests/runtime/test_operation_result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_operation_result_failed_string_representation_no_errors(self):
8585
result = OperationResult.failed()
8686

8787
# Assert
88-
assert str(result) == "Failed : "
88+
assert str(result) == "Failed"
8989

9090
def test_operation_result_failed_string_representation_with_errors(self):
9191
"""Test that failed OperationResult with errors has correct string representation."""

tests/tooling/models/test_chat_history_message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_chat_history_message_requires_non_empty_content(self):
7070
def test_chat_history_message_requires_timestamp(self):
7171
"""Test that ChatHistoryMessage requires a timestamp."""
7272
# Act & Assert
73-
with pytest.raises(ValueError, match="timestamp cannot be empty"):
73+
with pytest.raises(ValueError, match="timestamp cannot be None"):
7474
ChatHistoryMessage("msg-001", "user", "Test content", None)
7575

7676
def test_chat_history_message_supports_system_role(self):

tests/tooling/services/test_send_chat_history.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -124,35 +124,37 @@ async def test_send_chat_history_with_options(
124124
# Assert
125125
assert result.succeeded is True
126126

127-
def test_send_chat_history_validates_turn_context(self, service, chat_history_messages):
127+
@pytest.mark.asyncio
128+
async def test_send_chat_history_validates_turn_context(self, service, chat_history_messages):
128129
"""Test that send_chat_history validates turn_context parameter."""
129130
# Act & Assert
130131
with pytest.raises(ValueError, match="turn_context cannot be empty or None"):
131-
import asyncio
132-
133-
asyncio.run(service.send_chat_history(None, chat_history_messages))
132+
await service.send_chat_history(None, chat_history_messages)
134133

135-
def test_send_chat_history_validates_chat_history_messages(self, service, mock_turn_context):
134+
@pytest.mark.asyncio
135+
async def test_send_chat_history_validates_chat_history_messages(
136+
self, service, mock_turn_context
137+
):
136138
"""Test that send_chat_history validates chat_history_messages parameter."""
137139
# Act & Assert
138140
with pytest.raises(ValueError, match="chat_history_messages cannot be empty or None"):
139-
import asyncio
141+
await service.send_chat_history(mock_turn_context, None)
140142

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):
143+
@pytest.mark.asyncio
144+
async def test_send_chat_history_validates_activity(self, service, chat_history_messages):
144145
"""Test that send_chat_history validates turn_context.activity."""
145146
# Arrange
146147
mock_context = Mock()
147148
mock_context.activity = None
148149

149150
# Act & Assert
150151
with pytest.raises(ValueError, match="turn_context.activity cannot be None"):
151-
import asyncio
152-
153-
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
152+
await service.send_chat_history(mock_context, chat_history_messages)
154153

155-
def test_send_chat_history_validates_conversation_id(self, service, chat_history_messages):
154+
@pytest.mark.asyncio
155+
async def test_send_chat_history_validates_conversation_id(
156+
self, service, chat_history_messages
157+
):
156158
"""Test that send_chat_history validates conversation_id from activity."""
157159
# Arrange
158160
mock_context = Mock()
@@ -167,11 +169,10 @@ def test_send_chat_history_validates_conversation_id(self, service, chat_history
167169
ValueError,
168170
match="conversation_id cannot be empty or None.*turn_context.activity.conversation.id",
169171
):
170-
import asyncio
172+
await service.send_chat_history(mock_context, chat_history_messages)
171173

172-
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
173-
174-
def test_send_chat_history_validates_message_id(self, service, chat_history_messages):
174+
@pytest.mark.asyncio
175+
async def test_send_chat_history_validates_message_id(self, service, chat_history_messages):
175176
"""Test that send_chat_history validates message_id from activity."""
176177
# Arrange
177178
mock_context = Mock()
@@ -187,11 +188,10 @@ def test_send_chat_history_validates_message_id(self, service, chat_history_mess
187188
with pytest.raises(
188189
ValueError, match="message_id cannot be empty or None.*turn_context.activity.id"
189190
):
190-
import asyncio
191+
await service.send_chat_history(mock_context, chat_history_messages)
191192

192-
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
193-
194-
def test_send_chat_history_validates_user_message(self, service, chat_history_messages):
193+
@pytest.mark.asyncio
194+
async def test_send_chat_history_validates_user_message(self, service, chat_history_messages):
195195
"""Test that send_chat_history validates user_message from activity."""
196196
# Arrange
197197
mock_context = Mock()
@@ -207,9 +207,7 @@ def test_send_chat_history_validates_user_message(self, service, chat_history_me
207207
with pytest.raises(
208208
ValueError, match="user_message cannot be empty or None.*turn_context.activity.text"
209209
):
210-
import asyncio
211-
212-
asyncio.run(service.send_chat_history(mock_context, chat_history_messages))
210+
await service.send_chat_history(mock_context, chat_history_messages)
213211

214212
@pytest.mark.asyncio
215213
async def test_send_chat_history_handles_client_error(

0 commit comments

Comments
 (0)