@@ -2,6 +2,14 @@ import { CodeIndexManager } from "../manager"
22import { CodeIndexServiceFactory } from "../service-factory"
33import type { MockedClass } from "vitest"
44import * as path from "path"
5+ import * as fs from "fs/promises"
6+ import ignore from "ignore"
7+
8+ // Mock fs/promises module
9+ vi . mock ( "fs/promises" )
10+
11+ // Mock ignore module
12+ vi . mock ( "ignore" )
513
614// Mock vscode module
715vi . mock ( "vscode" , ( ) => {
@@ -581,4 +589,239 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
581589 consoleErrorSpy . mockRestore ( )
582590 } )
583591 } )
592+
593+ describe ( "gitignore pattern handling" , ( ) => {
594+ let mockIgnoreInstance : any
595+ let mockConfigManager : any
596+ let mockCacheManager : any
597+ let mockServiceFactoryInstance : any
598+
599+ beforeEach ( ( ) => {
600+ // Reset mocks
601+ vi . clearAllMocks ( )
602+
603+ // Mock ignore instance
604+ mockIgnoreInstance = {
605+ add : vi . fn ( ) ,
606+ ignores : vi . fn ( ( ) => false ) ,
607+ }
608+
609+ // Mock the ignore module to return our mock instance
610+ vi . mocked ( ignore ) . mockReturnValue ( mockIgnoreInstance )
611+
612+ // Mock config manager
613+ mockConfigManager = {
614+ loadConfiguration : vi . fn ( ) . mockResolvedValue ( { requiresRestart : false } ) ,
615+ isFeatureConfigured : true ,
616+ isFeatureEnabled : true ,
617+ getConfig : vi . fn ( ) . mockReturnValue ( {
618+ isConfigured : true ,
619+ embedderProvider : "openai" ,
620+ modelId : "text-embedding-3-small" ,
621+ openAiOptions : { openAiNativeApiKey : "test-key" } ,
622+ qdrantUrl : "http://localhost:6333" ,
623+ qdrantApiKey : "test-key" ,
624+ searchMinScore : 0.4 ,
625+ } ) ,
626+ }
627+ ; ( manager as any ) . _configManager = mockConfigManager
628+
629+ // Mock cache manager
630+ mockCacheManager = {
631+ initialize : vi . fn ( ) ,
632+ clearCacheFile : vi . fn ( ) ,
633+ }
634+ ; ( manager as any ) . _cacheManager = mockCacheManager
635+
636+ // Mock service factory
637+ mockServiceFactoryInstance = {
638+ createServices : vi . fn ( ) . mockReturnValue ( {
639+ embedder : { embedderInfo : { name : "openai" } } ,
640+ vectorStore : { } ,
641+ scanner : { } ,
642+ fileWatcher : {
643+ onDidStartBatchProcessing : vi . fn ( ) ,
644+ onBatchProgressUpdate : vi . fn ( ) ,
645+ watch : vi . fn ( ) ,
646+ stopWatcher : vi . fn ( ) ,
647+ dispose : vi . fn ( ) ,
648+ } ,
649+ } ) ,
650+ validateEmbedder : vi . fn ( ) . mockResolvedValue ( { valid : true } ) ,
651+ }
652+ MockedCodeIndexServiceFactory . mockImplementation ( ( ) => mockServiceFactoryInstance as any )
653+ } )
654+
655+ it ( "should handle invalid gitignore patterns gracefully" , async ( ) => {
656+ // Arrange - Mock .gitignore with invalid pattern
657+ const invalidGitignoreContent = `
658+ # Valid patterns
659+ node_modules/
660+ *.log
661+
662+ # Invalid pattern - character range out of order
663+ pqh[A-/]
664+
665+ # More valid patterns
666+ dist/
667+ .env
668+ `
669+ ; ( fs . readFile as any ) . mockResolvedValue ( invalidGitignoreContent )
670+
671+ // Make the first add() call throw an error (simulating invalid pattern)
672+ let addCallCount = 0
673+ mockIgnoreInstance . add . mockImplementation ( ( pattern : string ) => {
674+ addCallCount ++
675+ // Throw on first call (full content), succeed on individual patterns
676+ if ( addCallCount === 1 ) {
677+ throw new Error (
678+ "Invalid regular expression: /^pqh[A-\\/](?=$|\\/$)/i: Range out of order in character class" ,
679+ )
680+ }
681+ // Throw on the specific invalid pattern
682+ if ( pattern . includes ( "pqh[A-/]" ) ) {
683+ throw new Error (
684+ "Invalid regular expression: /^pqh[A-\\/](?=$|\\/$)/i: Range out of order in character class" ,
685+ )
686+ }
687+ } )
688+
689+ // Spy on console methods
690+ const consoleWarnSpy = vi . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
691+
692+ // Act
693+ await ( manager as any ) . _recreateServices ( )
694+
695+ // Assert - Should have logged warnings
696+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
697+ expect . stringContaining ( "Warning: .gitignore contains invalid patterns" ) ,
698+ )
699+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
700+ expect . stringContaining ( 'Skipping invalid .gitignore pattern: "pqh[A-/]"' ) ,
701+ )
702+
703+ // Should have attempted to add valid patterns individually
704+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalled ( )
705+
706+ // Should not throw an error - service creation should continue
707+ expect ( mockServiceFactoryInstance . createServices ) . toHaveBeenCalled ( )
708+ expect ( mockServiceFactoryInstance . validateEmbedder ) . toHaveBeenCalled ( )
709+
710+ // Cleanup
711+ consoleWarnSpy . mockRestore ( )
712+ } )
713+
714+ it ( "should process valid gitignore patterns normally" , async ( ) => {
715+ // Arrange - Mock .gitignore with all valid patterns
716+ const validGitignoreContent = `
717+ # Valid patterns
718+ node_modules/
719+ *.log
720+ dist/
721+ .env
722+ `
723+ ; ( fs . readFile as any ) . mockResolvedValue ( validGitignoreContent )
724+
725+ // All add() calls succeed
726+ mockIgnoreInstance . add . mockImplementation ( ( ) => { } )
727+
728+ // Spy on console methods
729+ const consoleWarnSpy = vi . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
730+
731+ // Act
732+ await ( manager as any ) . _recreateServices ( )
733+
734+ // Assert - Should not have logged any warnings
735+ expect ( consoleWarnSpy ) . not . toHaveBeenCalled ( )
736+
737+ // Should have added the content and .gitignore itself
738+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( validGitignoreContent )
739+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( ".gitignore" )
740+
741+ // Service creation should proceed normally
742+ expect ( mockServiceFactoryInstance . createServices ) . toHaveBeenCalled ( )
743+ expect ( mockServiceFactoryInstance . validateEmbedder ) . toHaveBeenCalled ( )
744+
745+ // Cleanup
746+ consoleWarnSpy . mockRestore ( )
747+ } )
748+
749+ it ( "should handle missing .gitignore file gracefully" , async ( ) => {
750+ // Arrange - Mock file not found error
751+ ; ( fs . readFile as any ) . mockRejectedValue ( new Error ( "ENOENT: no such file or directory" ) )
752+
753+ // Spy on console methods
754+ const consoleInfoSpy = vi . spyOn ( console , "info" ) . mockImplementation ( ( ) => { } )
755+
756+ // Act
757+ await ( manager as any ) . _recreateServices ( )
758+
759+ // Assert - Should log info message
760+ expect ( consoleInfoSpy ) . toHaveBeenCalledWith (
761+ ".gitignore file not found or could not be read, proceeding without gitignore patterns" ,
762+ )
763+
764+ // Should not attempt to add patterns
765+ expect ( mockIgnoreInstance . add ) . not . toHaveBeenCalled ( )
766+
767+ // Service creation should proceed normally
768+ expect ( mockServiceFactoryInstance . createServices ) . toHaveBeenCalled ( )
769+ expect ( mockServiceFactoryInstance . validateEmbedder ) . toHaveBeenCalled ( )
770+
771+ // Cleanup
772+ consoleInfoSpy . mockRestore ( )
773+ } )
774+
775+ it ( "should handle mixed valid and invalid patterns" , async ( ) => {
776+ // Arrange - Mock .gitignore with mix of valid and invalid patterns
777+ const mixedGitignoreContent = `
778+ node_modules/
779+ pqh[A-/]
780+ *.log
781+ [Z-A]invalid
782+ dist/
783+ `
784+ ; ( fs . readFile as any ) . mockResolvedValue ( mixedGitignoreContent )
785+
786+ // Make add() throw on invalid patterns
787+ mockIgnoreInstance . add . mockImplementation ( ( pattern : string ) => {
788+ if ( pattern === mixedGitignoreContent ) {
789+ throw new Error ( "Invalid patterns detected" )
790+ }
791+ if ( pattern . includes ( "[A-/]" ) || pattern . includes ( "[Z-A]" ) ) {
792+ throw new Error ( "Invalid character range" )
793+ }
794+ } )
795+
796+ // Spy on console methods
797+ const consoleWarnSpy = vi . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
798+
799+ // Act
800+ await ( manager as any ) . _recreateServices ( )
801+
802+ // Assert - Should have logged warnings for invalid patterns
803+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
804+ expect . stringContaining ( "Warning: .gitignore contains invalid patterns" ) ,
805+ )
806+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
807+ expect . stringContaining ( 'Skipping invalid .gitignore pattern: "pqh[A-/]"' ) ,
808+ )
809+ expect ( consoleWarnSpy ) . toHaveBeenCalledWith (
810+ expect . stringContaining ( 'Skipping invalid .gitignore pattern: "[Z-A]invalid"' ) ,
811+ )
812+
813+ // Should have attempted to add valid patterns
814+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( "node_modules/" )
815+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( "*.log" )
816+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( "dist/" )
817+ expect ( mockIgnoreInstance . add ) . toHaveBeenCalledWith ( ".gitignore" )
818+
819+ // Service creation should proceed normally
820+ expect ( mockServiceFactoryInstance . createServices ) . toHaveBeenCalled ( )
821+ expect ( mockServiceFactoryInstance . validateEmbedder ) . toHaveBeenCalled ( )
822+
823+ // Cleanup
824+ consoleWarnSpy . mockRestore ( )
825+ } )
826+ } )
584827} )
0 commit comments