1313from unittest .mock import AsyncMock , MagicMock , Mock , patch
1414
1515import pytest
16+ import yaml
1617
1718from cpex .framework import GlobalContext , PluginManager
1819from cpex .framework .hooks .tools import ToolPreInvokePayload
1920from cpex .framework .isolated .client import IsolatedVenvPlugin
20- from cpex .framework .models import PluginConfig
21+ from cpex .framework .loader .plugin import ALLOWED_PLUGIN_DIRS
22+ from cpex .framework .models import Config , PluginConfig
2123
2224
2325class TestIsolatedPluginIntegration :
@@ -26,48 +28,42 @@ class TestIsolatedPluginIntegration:
2628 @pytest .fixture
2729 def integration_config_path (self , tmp_path ):
2830 """Create a temporary config file for integration testing."""
29- config_content = """
30- plugin_dirs:
31- - "xplugins"
3231
33- plugin_settings:
34- parallel_execution_within_band: true
35- plugin_timeout: 30
36- fail_on_plugin_error: false
37-
38- plugins:
39- - name: "test_isolated_plugin"
40- kind: "isolated_venv"
41- description: "Test isolated plugin"
42- version: "1.0.0"
43- author: "Test"
44- hooks: ["tool_pre_invoke"]
45- config:
46- class_name: "test_plugin.TestPlugin"
47- requirements_file: "requirements.txt"
48- script_path: "xplugins"
49- """
50- config_file = tmp_path / "test_config.yaml"
32+ cfg = Config (plugins = [PluginConfig (name = "test_isolated_plugin" , kind = "isolated_venv" ,description = "Test isolated plugin" ,version = "1.0.0" ,author = "Test" ,hooks = ["tool_pre_invoke" ],
33+ config = {
34+ "class_name" : "test_plugin.TestPlugin" ,
35+ "requirements_file" : "requirements.txt"
36+ })],plugin_dirs = [str ((tmp_path / "xplugins" ).resolve ())],
37+ plugin_settings = {
38+ "parallel_execution_within_band" : True ,
39+ "plugin_timeout" : 30 ,
40+ "fail_on_plugin_error" : False
41+ })
42+ config_file = tmp_path / "xplugins" / "test_config.yaml"
43+ class_root = tmp_path / "xplugins" / "test_plugin"
44+ class_root .mkdir (parents = True , exist_ok = True )
45+ dumped_cfg = cfg .model_dump (mode = "json" )
46+ config_content = yaml .safe_dump (dumped_cfg , default_flow_style = False )
5147 config_file .write_text (config_content )
5248 return str (config_file )
5349
5450 @pytest .mark .asyncio
5551 @patch ("cpex.framework.isolated.client.VenvProcessCommunicator" )
5652 @patch .object (IsolatedVenvPlugin , "create_venv" )
5753 async def test_plugin_manager_with_isolated_plugin (
58- self , mock_create_venv , mock_comm_class , integration_config_path
54+ self , mock_create_venv , mock_comm_class , integration_config_path , tmp_path
5955 ):
6056 """Test PluginManager loading and initializing an isolated plugin."""
6157 # Setup mocks
6258 mock_create_venv .return_value = None
6359 mock_comm = MagicMock ()
6460 mock_comm .install_requirements = MagicMock ()
6561 mock_comm_class .return_value = mock_comm
66-
67- # Create manager
68- manager = PluginManager (integration_config_path )
69-
70- await manager .initialize ()
62+ with patch ( 'cpex.framework.loader.plugin.ALLOWED_PLUGIN_DIRS' , { str (( tmp_path / "xplugins" ). resolve ())}):
63+ # Create manager
64+ manager = PluginManager (integration_config_path )
65+
66+ await manager .initialize ()
7167
7268 @pytest .mark .asyncio
7369 @patch ("cpex.framework.isolated.client.VenvProcessCommunicator" )
@@ -96,35 +92,39 @@ async def test_isolated_plugin_full_lifecycle(self, mock_create_venv, mock_comm_
9692 "config" : {
9793 "class_name" : "test_plugin.TestPlugin" ,
9894 "requirements_file" : "requirements.txt" ,
99- "script_path" : "tests/unit/cpex/fixtures/plugins/isolated"
10095 }
10196 }
97+ resolved_plugin_path = (tmp_path / "xplugins" ).resolve ()
98+ plugin_root = resolved_plugin_path / "test_plugin"
99+ plugin_root .mkdir (parents = True , exist_ok = True )
100+ # resolved_plugin_path.mkdir(parents=True, exist_ok=True)
101+ with patch ('cpex.framework.loader.plugin.ALLOWED_PLUGIN_DIRS' , { str (resolved_plugin_path ) }):
102102
103- config = PluginConfig (** config_dict )
104-
105- # Create and initialize plugin
106- plugin = IsolatedVenvPlugin (config )
107-
108- with patch ("cpex.framework.isolated.client.get_hook_registry" ) as mock_registry :
109- from cpex .framework .hooks .tools import ToolPreInvokeResult
110- mock_reg = MagicMock ()
111- mock_reg .get_result_type .return_value = ToolPreInvokeResult
112- mock_reg .json_to_result = MagicMock ()
113- mock_reg .json_to_result .return_value = ToolPreInvokeResult (continue_processing = True )
114- mock_registry .return_value = mock_reg
103+ config = PluginConfig (** config_dict )
115104
116- await plugin .initialize ()
105+ # Create and initialize plugin
106+ plugin = IsolatedVenvPlugin (config , plugin_dirs = [resolved_plugin_path ])
117107
118- # Invoke hook
119- payload = ToolPreInvokePayload (name = "test_tool" , args = {})
120- global_ctx = GlobalContext (request_id = "req-123" )
121- from cpex .framework .models import PluginContext
122- context = PluginContext (global_context = global_ctx )
123-
124- result = await plugin .invoke_hook ("tool_pre_invoke" , payload , context )
125-
126- assert result is not None
127- assert result .continue_processing is True
108+ with patch ("cpex.framework.isolated.client.get_hook_registry" ) as mock_registry :
109+ from cpex .framework .hooks .tools import ToolPreInvokeResult
110+ mock_reg = MagicMock ()
111+ mock_reg .get_result_type .return_value = ToolPreInvokeResult
112+ mock_reg .json_to_result = MagicMock ()
113+ mock_reg .json_to_result .return_value = ToolPreInvokeResult (continue_processing = True )
114+ mock_registry .return_value = mock_reg
115+
116+ await plugin .initialize ()
117+
118+ # Invoke hook
119+ payload = ToolPreInvokePayload (name = "test_tool" , args = {})
120+ global_ctx = GlobalContext (request_id = "req-123" )
121+ from cpex .framework .models import PluginContext
122+ context = PluginContext (global_context = global_ctx )
123+
124+ result = await plugin .invoke_hook ("tool_pre_invoke" , payload , context )
125+
126+ assert result is not None
127+ assert result .continue_processing is True
128128
129129 @pytest .mark .asyncio
130130 async def test_isolated_plugin_error_handling (self , tmp_path ):
@@ -139,11 +139,15 @@ async def test_isolated_plugin_error_handling(self, tmp_path):
139139 "config" : {
140140 "class_name" : "test_plugin.TestPlugin" ,
141141 "requirements_file" : "requirements.txt" ,
142- "script_path" : "tests/unit/cpex/fixtures/plugins/isolated"
143142 }
144143 }
145144 config = PluginConfig (** config_dict )
146- plugin = IsolatedVenvPlugin (config )
145+ resolved_plugin_path = (tmp_path / "xplugins" ).resolve ()
146+ cache_root = resolved_plugin_path / "test_plugin"
147+ cache_root .mkdir (parents = True , exist_ok = True )
148+ # resolved_plugin_path.mkdir(parents=True, exist_ok=True)
149+
150+ plugin = IsolatedVenvPlugin (config , plugin_dirs = [str (resolved_plugin_path )])
147151
148152 # Try to invoke hook without initialization
149153 from cpex .framework .errors import PluginError
@@ -182,7 +186,11 @@ async def test_isolated_plugin_with_multiple_hooks(
182186 }
183187
184188 config = PluginConfig (** config_dict )
185- plugin = IsolatedVenvPlugin (config )
189+ resolved_plugin_path = (tmp_path / "xplugins" ).resolve ()
190+ cache_root = resolved_plugin_path / "test_plugin"
191+ cache_root .mkdir (parents = True , exist_ok = True )
192+
193+ plugin = IsolatedVenvPlugin (config , plugin_dirs = [str (resolved_plugin_path )])
186194
187195 await plugin .initialize ()
188196
@@ -266,11 +274,14 @@ def capture_task(script_path, task_data):
266274 "config" : {
267275 "class_name" : "test_plugin.TestPlugin" ,
268276 "requirements_file" : "requirements.txt" ,
269- "script_path" : "tests/unit/cpex/fixtures/plugins/isolated"
270277 }
271278 }
272279 config = PluginConfig (** config_dict )
273- plugin = IsolatedVenvPlugin (config )
280+ resolved_plugin_path = (tmp_path / "xplugins" ).resolve ()
281+ cache_root = resolved_plugin_path / "test_plugin"
282+ cache_root .mkdir (parents = True , exist_ok = True )
283+
284+ plugin = IsolatedVenvPlugin (config , plugin_dirs = [str (resolved_plugin_path )])
274285
275286 await plugin .initialize ()
276287
@@ -334,11 +345,14 @@ async def test_isolated_plugin_violation_handling(
334345 "config" : {
335346 "class_name" : "test_plugin.TestPlugin" ,
336347 "requirements_file" : "requirements.txt" ,
337- "script_path" : "tests/unit/cpex/fixtures/plugins/isolated"
338348 }
339349 }
340350 config = PluginConfig (** config_dict )
341- plugin = IsolatedVenvPlugin (config )
351+ resolved_plugin_path = (tmp_path / "xplugins" ).resolve ()
352+ cache_root = resolved_plugin_path / "test_plugin"
353+ cache_root .mkdir (parents = True , exist_ok = True )
354+
355+ plugin = IsolatedVenvPlugin (config , plugin_dirs = [str (resolved_plugin_path )])
342356
343357 await plugin .initialize ()
344358
0 commit comments