1515from uipath .platform .action_center .tasks import TaskRecipient , TaskRecipientType
1616
1717from uipath_langchain .agent .tools .escalation_tool import (
18+ _get_user_email ,
1819 create_escalation_tool ,
1920 resolve_asset ,
2021 resolve_recipient_value ,
@@ -509,6 +510,9 @@ async def test_escalation_tool_result_validation(
509510 """Test that tool properly processes and validates results."""
510511 # Mock interrupt to return a proper result object with action and data
511512 mock_result = MagicMock ()
513+ mock_result .id = 123
514+ mock_result .key = None
515+ mock_result .assigned_to_user = None
512516 mock_result .action = "approve"
513517 mock_result .data = {}
514518 mock_interrupt .return_value = mock_result
@@ -519,9 +523,11 @@ async def test_escalation_tool_result_validation(
519523 # Invoke through the wrapper
520524 result = await tool .awrapper (tool , call , {}) # type: ignore[attr-defined]
521525
522- # Should successfully process the result
526+ # Should successfully process the result with task info
523527 assert isinstance (result , dict )
524- assert result == {}
528+ assert result ["outcome" ] == "approve"
529+ assert result ["task_id" ] == 123
530+ assert result ["assigned_to" ] is None
525531
526532 @pytest .mark .asyncio
527533 @patch ("uipath_langchain.agent.tools.escalation_tool.interrupt" )
@@ -546,14 +552,19 @@ async def test_escalation_tool_extracts_action_from_result(
546552
547553 @pytest .mark .asyncio
548554 @patch ("uipath_langchain.agent.tools.escalation_tool.interrupt" )
549- async def test_escalation_tool_with_outcome_mapping (self , mock_interrupt ):
550- """Test escalation tool with outcome mapping for actions."""
555+ async def test_escalation_tool_with_outcome_mapping_end (self , mock_interrupt ):
556+ """Test escalation tool with outcome mapping that ends agent."""
557+ from uipath_langchain .agent .exceptions import AgentTerminationException
558+
551559 mock_result = MagicMock ()
560+ mock_result .id = 456
561+ mock_result .key = None
562+ mock_result .assigned_to_user = None
552563 mock_result .action = "approve"
553564 mock_result .data = {"approved" : True }
554565 mock_interrupt .return_value = mock_result
555566
556- # Create resource with outcome mapping
567+ # Create resource with outcome mapping where approve -> end
557568 channel_dict = {
558569 "name" : "action_center" ,
559570 "type" : "actionCenter" ,
@@ -578,8 +589,106 @@ async def test_escalation_tool_with_outcome_mapping(self, mock_interrupt):
578589 tool = create_escalation_tool (resource )
579590 call = ToolCall (args = {}, id = "test-call" , name = tool .name )
580591
581- # Invoke through the wrapper
582- await tool .awrapper (tool , call , {}) # type: ignore[attr-defined]
592+ # Invoke through the wrapper - should raise AgentTerminationException
593+ with pytest .raises (AgentTerminationException ):
594+ await tool .awrapper (tool , call , {}) # type: ignore[attr-defined]
583595
584- # Verify interrupt was called with approval action
585596 assert mock_interrupt .called
597+
598+
599+ class TestGetUserEmail :
600+ """Test the _get_user_email helper function."""
601+
602+ def test_none_returns_none (self ):
603+ """Test that None input returns None."""
604+ assert _get_user_email (None ) is None
605+
606+ def test_dict_with_email_address (self ):
607+ """Test extraction from dict with emailAddress field."""
608+ user = {"emailAddress" : "test@example.com" , "name" : "Test" }
609+ assert _get_user_email (user ) == "test@example.com"
610+
611+ def test_dict_without_email_address (self ):
612+ """Test dict without emailAddress returns None."""
613+ user = {"name" : "Test" , "id" : 123 }
614+ assert _get_user_email (user ) is None
615+
616+ def test_object_with_email_address (self ):
617+ """Test extraction from object with emailAddress attribute."""
618+ user = MagicMock (emailAddress = "test@example.com" )
619+ assert _get_user_email (user ) == "test@example.com"
620+
621+ def test_object_without_email_address (self ):
622+ """Test object without emailAddress attribute returns None."""
623+ user = MagicMock (spec = ["name" , "id" ])
624+ assert _get_user_email (user ) is None
625+
626+
627+ class TestEscalationToolTaskInfo :
628+ """Test that escalation tool extracts task_id and assigned_to."""
629+
630+ @pytest .fixture
631+ def escalation_resource (self ):
632+ """Create a minimal escalation tool resource config."""
633+ return AgentEscalationResourceConfig (
634+ name = "approval" ,
635+ description = "Request approval" ,
636+ channels = [
637+ AgentEscalationChannel (
638+ name = "action_center" ,
639+ type = "actionCenter" ,
640+ description = "Action Center channel" ,
641+ input_schema = {"type" : "object" , "properties" : {}},
642+ output_schema = {"type" : "object" , "properties" : {}},
643+ properties = AgentEscalationChannelProperties (
644+ app_name = "ApprovalApp" ,
645+ app_version = 1 ,
646+ resource_key = "test-key" ,
647+ ),
648+ recipients = [],
649+ )
650+ ],
651+ )
652+
653+ @pytest .mark .asyncio
654+ @patch ("uipath_langchain.agent.tools.escalation_tool.interrupt" )
655+ async def test_wrapper_returns_task_id_and_assigned_to (
656+ self , mock_interrupt , escalation_resource
657+ ):
658+ """Test that wrapper result includes task_id and assigned_to from Task."""
659+ mock_result = MagicMock ()
660+ mock_result .id = 12345
661+ mock_result .key = None
662+ mock_result .assigned_to_user = {"emailAddress" : "user@example.com" }
663+ mock_result .action = "approve"
664+ mock_result .data = {"reason" : "looks good" }
665+ mock_interrupt .return_value = mock_result
666+
667+ tool = create_escalation_tool (escalation_resource )
668+ call = ToolCall (args = {}, id = "test-call" , name = tool .name )
669+ result = await tool .awrapper (tool , call , {}) # type: ignore[attr-defined]
670+
671+ assert result ["task_id" ] == 12345
672+ assert result ["assigned_to" ] == "user@example.com"
673+ assert result ["outcome" ] == "approve"
674+
675+ @pytest .mark .asyncio
676+ @patch ("uipath_langchain.agent.tools.escalation_tool.interrupt" )
677+ async def test_wrapper_handles_missing_assigned_to_user (
678+ self , mock_interrupt , escalation_resource
679+ ):
680+ """Test that wrapper handles None assigned_to_user gracefully."""
681+ mock_result = MagicMock ()
682+ mock_result .id = 99999
683+ mock_result .key = None
684+ mock_result .assigned_to_user = None
685+ mock_result .action = "reject"
686+ mock_result .data = {}
687+ mock_interrupt .return_value = mock_result
688+
689+ tool = create_escalation_tool (escalation_resource )
690+ call = ToolCall (args = {}, id = "test-call" , name = tool .name )
691+ result = await tool .awrapper (tool , call , {}) # type: ignore[attr-defined]
692+
693+ assert result ["task_id" ] == 99999
694+ assert result ["assigned_to" ] is None
0 commit comments