11import { CodeIndexManager } from "../manager"
22import { CodeIndexServiceFactory } from "../service-factory"
33import type { MockedClass } from "vitest"
4+ import * as path from "path"
45
56// Mock vscode module
6- vi . mock ( "vscode" , ( ) => ( {
7- window : {
8- activeTextEditor : null ,
9- } ,
10- workspace : {
11- workspaceFolders : [
12- {
13- uri : { fsPath : "/test/workspace" } ,
14- name : "test" ,
15- index : 0 ,
16- } ,
17- ] ,
18- } ,
19- } ) )
7+ vi . mock ( "vscode" , ( ) => {
8+ const testPath = require ( "path" )
9+ const testWorkspacePath = testPath . join ( testPath . sep , "test" , "workspace" )
10+ return {
11+ window : {
12+ activeTextEditor : null ,
13+ } ,
14+ workspace : {
15+ workspaceFolders : [
16+ {
17+ uri : { fsPath : testWorkspacePath } ,
18+ name : "test" ,
19+ index : 0 ,
20+ } ,
21+ ] ,
22+ } ,
23+ }
24+ } )
2025
2126// Mock only the essential dependencies
22- vi . mock ( "../../../utils/path" , ( ) => ( {
23- getWorkspacePath : vi . fn ( ( ) => "/test/workspace" ) ,
24- } ) )
27+ vi . mock ( "../../../utils/path" , ( ) => {
28+ const testPath = require ( "path" )
29+ const testWorkspacePath = testPath . join ( testPath . sep , "test" , "workspace" )
30+ return {
31+ getWorkspacePath : vi . fn ( ( ) => testWorkspacePath ) ,
32+ }
33+ } )
2534
2635vi . mock ( "../state-manager" , ( ) => ( {
2736 CodeIndexStateManager : vi . fn ( ) . mockImplementation ( ( ) => ( {
@@ -48,6 +57,13 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
4857 let mockContext : any
4958 let manager : CodeIndexManager
5059
60+ // Define test paths for use in tests
61+ const testWorkspacePath = path . join ( path . sep , "test" , "workspace" )
62+ const testExtensionPath = path . join ( path . sep , "test" , "extension" )
63+ const testStoragePath = path . join ( path . sep , "test" , "storage" )
64+ const testGlobalStoragePath = path . join ( path . sep , "test" , "global-storage" )
65+ const testLogPath = path . join ( path . sep , "test" , "log" )
66+
5167 beforeEach ( ( ) => {
5268 // Clear all instances before each test
5369 CodeIndexManager . disposeAll ( )
@@ -57,14 +73,14 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
5773 workspaceState : { } as any ,
5874 globalState : { } as any ,
5975 extensionUri : { } as any ,
60- extensionPath : "/test/extension" ,
76+ extensionPath : testExtensionPath ,
6177 asAbsolutePath : vi . fn ( ) ,
6278 storageUri : { } as any ,
63- storagePath : "/test/storage" ,
79+ storagePath : testStoragePath ,
6480 globalStorageUri : { } as any ,
65- globalStoragePath : "/test/global-storage" ,
81+ globalStoragePath : testGlobalStoragePath ,
6682 logUri : { } as any ,
67- logPath : "/test/log" ,
83+ logPath : testLogPath ,
6884 extensionMode : 3 , // vscode.ExtensionMode.Test
6985 secrets : { } as any ,
7086 environmentVariableCollection : { } as any ,
@@ -118,7 +134,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
118134 // Mock service factory to handle _recreateServices call
119135 const mockServiceFactoryInstance = {
120136 configManager : mockConfigManager ,
121- workspacePath : "/test/workspace" ,
137+ workspacePath : testWorkspacePath ,
122138 cacheManager : mockCacheManager ,
123139 createEmbedder : vi . fn ( ) . mockReturnValue ( { embedderInfo : { name : "openai" } } ) ,
124140 createVectorStore : vi . fn ( ) . mockReturnValue ( { } ) ,
@@ -192,7 +208,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
192208 // Mock service factory to handle _recreateServices call
193209 const mockServiceFactoryInstance = {
194210 configManager : mockConfigManager ,
195- workspacePath : "/test/workspace" ,
211+ workspacePath : testWorkspacePath ,
196212 cacheManager : mockCacheManager ,
197213 createEmbedder : vi . fn ( ) . mockReturnValue ( { embedderInfo : { name : "openai" } } ) ,
198214 createVectorStore : vi . fn ( ) . mockReturnValue ( { } ) ,
@@ -370,4 +386,199 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
370386 expect ( mockServiceFactoryInstance . validateEmbedder ) . not . toHaveBeenCalled ( )
371387 } )
372388 } )
389+
390+ describe ( "recoverFromError" , ( ) => {
391+ let mockConfigManager : any
392+ let mockCacheManager : any
393+ let mockStateManager : any
394+
395+ beforeEach ( ( ) => {
396+ // Mock config manager
397+ mockConfigManager = {
398+ loadConfiguration : vi . fn ( ) . mockResolvedValue ( { requiresRestart : false } ) ,
399+ isFeatureConfigured : true ,
400+ isFeatureEnabled : true ,
401+ getConfig : vi . fn ( ) . mockReturnValue ( {
402+ isConfigured : true ,
403+ embedderProvider : "openai" ,
404+ modelId : "text-embedding-3-small" ,
405+ openAiOptions : { openAiNativeApiKey : "test-key" } ,
406+ qdrantUrl : "http://localhost:6333" ,
407+ qdrantApiKey : "test-key" ,
408+ searchMinScore : 0.4 ,
409+ } ) ,
410+ }
411+ ; ( manager as any ) . _configManager = mockConfigManager
412+
413+ // Mock cache manager
414+ mockCacheManager = {
415+ initialize : vi . fn ( ) ,
416+ clearCacheFile : vi . fn ( ) ,
417+ }
418+ ; ( manager as any ) . _cacheManager = mockCacheManager
419+
420+ // Mock state manager
421+ mockStateManager = ( manager as any ) . _stateManager
422+ mockStateManager . setSystemState = vi . fn ( )
423+ mockStateManager . getCurrentStatus = vi . fn ( ) . mockReturnValue ( {
424+ systemStatus : "Error" ,
425+ message : "Failed during initial scan: fetch failed" ,
426+ processedItems : 0 ,
427+ totalItems : 0 ,
428+ currentItemUnit : "items" ,
429+ } )
430+
431+ // Mock orchestrator and search service to simulate initialized state
432+ ; ( manager as any ) . _orchestrator = { stopWatcher : vi . fn ( ) , state : "Error" }
433+ ; ( manager as any ) . _searchService = { }
434+ ; ( manager as any ) . _serviceFactory = { }
435+ } )
436+
437+ it ( "should clear error state when recoverFromError is called" , async ( ) => {
438+ // Act
439+ await manager . recoverFromError ( )
440+
441+ // Assert
442+ expect ( mockStateManager . setSystemState ) . toHaveBeenCalledWith ( "Standby" , "" )
443+ } )
444+
445+ it ( "should reset internal service instances" , async ( ) => {
446+ // Verify initial state
447+ expect ( ( manager as any ) . _configManager ) . toBeDefined ( )
448+ expect ( ( manager as any ) . _serviceFactory ) . toBeDefined ( )
449+ expect ( ( manager as any ) . _orchestrator ) . toBeDefined ( )
450+ expect ( ( manager as any ) . _searchService ) . toBeDefined ( )
451+
452+ // Act
453+ await manager . recoverFromError ( )
454+
455+ // Assert - all service instances should be undefined
456+ expect ( ( manager as any ) . _configManager ) . toBeUndefined ( )
457+ expect ( ( manager as any ) . _serviceFactory ) . toBeUndefined ( )
458+ expect ( ( manager as any ) . _orchestrator ) . toBeUndefined ( )
459+ expect ( ( manager as any ) . _searchService ) . toBeUndefined ( )
460+ } )
461+
462+ it ( "should make manager report as not initialized after recovery" , async ( ) => {
463+ // Verify initial state
464+ expect ( manager . isInitialized ) . toBe ( true )
465+
466+ // Act
467+ await manager . recoverFromError ( )
468+
469+ // Assert
470+ expect ( manager . isInitialized ) . toBe ( false )
471+ } )
472+
473+ it ( "should allow re-initialization after recovery" , async ( ) => {
474+ // Setup mock for re-initialization
475+ const mockServiceFactoryInstance = {
476+ createServices : vi . fn ( ) . mockReturnValue ( {
477+ embedder : { embedderInfo : { name : "openai" } } ,
478+ vectorStore : { } ,
479+ scanner : { } ,
480+ fileWatcher : {
481+ onDidStartBatchProcessing : vi . fn ( ) ,
482+ onBatchProgressUpdate : vi . fn ( ) ,
483+ watch : vi . fn ( ) ,
484+ stopWatcher : vi . fn ( ) ,
485+ dispose : vi . fn ( ) ,
486+ } ,
487+ } ) ,
488+ validateEmbedder : vi . fn ( ) . mockResolvedValue ( { valid : true } ) ,
489+ }
490+ MockedCodeIndexServiceFactory . mockImplementation ( ( ) => mockServiceFactoryInstance as any )
491+
492+ // Act - recover from error
493+ await manager . recoverFromError ( )
494+
495+ // Verify manager is not initialized
496+ expect ( manager . isInitialized ) . toBe ( false )
497+
498+ // Mock context proxy for initialization
499+ const mockContextProxy = {
500+ getValue : vi . fn ( ) ,
501+ setValue : vi . fn ( ) ,
502+ storeSecret : vi . fn ( ) ,
503+ getSecret : vi . fn ( ) ,
504+ refreshSecrets : vi . fn ( ) . mockResolvedValue ( undefined ) ,
505+ getGlobalState : vi . fn ( ) . mockReturnValue ( {
506+ codebaseIndexEnabled : true ,
507+ codebaseIndexQdrantUrl : "http://localhost:6333" ,
508+ codebaseIndexEmbedderProvider : "openai" ,
509+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
510+ codebaseIndexEmbedderModelDimension : 1536 ,
511+ codebaseIndexSearchMaxResults : 10 ,
512+ codebaseIndexSearchMinScore : 0.4 ,
513+ } ) ,
514+ }
515+
516+ // Re-initialize
517+ await manager . initialize ( mockContextProxy as any )
518+
519+ // Assert - manager should be initialized again
520+ expect ( manager . isInitialized ) . toBe ( true )
521+ expect ( mockServiceFactoryInstance . createServices ) . toHaveBeenCalled ( )
522+ expect ( mockServiceFactoryInstance . validateEmbedder ) . toHaveBeenCalled ( )
523+ } )
524+
525+ it ( "should be safe to call when not in error state (idempotent)" , async ( ) => {
526+ // Setup manager in non-error state
527+ mockStateManager . getCurrentStatus . mockReturnValue ( {
528+ systemStatus : "Standby" ,
529+ message : "" ,
530+ processedItems : 0 ,
531+ totalItems : 0 ,
532+ currentItemUnit : "items" ,
533+ } )
534+
535+ // Verify initial state is not error
536+ const initialStatus = manager . getCurrentStatus ( )
537+ expect ( initialStatus . systemStatus ) . not . toBe ( "Error" )
538+
539+ // Act - call recoverFromError when not in error state
540+ await expect ( manager . recoverFromError ( ) ) . resolves . not . toThrow ( )
541+
542+ // Assert - should still clear state and service instances
543+ expect ( mockStateManager . setSystemState ) . toHaveBeenCalledWith ( "Standby" , "" )
544+ expect ( ( manager as any ) . _configManager ) . toBeUndefined ( )
545+ expect ( ( manager as any ) . _serviceFactory ) . toBeUndefined ( )
546+ expect ( ( manager as any ) . _orchestrator ) . toBeUndefined ( )
547+ expect ( ( manager as any ) . _searchService ) . toBeUndefined ( )
548+ } )
549+
550+ it ( "should continue recovery even if setSystemState throws" , async ( ) => {
551+ // Setup state manager to throw on setSystemState
552+ mockStateManager . setSystemState . mockImplementation ( ( ) => {
553+ throw new Error ( "State update failed" )
554+ } )
555+
556+ // Setup manager with service instances
557+ ; ( manager as any ) . _configManager = mockConfigManager
558+ ; ( manager as any ) . _serviceFactory = { }
559+ ; ( manager as any ) . _orchestrator = { stopWatcher : vi . fn ( ) }
560+ ; ( manager as any ) . _searchService = { }
561+
562+ // Spy on console.error
563+ const consoleErrorSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } )
564+
565+ // Act - should not throw despite setSystemState error
566+ await expect ( manager . recoverFromError ( ) ) . resolves . not . toThrow ( )
567+
568+ // Assert - error should be logged
569+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
570+ "Failed to clear error state during recovery:" ,
571+ expect . any ( Error ) ,
572+ )
573+
574+ // Assert - service instances should still be cleared
575+ expect ( ( manager as any ) . _configManager ) . toBeUndefined ( )
576+ expect ( ( manager as any ) . _serviceFactory ) . toBeUndefined ( )
577+ expect ( ( manager as any ) . _orchestrator ) . toBeUndefined ( )
578+ expect ( ( manager as any ) . _searchService ) . toBeUndefined ( )
579+
580+ // Cleanup
581+ consoleErrorSpy . mockRestore ( )
582+ } )
583+ } )
373584} )
0 commit comments