@@ -301,3 +301,87 @@ def test_directory_property():
301301 project_id = 1 ,
302302 )
303303 assert row3 .directory == ""
304+
305+
306+ class TestSearchTermPreparation :
307+ """Test cases for FTS5 search term preparation."""
308+
309+ def test_simple_terms_get_prefix_wildcard (self , search_repository ):
310+ """Simple alphanumeric terms should get prefix matching."""
311+ assert search_repository ._prepare_search_term ("hello" ) == "hello*"
312+ assert search_repository ._prepare_search_term ("project" ) == "project*"
313+ assert search_repository ._prepare_search_term ("test123" ) == "test123*"
314+
315+ def test_terms_with_existing_wildcard_unchanged (self , search_repository ):
316+ """Terms that already contain * should remain unchanged."""
317+ assert search_repository ._prepare_search_term ("hello*" ) == "hello*"
318+ assert search_repository ._prepare_search_term ("test*world" ) == "test*world"
319+
320+ def test_boolean_operators_preserved (self , search_repository ):
321+ """Boolean operators should be preserved without modification."""
322+ assert search_repository ._prepare_search_term ("hello AND world" ) == "hello AND world"
323+ assert search_repository ._prepare_search_term ("cat OR dog" ) == "cat OR dog"
324+ assert search_repository ._prepare_search_term ("project NOT meeting" ) == "project NOT meeting"
325+ assert search_repository ._prepare_search_term ("(hello AND world) OR test" ) == "(hello AND world) OR test"
326+
327+ def test_programming_terms_should_work (self , search_repository ):
328+ """Programming-related terms with special chars should be searchable."""
329+ # These should be quoted to handle special characters safely
330+ assert search_repository ._prepare_search_term ("C++" ) == '"C++"*'
331+ assert search_repository ._prepare_search_term ("function()" ) == '"function()"*'
332+ assert search_repository ._prepare_search_term ("email@domain.com" ) == '"email@domain.com"*'
333+ assert search_repository ._prepare_search_term ("array[index]" ) == '"array[index]"*'
334+ assert search_repository ._prepare_search_term ("config.json" ) == '"config.json"*'
335+
336+ def test_malformed_fts5_syntax_quoted (self , search_repository ):
337+ """Malformed FTS5 syntax should be quoted to prevent errors."""
338+ # Multiple operators without proper syntax
339+ assert search_repository ._prepare_search_term ("+++invalid+++" ) == '"+++invalid+++"*'
340+ assert search_repository ._prepare_search_term ("!!!error!!!" ) == '"!!!error!!!"*'
341+ assert search_repository ._prepare_search_term ("@#$%^&*()" ) == '"@#$%^&*()"*'
342+
343+ def test_quoted_strings_handled_properly (self , search_repository ):
344+ """Strings with quotes should have quotes escaped."""
345+ assert search_repository ._prepare_search_term ('say "hello"' ) == '"say ""hello"""*'
346+ assert search_repository ._prepare_search_term ("it's working" ) == '"it\' s working"*'
347+
348+ def test_file_paths_no_prefix_wildcard (self , search_repository ):
349+ """File paths should not get prefix wildcards."""
350+ assert search_repository ._prepare_search_term ("config.json" , is_prefix = False ) == '"config.json"'
351+ assert search_repository ._prepare_search_term ("docs/readme.md" , is_prefix = False ) == '"docs/readme.md"'
352+
353+ def test_spaces_handled_correctly (self , search_repository ):
354+ """Terms with spaces should be quoted."""
355+ assert search_repository ._prepare_search_term ("hello world" ) == '"hello world"*'
356+ assert search_repository ._prepare_search_term ("project planning" ) == '"project planning"*'
357+
358+ @pytest .mark .asyncio
359+ async def test_search_with_special_characters_returns_results (self , search_repository ):
360+ """Integration test: search with special characters should work gracefully."""
361+ # This test ensures the search doesn't crash with FTS5 syntax errors
362+
363+ # These should all return empty results gracefully, not crash
364+ results1 = await search_repository .search (search_text = "C++" )
365+ assert isinstance (results1 , list ) # Should not crash
366+
367+ results2 = await search_repository .search (search_text = "function()" )
368+ assert isinstance (results2 , list ) # Should not crash
369+
370+ results3 = await search_repository .search (search_text = "+++malformed+++" )
371+ assert isinstance (results3 , list ) # Should not crash, return empty results
372+
373+ results4 = await search_repository .search (search_text = "email@domain.com" )
374+ assert isinstance (results4 , list ) # Should not crash
375+
376+ @pytest .mark .asyncio
377+ async def test_boolean_search_still_works (self , search_repository ):
378+ """Boolean search operations should continue to work."""
379+ # These should not crash and should respect boolean logic
380+ results1 = await search_repository .search (search_text = "hello AND world" )
381+ assert isinstance (results1 , list )
382+
383+ results2 = await search_repository .search (search_text = "cat OR dog" )
384+ assert isinstance (results2 , list )
385+
386+ results3 = await search_repository .search (search_text = "project NOT meeting" )
387+ assert isinstance (results3 , list )
0 commit comments