11import pytest
22import sys
33import types
4+ import importlib .util
5+ from pathlib import Path
46from unittest .mock import AsyncMock , MagicMock , patch , Mock , PropertyMock
57
8+ TEST_ROOT = Path (__file__ ).resolve ().parents [2 ]
9+ PROJECT_ROOT = TEST_ROOT .parent
10+
11+ # Ensure project backend package is found before test/backend
12+ for _path in (str (PROJECT_ROOT ), str (TEST_ROOT )):
13+ if _path not in sys .path :
14+ sys .path .insert (0 , _path )
15+ from test .common .env_test_utils import bootstrap_env
16+
17+ env_state = bootstrap_env ()
18+ consts_const = env_state ["mock_const" ]
19+
620from test .common .env_test_utils import bootstrap_env
721
822env_state = bootstrap_env ()
@@ -17,6 +31,31 @@ def _create_stub_module(name: str, **attrs):
1731 return module
1832
1933
34+ # Configure required constants via shared bootstrap env
35+ consts_const .MINIO_ENDPOINT = "http://localhost:9000"
36+ consts_const .MINIO_ACCESS_KEY = "test_access_key"
37+ consts_const .MINIO_SECRET_KEY = "test_secret_key"
38+ consts_const .MINIO_REGION = "us-east-1"
39+ consts_const .MINIO_DEFAULT_BUCKET = "test-bucket"
40+ consts_const .POSTGRES_HOST = "localhost"
41+ consts_const .POSTGRES_USER = "test_user"
42+ consts_const .NEXENT_POSTGRES_PASSWORD = "test_password"
43+ consts_const .POSTGRES_DB = "test_db"
44+ consts_const .POSTGRES_PORT = 5432
45+ consts_const .DEFAULT_TENANT_ID = "default_tenant"
46+ consts_const .LOCAL_MCP_SERVER = "http://localhost:5011"
47+ consts_const .MODEL_CONFIG_MAPPING = {"llm" : "llm_config" }
48+ consts_const .LANGUAGE = {"ZH" : "zh" }
49+ consts_const .DATA_PROCESS_SERVICE = "https://example.com/data-process"
50+ # Utilities ---------------------------------------------------------------
51+ def _create_stub_module (name : str , ** attrs ):
52+ """Return a lightweight module stub with the provided attributes."""
53+ module = types .ModuleType (name )
54+ for attr_name , attr_value in attrs .items ():
55+ setattr (module , attr_name , attr_value )
56+ return module
57+
58+
2059# Configure required constants via shared bootstrap env
2160consts_const .MINIO_ENDPOINT = "http://localhost:9000"
2261consts_const .MINIO_ACCESS_KEY = "test_access_key"
@@ -46,6 +85,7 @@ def _create_stub_module(name: str, **attrs):
4685# if the testing environment does not have it available.
4786boto3_mock = MagicMock ()
4887sys .modules ['boto3' ] = boto3_mock
88+ sys .modules ['dotenv' ] = MagicMock (load_dotenv = MagicMock ())
4989
5090# Mock the entire client module
5191client_mock = MagicMock ()
@@ -92,6 +132,14 @@ def _create_stub_module(name: str, **attrs):
92132sys .modules ['services.image_service' ] = _create_stub_module (
93133 "services.image_service" , get_vlm_model = MagicMock (return_value = "stub_vlm" )
94134)
135+ sys .modules ['services.file_management_service' ] = _create_stub_module (
136+ "services.file_management_service" ,
137+ get_llm_model = MagicMock (return_value = "stub_llm_model" ),
138+ )
139+ sys .modules ['services.tool_configuration_service' ] = _create_stub_module (
140+ "services.tool_configuration_service" ,
141+ initialize_tools_on_startup = AsyncMock (),
142+ )
95143# Build top-level nexent module to avoid importing the real package
96144nexent_module = _create_stub_module (
97145 "nexent" ,
@@ -127,7 +175,31 @@ def _create_stub_module(name: str, **attrs):
127175sys .modules ['smolagents' ] = smolagents_module
128176sys .modules ['smolagents.tools' ] = smolagents_tools_module
129177
130- # Now import the module under test
178+ # Ensure real backend.agents.create_agent_info is available and uses our stubs
179+ backend_pkg = sys .modules .get ("backend" )
180+ if backend_pkg is None :
181+ backend_pkg = types .ModuleType ("backend" )
182+ backend_pkg .__path__ = [str ((TEST_ROOT .parent ) / "backend" )]
183+ sys .modules ["backend" ] = backend_pkg
184+
185+ agents_pkg = sys .modules .get ("backend.agents" )
186+ if agents_pkg is None :
187+ agents_pkg = types .ModuleType ("backend.agents" )
188+ agents_pkg .__path__ = [str ((TEST_ROOT .parent ) / "backend" / "agents" )]
189+ sys .modules ["backend.agents" ] = agents_pkg
190+ setattr (backend_pkg , "agents" , agents_pkg )
191+
192+ create_agent_info_path = (TEST_ROOT .parent / "backend" / "agents" / "create_agent_info.py" )
193+ spec = importlib .util .spec_from_file_location (
194+ "backend.agents.create_agent_info" , create_agent_info_path
195+ )
196+ create_agent_info_module = importlib .util .module_from_spec (spec )
197+ sys .modules ["backend.agents.create_agent_info" ] = create_agent_info_module
198+ assert spec .loader is not None
199+ spec .loader .exec_module (create_agent_info_module )
200+ setattr (agents_pkg , "create_agent_info" , create_agent_info_module )
201+
202+ # Now import the symbols under test
131203from backend .agents .create_agent_info import (
132204 discover_langchain_tools ,
133205 create_tool_config_list ,
@@ -324,6 +396,43 @@ async def test_create_tool_config_list_with_knowledge_base_tool(self):
324396 last_call = mock_tool_config .call_args_list [- 1 ]
325397 assert last_call [1 ]['class_name' ] == "KnowledgeBaseSearchTool"
326398
399+ @pytest .mark .asyncio
400+ async def test_create_tool_config_list_with_analyze_text_file_tool (self ):
401+ """Ensure AnalyzeTextFileTool receives text-specific metadata."""
402+ mock_tool_instance = MagicMock ()
403+ mock_tool_instance .class_name = "AnalyzeTextFileTool"
404+ mock_tool_config .return_value = mock_tool_instance
405+
406+ with patch ('backend.agents.create_agent_info.discover_langchain_tools' , return_value = []), \
407+ patch ('backend.agents.create_agent_info.search_tools_for_sub_agent' ) as mock_search_tools , \
408+ patch ('backend.agents.create_agent_info.get_llm_model' ) as mock_get_llm_model , \
409+ patch ('backend.agents.create_agent_info.minio_client' , new_callable = MagicMock ) as mock_minio_client :
410+
411+ mock_search_tools .return_value = [
412+ {
413+ "class_name" : "AnalyzeTextFileTool" ,
414+ "name" : "analyze_text_file" ,
415+ "description" : "Analyze text file tool" ,
416+ "inputs" : "string" ,
417+ "output_type" : "array" ,
418+ "params" : [{"name" : "prompt" , "default" : "describe" }],
419+ "source" : "local" ,
420+ "usage" : None
421+ }
422+ ]
423+ mock_get_llm_model .return_value = "mock_llm_model"
424+
425+ result = await create_tool_config_list ("agent_1" , "tenant_1" , "user_1" )
426+
427+ assert len (result ) == 1
428+ assert result [0 ] is mock_tool_instance
429+ mock_get_llm_model .assert_called_once_with (tenant_id = "tenant_1" )
430+ assert mock_tool_instance .metadata == {
431+ "llm_model" : "mock_llm_model" ,
432+ "storage_client" : mock_minio_client ,
433+ "data_process_service_url" : consts_const .DATA_PROCESS_SERVICE ,
434+ }
435+
327436 @pytest .mark .asyncio
328437 async def test_create_tool_config_list_with_analyze_image_tool (self ):
329438 """Ensure AnalyzeImageTool receives VLM model metadata."""
0 commit comments