1+ import os
2+ import tempfile
13from argparse import ArgumentParser
2- from unittest .mock import AsyncMock , MagicMock , patch
4+ from unittest .mock import AsyncMock , MagicMock , mock_open , patch
35
46import pytest
57from mcp import McpError
1113 mcp_server ,
1214 parse_cli_args ,
1315 query_tool ,
16+ vectorise_files ,
1417)
1518
1619
@@ -168,6 +171,125 @@ async def test_query_tool_no_collection():
168171 )
169172
170173
174+ @pytest .mark .asyncio
175+ async def test_vectorise_tool_invalid_project_root ():
176+ with (
177+ patch ("os.path.isdir" , return_value = False ),
178+ ):
179+ with pytest .raises (McpError ):
180+ await vectorise_files (paths = ["foo.bar" ], project_root = "." )
181+
182+
183+ @pytest .mark .asyncio
184+ async def test_vectorise_files_success ():
185+ with tempfile .TemporaryDirectory () as temp_dir :
186+ file_path = f"{ temp_dir } /test_file.py"
187+ with open (file_path , "w" ) as f :
188+ f .write ("def func(): pass" )
189+
190+ with (
191+ patch ("os.path.isdir" , return_value = True ),
192+ patch ("vectorcode.mcp_main.get_project_config" ) as mock_get_project_config ,
193+ patch ("vectorcode.mcp_main.get_client" ) as mock_get_client ,
194+ patch ("vectorcode.mcp_main.get_collection" ) as mock_get_collection ,
195+ patch ("vectorcode.subcommands.vectorise.chunked_add" ),
196+ patch (
197+ "vectorcode.subcommands.vectorise.hash_file" , return_value = "test_hash"
198+ ),
199+ ):
200+ mock_config = Config (project_root = temp_dir )
201+ mock_get_project_config .return_value = mock_config
202+ mock_client = AsyncMock ()
203+ mock_get_client .return_value = mock_client
204+ mock_collection = AsyncMock ()
205+ mock_collection .get .return_value = {"ids" : [], "metadatas" : []}
206+ mock_get_collection .return_value = mock_collection
207+ mock_client .get_max_batch_size .return_value = 100
208+
209+ result = await vectorise_files (paths = [file_path ], project_root = temp_dir )
210+
211+ assert result ["add" ] == 1
212+ mock_get_project_config .assert_called_once_with (temp_dir )
213+ mock_get_client .assert_called_once_with (mock_config )
214+ mock_get_collection .assert_called_once_with (mock_client , mock_config , True )
215+
216+
217+ @pytest .mark .asyncio
218+ async def test_vectorise_files_collection_access_failure ():
219+ with (
220+ patch ("os.path.isdir" , return_value = True ),
221+ patch ("vectorcode.mcp_main.get_project_config" ),
222+ patch ("vectorcode.mcp_main.get_client" , side_effect = Exception ("Client error" )),
223+ patch ("vectorcode.mcp_main.get_collection" ),
224+ ):
225+ with pytest .raises (McpError ) as exc_info :
226+ await vectorise_files (paths = ["file.py" ], project_root = "/valid/path" )
227+
228+ assert exc_info .value .error .code == 1
229+ assert (
230+ "Failed to create the collection at /valid/path"
231+ in exc_info .value .error .message
232+ )
233+
234+
235+ @pytest .mark .asyncio
236+ async def test_vectorise_files_with_exclude_spec ():
237+ with tempfile .TemporaryDirectory () as temp_dir :
238+ file1 = f"{ temp_dir } /file1.py"
239+ excluded_file = f"{ temp_dir } /excluded.py"
240+ exclude_spec_file = f"{ temp_dir } /.vectorcode/vectorcode.exclude"
241+
242+ os .makedirs (f"{ temp_dir } /.vectorcode" )
243+ with open (file1 , "w" ) as f :
244+ f .write ("content1" )
245+ with open (excluded_file , "w" ) as f :
246+ f .write ("content_excluded" )
247+
248+ # Create mock file handles for specific file contents
249+ mock_exclude_file_handle = mock_open (read_data = "excluded.py" ).return_value
250+
251+ def mock_open_side_effect (filename , * args , ** kwargs ):
252+ if filename == exclude_spec_file :
253+ return mock_exclude_file_handle
254+ # For other files that might be opened, return a generic mock
255+ return MagicMock ()
256+
257+ with (
258+ patch ("os.path.isdir" , return_value = True ),
259+ patch ("vectorcode.mcp_main.get_project_config" ) as mock_get_project_config ,
260+ patch ("vectorcode.mcp_main.get_client" ) as mock_get_client ,
261+ patch ("vectorcode.mcp_main.get_collection" ) as mock_get_collection ,
262+ patch ("vectorcode.subcommands.vectorise.chunked_add" ) as mock_chunked_add ,
263+ patch (
264+ "vectorcode.subcommands.vectorise.hash_file" , return_value = "test_hash"
265+ ),
266+ # Patch builtins.open with the custom side effect
267+ patch ("builtins.open" , side_effect = mock_open_side_effect ),
268+ # Patch os.path.isfile to control which files "exist"
269+ patch (
270+ "os.path.isfile" ,
271+ side_effect = lambda x : x in [file1 , excluded_file , exclude_spec_file ],
272+ ),
273+ ):
274+ mock_config = Config (project_root = temp_dir )
275+ mock_get_project_config .return_value = mock_config
276+ mock_client = AsyncMock ()
277+ mock_get_client .return_value = mock_client
278+ mock_collection = AsyncMock ()
279+ mock_collection .get .return_value = {"ids" : [], "metadatas" : []}
280+ mock_get_collection .return_value = mock_collection
281+ mock_client .get_max_batch_size .return_value = 100
282+
283+ result = await vectorise_files (
284+ paths = [file1 , excluded_file ], project_root = temp_dir
285+ )
286+
287+ assert result ["add" ] == 0
288+ assert mock_chunked_add .call_count == 0
289+ call_args = [call [0 ][0 ] for call in mock_chunked_add .call_args_list ]
290+ assert excluded_file not in call_args
291+
292+
171293@pytest .mark .asyncio
172294async def test_mcp_server ():
173295 with (
@@ -188,7 +310,7 @@ async def test_mcp_server():
188310
189311 await mcp_server ()
190312
191- assert mock_add_tool .call_count == 2
313+ assert mock_add_tool .call_count == 3
192314
193315
194316@pytest .mark .asyncio
@@ -223,7 +345,7 @@ async def new_get_collections(clients):
223345
224346 await mcp_server ()
225347
226- assert mock_add_tool .call_count == 2
348+ assert mock_add_tool .call_count == 3
227349 mock_get_collections .assert_called ()
228350
229351
0 commit comments