1+ import sys
2+ import types
3+ from pathlib import Path
14from threading import Event
25from unittest .mock import MagicMock , patch
36
47import pytest
58
9+ TEST_ROOT = Path (__file__ ).resolve ().parents [3 ]
10+ PROJECT_ROOT = TEST_ROOT .parent
11+ for _path in (str (PROJECT_ROOT ), str (TEST_ROOT )):
12+ if _path not in sys .path :
13+ sys .path .insert (0 , _path )
14+
15+ SDK_SOURCE_ROOT = PROJECT_ROOT / "sdk"
16+ sdk_namespace_module = types .ModuleType ("sdk" )
17+ sdk_namespace_module .__path__ = [str (SDK_SOURCE_ROOT )]
18+
619# ---------------------------------------------------------------------------
720# Prepare mocks for external dependencies that are not required for this test
821# ---------------------------------------------------------------------------
@@ -68,7 +81,91 @@ class _TestCoreAgent:
6881
6982# Very lightweight mock for openai path required by internal OpenAIModel import
7083mock_openai_chat_completion_message = MagicMock ()
84+
85+ mock_botocore_module = types .ModuleType ("botocore" )
86+ mock_botocore_exceptions = types .ModuleType ("botocore.exceptions" )
87+ mock_botocore_exceptions .ClientError = MagicMock ()
88+ mock_botocore_module .exceptions = mock_botocore_exceptions
89+ mock_botocore_client = types .ModuleType ("botocore.client" )
90+ mock_botocore_client .Config = MagicMock ()
91+ mock_botocore_args = types .ModuleType ("botocore.args" )
92+ mock_botocore_args .ClientArgsCreator = MagicMock ()
93+ mock_botocore_regions = types .ModuleType ("botocore.regions" )
94+ mock_botocore_regions .EndpointResolverBuiltins = MagicMock ()
95+ mock_botocore_crt = types .ModuleType ("botocore.crt" )
96+ mock_botocore_crt .CRT_SUPPORTED_AUTH_TYPES = []
97+
98+
99+ class _MockMessageObserver :
100+ def add_message (self , * args , ** kwargs ):
101+ return None
102+
103+
104+ class _MockProcessType :
105+ TOKEN_COUNT = "token_count"
106+ FINAL_ANSWER = "final_answer"
107+ ERROR = "error"
108+
109+
110+ mock_nexent_core_utils_module = types .ModuleType ("nexent.core.utils" )
111+ mock_nexent_core_utils_observer_module = types .ModuleType ("nexent.core.utils.observer" )
112+ mock_nexent_core_utils_observer_module .MessageObserver = _MockMessageObserver
113+ mock_nexent_core_utils_observer_module .ProcessType = _MockProcessType
114+
115+ mock_prompt_template_utils_module = types .ModuleType (
116+ "nexent.core.utils.prompt_template_utils"
117+ )
118+ mock_prompt_template_utils_module .get_prompt_template = MagicMock (return_value = "" )
119+
120+ mock_tools_common_message_module = types .ModuleType (
121+ "nexent.core.utils.tools_common_message"
122+ )
123+
124+
125+ class _EnumStub :
126+ def __init__ (self , value ):
127+ self .value = value
128+
129+
130+ class _MockToolCategory :
131+ SEARCH = _EnumStub ("search" )
132+ FILE = _EnumStub ("file" )
133+ EMAIL = _EnumStub ("email" )
134+ TERMINAL = _EnumStub ("terminal" )
135+ MULTIMODAL = _EnumStub ("multimodal" )
136+
137+
138+ class _MockToolSign :
139+ KNOWLEDGE_BASE = _EnumStub ("a" )
140+ EXA_SEARCH = _EnumStub ("b" )
141+ LINKUP_SEARCH = _EnumStub ("c" )
142+ TAVILY_SEARCH = _EnumStub ("d" )
143+ FILE_OPERATION = _EnumStub ("f" )
144+ TERMINAL_OPERATION = _EnumStub ("t" )
145+ MULTIMODAL_OPERATION = _EnumStub ("m" )
146+
147+
148+ mock_tools_common_message_module .ToolCategory = _MockToolCategory
149+ mock_tools_common_message_module .ToolSign = _MockToolSign
150+
151+ mock_nexent_core_utils_module .observer = mock_nexent_core_utils_observer_module
152+ mock_nexent_core_utils_module .prompt_template_utils = mock_prompt_template_utils_module
153+ mock_nexent_core_utils_module .tools_common_message = mock_tools_common_message_module
154+
155+ mock_nexent_core_module = types .ModuleType ("nexent.core" )
156+ mock_nexent_core_module .utils = mock_nexent_core_utils_module
157+ mock_nexent_core_module .MessageObserver = _MockMessageObserver
158+ mock_nexent_module = types .ModuleType ("nexent" )
159+ mock_nexent_module .core = mock_nexent_core_module
160+ mock_nexent_storage_module = types .ModuleType ("nexent.storage" )
161+ mock_nexent_storage_module .MinIOStorageClient = MagicMock ()
162+ mock_nexent_module .storage = mock_nexent_storage_module
163+ mock_nexent_multi_modal_module = types .ModuleType ("nexent.multi_modal" )
164+ mock_nexent_load_save_module = types .ModuleType ("nexent.multi_modal.load_save_object" )
165+ mock_nexent_load_save_module .LoadSaveObjectManager = MagicMock ()
166+ mock_nexent_module .multi_modal = mock_nexent_multi_modal_module
71167module_mocks = {
168+ "sdk" : sdk_namespace_module ,
72169 "smolagents" : mock_smolagents ,
73170 "smolagents.tools" : mock_smolagents_tools ,
74171 "smolagents.agents" : MagicMock (),
@@ -95,6 +192,22 @@ class _TestCoreAgent:
95192 "cryptography.hazmat.primitives.ciphers.base" : MagicMock (),
96193 "cryptography.hazmat.bindings" : MagicMock (),
97194 "cryptography.hazmat.bindings._rust" : MagicMock (),
195+ "boto3" : MagicMock (),
196+ "botocore" : mock_botocore_module ,
197+ "botocore.client" : mock_botocore_client ,
198+ "botocore.exceptions" : mock_botocore_exceptions ,
199+ "botocore.args" : mock_botocore_args ,
200+ "botocore.regions" : mock_botocore_regions ,
201+ "botocore.crt" : mock_botocore_crt ,
202+ "nexent" : mock_nexent_module ,
203+ "nexent.core" : mock_nexent_core_module ,
204+ "nexent.core.utils" : mock_nexent_core_utils_module ,
205+ "nexent.core.utils.observer" : mock_nexent_core_utils_observer_module ,
206+ "nexent.core.utils.prompt_template_utils" : mock_prompt_template_utils_module ,
207+ "nexent.core.utils.tools_common_message" : mock_tools_common_message_module ,
208+ "nexent.storage" : mock_nexent_storage_module ,
209+ "nexent.multi_modal" : mock_nexent_multi_modal_module ,
210+ "nexent.multi_modal.load_save_object" : mock_nexent_load_save_module ,
98211 # Mock the OpenAIModel import
99212 "sdk.nexent.core.models.openai_llm" : MagicMock (OpenAIModel = mock_openai_model_class ),
100213 # Mock CoreAgent import
@@ -508,6 +621,7 @@ def test_create_local_tool_analyze_text_file_tool(nexent_agent_instance):
508621 metadata = {
509622 "llm_model" : "llm_model_obj" ,
510623 "storage_client" : "storage_client_obj" ,
624+ "data_process_service_url" : "https://example.com" ,
511625 },
512626 )
513627
@@ -693,6 +807,48 @@ def test_create_local_tool_knowledge_base_search_tool_with_none_defaults(nexent_
693807 assert mock_kb_tool_instance .embedding_model is None
694808 assert result == mock_kb_tool_instance
695809
810+ def test_create_local_tool_analyze_text_file_tool (nexent_agent_instance ):
811+ """Test AnalyzeTextFileTool creation injects observer and metadata."""
812+ mock_analyze_tool_class = MagicMock ()
813+ mock_analyze_tool_instance = MagicMock ()
814+ mock_analyze_tool_class .return_value = mock_analyze_tool_instance
815+
816+ tool_config = ToolConfig (
817+ class_name = "AnalyzeTextFileTool" ,
818+ name = "analyze_text_file" ,
819+ description = "desc" ,
820+ inputs = "{}" ,
821+ output_type = "string" ,
822+ params = {"prompt" : "describe this" },
823+ source = "local" ,
824+ metadata = {
825+ "llm_model" : "llm_model_obj" ,
826+ "storage_client" : "storage_client_obj" ,
827+ "data_process_service_url" : "DATA_PROCESS_SERVICE" ,
828+
829+ },
830+ )
831+
832+ original_value = nexent_agent .__dict__ .get ("AnalyzeTextFileTool" )
833+ nexent_agent .__dict__ ["AnalyzeTextFileTool" ] = mock_analyze_tool_class
834+
835+ try :
836+ result = nexent_agent_instance .create_local_tool (tool_config )
837+ finally :
838+ if original_value is not None :
839+ nexent_agent .__dict__ ["AnalyzeTextFileTool" ] = original_value
840+ elif "AnalyzeTextFileTool" in nexent_agent .__dict__ :
841+ del nexent_agent .__dict__ ["AnalyzeTextFileTool" ]
842+
843+ mock_analyze_tool_class .assert_called_once_with (
844+ observer = nexent_agent_instance .observer ,
845+ llm_model = "llm_model_obj" ,
846+ storage_client = "storage_client_obj" ,
847+ data_process_service_url = "DATA_PROCESS_SERVICE" ,
848+ prompt = "describe this" ,
849+ )
850+ assert result == mock_analyze_tool_instance
851+
696852
697853def test_create_local_tool_with_observer_attribute (nexent_agent_instance ):
698854 """Test create_local_tool sets observer attribute on tool if it exists."""
0 commit comments