@@ -124,11 +124,15 @@ async def test_invoke_calls_processes_invoke_async(
124124 mock_job .key = "job-key-123"
125125 mock_job .folder_key = "folder-key-123"
126126
127+ mock_resumed_job = MagicMock (spec = Job )
128+ mock_resumed_job .state = "successful"
129+
127130 mock_client = MagicMock ()
128131 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
132+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
129133 mock_uipath_class .return_value = mock_client
130134
131- mock_interrupt .return_value = { "output" : "result" }
135+ mock_interrupt .return_value = mock_resumed_job
132136
133137 tool = create_process_tool (process_resource )
134138 await tool .ainvoke ({})
@@ -148,16 +152,20 @@ async def test_invoke_calls_processes_invoke_async(
148152 async def test_invoke_interrupts_with_wait_job (
149153 self , mock_uipath_class , mock_interrupt , process_resource
150154 ):
151- """Test that after invoking, the tool interrupts with WaitJob ."""
155+ """Test that after invoking, the tool interrupts with WaitJobRaw ."""
152156 mock_job = MagicMock (spec = Job )
153157 mock_job .key = "job-key-456"
154158 mock_job .folder_key = "folder-key-456"
155159
160+ mock_resumed_job = MagicMock (spec = Job )
161+ mock_resumed_job .state = "successful"
162+
156163 mock_client = MagicMock ()
157164 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
165+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
158166 mock_uipath_class .return_value = mock_client
159167
160- mock_interrupt .return_value = { "output" : "done" }
168+ mock_interrupt .return_value = mock_resumed_job
161169
162170 tool = create_process_tool (process_resource )
163171 await tool .ainvoke ({})
@@ -179,11 +187,15 @@ async def test_invoke_passes_input_arguments(
179187 mock_job .key = "job-key"
180188 mock_job .folder_key = "folder-key"
181189
190+ mock_resumed_job = MagicMock (spec = Job )
191+ mock_resumed_job .state = "successful"
192+
182193 mock_client = MagicMock ()
183194 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
195+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
184196 mock_uipath_class .return_value = mock_client
185197
186- mock_interrupt .return_value = { "result" : "processed" }
198+ mock_interrupt .return_value = mock_resumed_job
187199
188200 tool = create_process_tool (process_resource_with_inputs )
189201 await tool .ainvoke ({"name" : "test-data" , "count" : 42 })
@@ -196,25 +208,59 @@ async def test_invoke_passes_input_arguments(
196208 @pytest .mark .asyncio
197209 @patch ("uipath_langchain.agent.tools.durable_interrupt.decorator.interrupt" )
198210 @patch ("uipath_langchain.agent.tools.process_tool.UiPath" )
199- async def test_invoke_returns_interrupt_value (
211+ async def test_invoke_returns_output_from_extract (
200212 self , mock_uipath_class , mock_interrupt , process_resource
201213 ):
202- """Test that the tool returns the value from interrupt() ."""
214+ """Test that the tool returns the extracted job output on success ."""
203215 mock_job = MagicMock (spec = Job )
204216 mock_job .key = "job-key"
205217 mock_job .folder_key = "folder-key"
206218
219+ mock_resumed_job = MagicMock (spec = Job )
220+ mock_resumed_job .state = "successful"
221+
207222 mock_client = MagicMock ()
208223 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
224+ mock_client .jobs .extract_output_async = AsyncMock (
225+ return_value = '{"output_arg": "value123"}'
226+ )
209227 mock_uipath_class .return_value = mock_client
210228
211- mock_interrupt .return_value = { "output_arg" : "value123" }
229+ mock_interrupt .return_value = mock_resumed_job
212230
213231 tool = create_process_tool (process_resource )
214232 result = await tool .ainvoke ({})
215233
216234 assert result == {"output_arg" : "value123" }
217235
236+ @pytest .mark .asyncio
237+ @patch ("uipath_langchain.agent.tools.durable_interrupt.decorator.interrupt" )
238+ @patch ("uipath_langchain.agent.tools.process_tool.UiPath" )
239+ async def test_invoke_returns_error_message_on_faulted_job (
240+ self , mock_uipath_class , mock_interrupt , process_resource
241+ ):
242+ """Test that the tool returns an error message string when the job is faulted."""
243+ mock_job = MagicMock (spec = Job )
244+ mock_job .key = "job-key"
245+ mock_job .folder_key = "folder-key"
246+
247+ mock_resumed_job = MagicMock (spec = Job )
248+ mock_resumed_job .state = "faulted"
249+ mock_resumed_job .job_error = None
250+ mock_resumed_job .info = "Something went wrong in the workflow"
251+
252+ mock_client = MagicMock ()
253+ mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
254+ mock_uipath_class .return_value = mock_client
255+
256+ mock_interrupt .return_value = mock_resumed_job
257+
258+ tool = create_process_tool (process_resource )
259+ result = await tool .ainvoke ({})
260+
261+ assert isinstance (result , str )
262+ assert "Something went wrong in the workflow" in result
263+
218264
219265class TestProcessToolSpanContext :
220266 """Test that _span_context is properly wired for tracing."""
@@ -230,11 +276,15 @@ async def test_span_context_parent_span_id_passed_to_invoke(
230276 mock_job .key = "job-key"
231277 mock_job .folder_key = "folder-key"
232278
279+ mock_resumed_job = MagicMock (spec = Job )
280+ mock_resumed_job .state = "successful"
281+
233282 mock_client = MagicMock ()
234283 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
284+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
235285 mock_uipath_class .return_value = mock_client
236286
237- mock_interrupt .return_value = {}
287+ mock_interrupt .return_value = mock_resumed_job
238288
239289 tool = create_process_tool (process_resource )
240290 assert tool .metadata is not None
@@ -258,11 +308,15 @@ async def test_span_context_consumed_after_invoke(
258308 mock_job .key = "job-key"
259309 mock_job .folder_key = "folder-key"
260310
311+ mock_resumed_job = MagicMock (spec = Job )
312+ mock_resumed_job .state = "successful"
313+
261314 mock_client = MagicMock ()
262315 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
316+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
263317 mock_uipath_class .return_value = mock_client
264318
265- mock_interrupt .return_value = {}
319+ mock_interrupt .return_value = mock_resumed_job
266320
267321 tool = create_process_tool (process_resource )
268322 assert tool .metadata is not None
@@ -284,11 +338,15 @@ async def test_span_context_defaults_to_none_when_empty(
284338 mock_job .key = "job-key"
285339 mock_job .folder_key = "folder-key"
286340
341+ mock_resumed_job = MagicMock (spec = Job )
342+ mock_resumed_job .state = "successful"
343+
287344 mock_client = MagicMock ()
288345 mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
346+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
289347 mock_uipath_class .return_value = mock_client
290348
291- mock_interrupt .return_value = {}
349+ mock_interrupt .return_value = mock_resumed_job
292350
293351 tool = create_process_tool (process_resource )
294352 # Don't set any parent_span_id
0 commit comments