@@ -47,6 +47,22 @@ def flow_resource():
4747 )
4848
4949
50+ @pytest .fixture
51+ def function_resource ():
52+ """Create a process tool resource config of type Function (coded function release)."""
53+ return AgentProcessToolResourceConfig (
54+ type = AgentToolType .FUNCTION ,
55+ name = "test_function" ,
56+ description = "Test function description" ,
57+ input_schema = {"type" : "object" , "properties" : {}},
58+ output_schema = {"type" : "object" , "properties" : {}},
59+ properties = AgentProcessToolProperties (
60+ process_name = "MyFunction" ,
61+ folder_path = "/Shared/Functions" ,
62+ ),
63+ )
64+
65+
5066@pytest .fixture
5167def process_resource_with_inputs ():
5268 """Create a process tool resource config with input arguments."""
@@ -550,3 +566,84 @@ async def test_flow_tool_uses_non_agent_bts_key(
550566 bts_context = tool .metadata ["_bts_context" ]
551567 assert bts_context .get ("wait_for_job_key" ) == "flow-job-key"
552568 assert "wait_for_agent_job_key" not in bts_context
569+
570+
571+ class TestProcessToolFunctionType :
572+ """Test that a Function-type resource flows through create_process_tool correctly."""
573+
574+ def test_function_tool_metadata_has_function_tool_type (self , function_resource ):
575+ """A Function resource produces a tool with metadata.tool_type == 'function'."""
576+ tool = create_process_tool (function_resource )
577+ assert tool .metadata is not None
578+ assert tool .metadata ["tool_type" ] == "function"
579+
580+ def test_function_tool_metadata_has_display_name (self , function_resource ):
581+ """Function tool metadata exposes the process_name as display_name."""
582+ tool = create_process_tool (function_resource )
583+ assert tool .metadata is not None
584+ assert tool .metadata ["display_name" ] == "MyFunction"
585+
586+ @pytest .mark .asyncio
587+ @patch .dict (os .environ , {"UIPATH_FOLDER_PATH" : "/Shared/Functions" })
588+ @patch ("uipath_langchain._utils.durable_interrupt.decorator.interrupt" )
589+ @patch ("uipath_langchain.agent.tools.process_tool.UiPath" )
590+ async def test_function_tool_invokes_processes_invoke_async (
591+ self , mock_uipath_class , mock_interrupt , function_resource
592+ ):
593+ """Function tool invokes client.processes.invoke_async (same path as Process)."""
594+ mock_job = MagicMock (spec = Job )
595+ mock_job .key = "function-job-key"
596+ mock_job .folder_key = "function-folder-key"
597+
598+ mock_resumed_job = MagicMock (spec = Job )
599+ mock_resumed_job .state = "successful"
600+
601+ mock_client = MagicMock ()
602+ mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
603+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
604+ mock_uipath_class .return_value = mock_client
605+
606+ mock_interrupt .return_value = mock_resumed_job
607+
608+ tool = create_process_tool (function_resource )
609+ await tool .ainvoke ({})
610+
611+ mock_client .processes .invoke_async .assert_called_once_with (
612+ name = "MyFunction" ,
613+ input_arguments = {},
614+ folder_path = "/Shared/Functions" ,
615+ attachments = [],
616+ parent_span_id = None ,
617+ parent_operation_id = None ,
618+ run_as_me = None ,
619+ )
620+
621+ @pytest .mark .asyncio
622+ @patch ("uipath_langchain._utils.durable_interrupt.decorator.interrupt" )
623+ @patch ("uipath_langchain.agent.tools.process_tool.UiPath" )
624+ async def test_function_tool_uses_non_agent_bts_key (
625+ self , mock_uipath_class , mock_interrupt , function_resource
626+ ):
627+ """Function tool stores the BTS tracking key under 'wait_for_job_key' (not agent variant)."""
628+ mock_job = MagicMock (spec = Job )
629+ mock_job .key = "function-job-key"
630+ mock_job .folder_key = "function-folder-key"
631+
632+ mock_resumed_job = MagicMock (spec = Job )
633+ mock_resumed_job .state = "successful"
634+
635+ mock_client = MagicMock ()
636+ mock_client .processes .invoke_async = AsyncMock (return_value = mock_job )
637+ mock_client .jobs .extract_output_async = AsyncMock (return_value = None )
638+ mock_uipath_class .return_value = mock_client
639+
640+ mock_interrupt .return_value = mock_resumed_job
641+
642+ tool = create_process_tool (function_resource )
643+ assert tool .metadata is not None
644+
645+ await tool .ainvoke ({})
646+
647+ bts_context = tool .metadata ["_bts_context" ]
648+ assert bts_context .get ("wait_for_job_key" ) == "function-job-key"
649+ assert "wait_for_agent_job_key" not in bts_context
0 commit comments