11"""Tests for job_attachment_wrapper module."""
22
3+ import json
34import uuid
45from typing import Any , cast
56from unittest .mock import AsyncMock , MagicMock , patch
67
78import pytest
9+ from langchain_core .messages import ToolMessage
810from langchain_core .messages .tool import ToolCall
911from langchain_core .tools import BaseTool
1012from langgraph .types import Command
@@ -85,7 +87,14 @@ def create_mock_attachment(self, attachment_id: uuid.UUID) -> MagicMock:
8587 def mock_tool (self ):
8688 """Create a mock tool."""
8789 tool = MagicMock (spec = BaseTool )
88- tool .ainvoke = AsyncMock (return_value = {"result" : "success" })
90+
91+ async def ainvoke_side_effect (call ):
92+ tool_message = ToolMessage (
93+ content = "{'result': 'success'}" , tool_call_id = call .get ("id" , "call_123" )
94+ )
95+ return tool_message
96+
97+ tool .ainvoke = AsyncMock (side_effect = ainvoke_side_effect )
8998 return tool
9099
91100 @pytest .fixture
@@ -129,7 +138,7 @@ async def test_tool_without_args_schema(
129138
130139 assert isinstance (result , Command )
131140 self .assert_command_success (result )
132- mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call [ "args" ] )
141+ mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call )
133142
134143 @pytest .mark .asyncio
135144 async def test_tool_with_dict_args_schema (
@@ -143,7 +152,7 @@ async def test_tool_with_dict_args_schema(
143152
144153 assert isinstance (result , Command )
145154 self .assert_command_success (result )
146- mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call [ "args" ] )
155+ mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call )
147156
148157 @pytest .mark .asyncio
149158 async def test_tool_with_non_basemodel_schema (
@@ -157,7 +166,7 @@ async def test_tool_with_non_basemodel_schema(
157166
158167 assert isinstance (result , Command )
159168 self .assert_command_success (result )
160- mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call [ "args" ] )
169+ mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call )
161170
162171 @pytest .mark .asyncio
163172 @patch (
@@ -180,7 +189,7 @@ async def test_tool_with_no_attachment_paths(
180189 assert isinstance (result , Command )
181190 self .assert_command_success (result )
182191 mock_get_paths .assert_called_once_with (MockAttachmentSchema )
183- mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call [ "args" ] )
192+ mock_tool .ainvoke .assert_awaited_once_with (mock_tool_call )
184193
185194 @pytest .mark .asyncio
186195 @patch (
@@ -219,9 +228,9 @@ async def test_tool_with_valid_attachments(
219228 self .assert_command_success (result )
220229 # Verify that tool.ainvoke was called (with replaced attachment)
221230 mock_tool .ainvoke .assert_awaited_once ()
222- called_args = mock_tool .ainvoke .call_args [0 ][0 ]
223- assert called_args ["name" ] == "test"
224- assert "attachment" in called_args
231+ called_tool_call = mock_tool .ainvoke .call_args [0 ][0 ]
232+ assert called_tool_call [ "args" ] ["name" ] == "test"
233+ assert "attachment" in called_tool_call [ "args" ]
225234
226235 @pytest .mark .asyncio
227236 @patch (
@@ -557,7 +566,8 @@ async def test_tool_with_complex_nested_structure_all_valid(
557566 self .assert_command_success (result , tool_call_id = "call_complex_456" )
558567 # Tool should be invoked with replaced attachments
559568 mock_tool .ainvoke .assert_awaited_once ()
560- called_args = mock_tool .ainvoke .call_args [0 ][0 ]
569+ called_tool_call = mock_tool .ainvoke .call_args [0 ][0 ]
570+ called_args = called_tool_call ["args" ]
561571
562572 # Verify structure is preserved
563573 assert "request" in called_args
@@ -588,12 +598,14 @@ async def test_structured_tool_with_output_attachments(
588598 # Create a tool with output type
589599 mock_tool = MagicMock (spec = BaseTool )
590600 mock_tool .args_schema = None
591- mock_tool .ainvoke = AsyncMock (
592- return_value = {
593- "result_attachment" : output_attachment .model_dump (),
594- "additional_attachments" : [],
595- }
601+ tool_output = {
602+ "result_attachment" : output_attachment .model_dump (),
603+ "additional_attachments" : [],
604+ }
605+ tool_message = ToolMessage (
606+ content = json .dumps (tool_output ), tool_call_id = mock_tool_call ["id" ]
596607 )
608+ mock_tool .ainvoke = AsyncMock (return_value = tool_message )
597609
598610 wrapper = get_job_attachment_wrapper (output_type = MockOutputSchema )
599611 result = await wrapper (mock_tool , mock_tool_call , mock_state )
@@ -606,9 +618,7 @@ async def test_structured_tool_with_output_attachments(
606618 expected_content = None ,
607619 )
608620 # Verify get_job_attachments was called with correct parameters
609- mock_get_job_attachments .assert_called_once_with (
610- MockOutputSchema , mock_tool .ainvoke .return_value
611- )
621+ mock_get_job_attachments .assert_called_once_with (MockOutputSchema , tool_output )
612622
613623 @pytest .mark .asyncio
614624 @patch ("uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachments" )
@@ -631,15 +641,17 @@ async def test_structured_tool_with_multiple_output_attachments(
631641 # Create a tool with output type
632642 mock_tool = MagicMock (spec = BaseTool )
633643 mock_tool .args_schema = None
634- mock_tool .ainvoke = AsyncMock (
635- return_value = {
636- "result_attachment" : attachment1 .model_dump (),
637- "additional_attachments" : [
638- attachment2 .model_dump (),
639- attachment3 .model_dump (),
640- ],
641- }
644+ tool_output = {
645+ "result_attachment" : attachment1 .model_dump (),
646+ "additional_attachments" : [
647+ attachment2 .model_dump (),
648+ attachment3 .model_dump (),
649+ ],
650+ }
651+ tool_message = ToolMessage (
652+ content = json .dumps (tool_output ), tool_call_id = mock_tool_call ["id" ]
642653 )
654+ mock_tool .ainvoke = AsyncMock (return_value = tool_message )
643655
644656 wrapper = get_job_attachment_wrapper (output_type = MockOutputSchema )
645657 result = await wrapper (mock_tool , mock_tool_call , mock_state )
@@ -668,9 +680,14 @@ async def test_structured_tool_with_no_output_attachments(
668680 # Create a tool with output type
669681 mock_tool = MagicMock (spec = BaseTool )
670682 mock_tool .args_schema = None
671- mock_tool .ainvoke = AsyncMock (
672- return_value = {"result_attachment" : None , "additional_attachments" : []}
683+ tool_output : dict [str , Any ] = {
684+ "result_attachment" : None ,
685+ "additional_attachments" : [],
686+ }
687+ tool_message = ToolMessage (
688+ content = json .dumps (tool_output ), tool_call_id = mock_tool_call ["id" ]
673689 )
690+ mock_tool .ainvoke = AsyncMock (return_value = tool_message )
674691
675692 wrapper = get_job_attachment_wrapper (output_type = MockOutputSchema )
676693 result = await wrapper (mock_tool , mock_tool_call , mock_state )
@@ -703,12 +720,14 @@ async def test_structured_tool_filters_attachments_with_none_id(
703720 # Create a tool with output type
704721 mock_tool = MagicMock (spec = BaseTool )
705722 mock_tool .args_schema = None
706- mock_tool .ainvoke = AsyncMock (
707- return_value = {
708- "result_attachment" : attachment_with_id .model_dump (),
709- "additional_attachments" : [attachment_without_id .model_dump ()],
710- }
723+ tool_output = {
724+ "result_attachment" : attachment_with_id .model_dump (),
725+ "additional_attachments" : [attachment_without_id .model_dump ()],
726+ }
727+ tool_message = ToolMessage (
728+ content = json .dumps (tool_output ), tool_call_id = mock_tool_call ["id" ]
711729 )
730+ mock_tool .ainvoke = AsyncMock (return_value = tool_message )
712731
713732 wrapper = get_job_attachment_wrapper (output_type = MockOutputSchema )
714733 result = await wrapper (mock_tool , mock_tool_call , mock_state )
0 commit comments