@@ -10,7 +10,16 @@ vitest.mock("@qdrant/js-client-rest")
1010vitest . mock ( "crypto" )
1111vitest . mock ( "../../../../utils/path" )
1212vitest . mock ( "../../../../i18n" , ( ) => ( {
13- t : ( key : string ) => key , // Just return the key for testing
13+ t : ( key : string , params ?: any ) => {
14+ // Mock translation function that includes parameters for testing
15+ if ( key === "embeddings:vectorStore.vectorDimensionMismatch" && params ?. errorMessage ) {
16+ return `Failed to update vector index for new model. Please try clearing the index and starting again. Details: ${ params . errorMessage } `
17+ }
18+ if ( key === "embeddings:vectorStore.qdrantConnectionFailed" && params ?. qdrantUrl && params ?. errorMessage ) {
19+ return `Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at ${ params . qdrantUrl } . Error: ${ params . errorMessage } `
20+ }
21+ return key // Just return the key for other cases
22+ } ,
1423} ) )
1524vitest . mock ( "path" , ( ) => ( {
1625 ...vitest . importActual ( "path" ) ,
@@ -564,16 +573,22 @@ describe("QdrantVectorStore", () => {
564573 } )
565574 it ( "should recreate collection if it exists but vectorSize mismatches and return true" , async ( ) => {
566575 const differentVectorSize = 768
567- // Mock getCollection to return existing collection info with different vector size
568- mockQdrantClientInstance . getCollection . mockResolvedValue ( {
569- config : {
570- params : {
571- vectors : {
572- size : differentVectorSize , // Mismatching vector size
576+ // Mock getCollection to return existing collection info with different vector size first,
577+ // then return 404 to confirm deletion
578+ mockQdrantClientInstance . getCollection
579+ . mockResolvedValueOnce ( {
580+ config : {
581+ params : {
582+ vectors : {
583+ size : differentVectorSize , // Mismatching vector size
584+ } ,
573585 } ,
574586 } ,
575- } ,
576- } as any )
587+ } as any )
588+ . mockRejectedValueOnce ( {
589+ response : { status : 404 } ,
590+ message : "Not found" ,
591+ } )
577592 mockQdrantClientInstance . deleteCollection . mockResolvedValue ( true as any )
578593 mockQdrantClientInstance . createCollection . mockResolvedValue ( true as any )
579594 mockQdrantClientInstance . createPayloadIndex . mockResolvedValue ( { } as any )
@@ -582,7 +597,7 @@ describe("QdrantVectorStore", () => {
582597 const result = await vectorStore . initialize ( )
583598
584599 expect ( result ) . toBe ( true )
585- expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 1 )
600+ expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 2 ) // Once to check, once to verify deletion
586601 expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledWith ( expectedCollectionName )
587602 expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledTimes ( 1 )
588603 expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledWith ( expectedCollectionName )
@@ -703,7 +718,7 @@ describe("QdrantVectorStore", () => {
703718 }
704719
705720 expect ( caughtError ) . toBeDefined ( )
706- expect ( caughtError . message ) . toContain ( "embeddings:vectorStore.vectorDimensionMismatch " )
721+ expect ( caughtError . message ) . toContain ( "Failed to update vector index for new model " )
707722 expect ( caughtError . cause ) . toBe ( deleteError )
708723
709724 expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 1 )
@@ -719,15 +734,21 @@ describe("QdrantVectorStore", () => {
719734
720735 it ( "should throw vectorDimensionMismatch error when createCollection fails during recreation" , async ( ) => {
721736 const differentVectorSize = 768
722- mockQdrantClientInstance . getCollection . mockResolvedValue ( {
723- config : {
724- params : {
725- vectors : {
726- size : differentVectorSize ,
737+ mockQdrantClientInstance . getCollection
738+ . mockResolvedValueOnce ( {
739+ config : {
740+ params : {
741+ vectors : {
742+ size : differentVectorSize ,
743+ } ,
727744 } ,
728745 } ,
729- } ,
730- } as any )
746+ } as any )
747+ // Second call should return 404 to confirm deletion
748+ . mockRejectedValueOnce ( {
749+ response : { status : 404 } ,
750+ message : "Not found" ,
751+ } )
731752
732753 // Delete succeeds but create fails
733754 mockQdrantClientInstance . deleteCollection . mockResolvedValue ( true as any )
@@ -745,10 +766,10 @@ describe("QdrantVectorStore", () => {
745766 }
746767
747768 expect ( caughtError ) . toBeDefined ( )
748- expect ( caughtError . message ) . toContain ( "embeddings:vectorStore.vectorDimensionMismatch " )
769+ expect ( caughtError . message ) . toContain ( "Failed to update vector index for new model " )
749770 expect ( caughtError . cause ) . toBe ( createError )
750771
751- expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 1 )
772+ expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 2 )
752773 expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledTimes ( 1 )
753774 expect ( mockQdrantClientInstance . createCollection ) . toHaveBeenCalledTimes ( 1 )
754775 expect ( mockQdrantClientInstance . createPayloadIndex ) . not . toHaveBeenCalled ( )
@@ -758,6 +779,166 @@ describe("QdrantVectorStore", () => {
758779 ; ( console . error as any ) . mockRestore ( )
759780 ; ( console . warn as any ) . mockRestore ( )
760781 } )
782+
783+ it ( "should verify collection deletion before proceeding with recreation" , async ( ) => {
784+ const differentVectorSize = 768
785+ mockQdrantClientInstance . getCollection
786+ . mockResolvedValueOnce ( {
787+ config : {
788+ params : {
789+ vectors : {
790+ size : differentVectorSize ,
791+ } ,
792+ } ,
793+ } ,
794+ } as any )
795+ // Second call should return 404 to confirm deletion
796+ . mockRejectedValueOnce ( {
797+ response : { status : 404 } ,
798+ message : "Not found" ,
799+ } )
800+
801+ mockQdrantClientInstance . deleteCollection . mockResolvedValue ( true as any )
802+ mockQdrantClientInstance . createCollection . mockResolvedValue ( true as any )
803+ mockQdrantClientInstance . createPayloadIndex . mockResolvedValue ( { } as any )
804+ vitest . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
805+
806+ const result = await vectorStore . initialize ( )
807+
808+ expect ( result ) . toBe ( true )
809+ // Should call getCollection twice: once to check existing, once to verify deletion
810+ expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 2 )
811+ expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledTimes ( 1 )
812+ expect ( mockQdrantClientInstance . createCollection ) . toHaveBeenCalledTimes ( 1 )
813+ expect ( mockQdrantClientInstance . createPayloadIndex ) . toHaveBeenCalledTimes ( 5 )
814+ ; ( console . warn as any ) . mockRestore ( )
815+ } )
816+
817+ it ( "should throw error if collection still exists after deletion attempt" , async ( ) => {
818+ const differentVectorSize = 768
819+ mockQdrantClientInstance . getCollection
820+ . mockResolvedValueOnce ( {
821+ config : {
822+ params : {
823+ vectors : {
824+ size : differentVectorSize ,
825+ } ,
826+ } ,
827+ } ,
828+ } as any )
829+ // Second call should still return the collection (deletion failed)
830+ . mockResolvedValueOnce ( {
831+ config : {
832+ params : {
833+ vectors : {
834+ size : differentVectorSize ,
835+ } ,
836+ } ,
837+ } ,
838+ } as any )
839+
840+ mockQdrantClientInstance . deleteCollection . mockResolvedValue ( true as any )
841+ vitest . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } )
842+ vitest . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
843+
844+ let caughtError : any
845+ try {
846+ await vectorStore . initialize ( )
847+ } catch ( error : any ) {
848+ caughtError = error
849+ }
850+
851+ expect ( caughtError ) . toBeDefined ( )
852+ expect ( caughtError . message ) . toContain ( "Failed to update vector index for new model" )
853+ // The error message should contain the contextual error details
854+ expect ( caughtError . message ) . toContain ( "Deleted existing collection but failed verification step" )
855+
856+ expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 2 )
857+ expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledTimes ( 1 )
858+ expect ( mockQdrantClientInstance . createCollection ) . not . toHaveBeenCalled ( )
859+ expect ( mockQdrantClientInstance . createPayloadIndex ) . not . toHaveBeenCalled ( )
860+ ; ( console . error as any ) . mockRestore ( )
861+ ; ( console . warn as any ) . mockRestore ( )
862+ } )
863+
864+ it ( "should handle dimension mismatch scenario from 2048 to 768 dimensions" , async ( ) => {
865+ // Simulate the exact scenario from the issue: switching from 2048 to 768 dimensions
866+ const oldVectorSize = 2048
867+ const newVectorSize = 768
868+
869+ // Create a new vector store with the new dimension
870+ const newVectorStore = new QdrantVectorStore ( mockWorkspacePath , mockQdrantUrl , newVectorSize , mockApiKey )
871+
872+ mockQdrantClientInstance . getCollection
873+ . mockResolvedValueOnce ( {
874+ config : {
875+ params : {
876+ vectors : {
877+ size : oldVectorSize , // Existing collection has 2048 dimensions
878+ } ,
879+ } ,
880+ } ,
881+ } as any )
882+ // Second call should return 404 to confirm deletion
883+ . mockRejectedValueOnce ( {
884+ response : { status : 404 } ,
885+ message : "Not found" ,
886+ } )
887+
888+ mockQdrantClientInstance . deleteCollection . mockResolvedValue ( true as any )
889+ mockQdrantClientInstance . createCollection . mockResolvedValue ( true as any )
890+ mockQdrantClientInstance . createPayloadIndex . mockResolvedValue ( { } as any )
891+ vitest . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
892+
893+ const result = await newVectorStore . initialize ( )
894+
895+ expect ( result ) . toBe ( true )
896+ expect ( mockQdrantClientInstance . getCollection ) . toHaveBeenCalledTimes ( 2 )
897+ expect ( mockQdrantClientInstance . deleteCollection ) . toHaveBeenCalledTimes ( 1 )
898+ expect ( mockQdrantClientInstance . createCollection ) . toHaveBeenCalledWith ( expectedCollectionName , {
899+ vectors : {
900+ size : newVectorSize , // Should create with new 768 dimensions
901+ distance : "Cosine" ,
902+ } ,
903+ } )
904+ expect ( mockQdrantClientInstance . createPayloadIndex ) . toHaveBeenCalledTimes ( 5 )
905+ ; ( console . warn as any ) . mockRestore ( )
906+ } )
907+
908+ it ( "should provide detailed error context for different failure scenarios" , async ( ) => {
909+ const differentVectorSize = 768
910+ mockQdrantClientInstance . getCollection . mockResolvedValue ( {
911+ config : {
912+ params : {
913+ vectors : {
914+ size : differentVectorSize ,
915+ } ,
916+ } ,
917+ } ,
918+ } as any )
919+
920+ // Test deletion failure with specific error message
921+ const deleteError = new Error ( "Qdrant server unavailable" )
922+ mockQdrantClientInstance . deleteCollection . mockRejectedValue ( deleteError )
923+ vitest . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } )
924+ vitest . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
925+
926+ let caughtError : any
927+ try {
928+ await vectorStore . initialize ( )
929+ } catch ( error : any ) {
930+ caughtError = error
931+ }
932+
933+ expect ( caughtError ) . toBeDefined ( )
934+ expect ( caughtError . message ) . toContain ( "Failed to update vector index for new model" )
935+ // The error message should contain the contextual error details
936+ expect ( caughtError . message ) . toContain ( "Failed to delete existing collection with vector size" )
937+ expect ( caughtError . message ) . toContain ( "Qdrant server unavailable" )
938+ expect ( caughtError . cause ) . toBe ( deleteError )
939+ ; ( console . error as any ) . mockRestore ( )
940+ ; ( console . warn as any ) . mockRestore ( )
941+ } )
761942 } )
762943
763944 it ( "should return true when collection exists" , async ( ) => {
0 commit comments