11import { Command } from 'commander' ;
22import { initCommand } from '../init' ;
3- import { AIService } from '../../services/aiService ' ;
4- import { ProgressTracker } from '../../services/progressTracker' ;
5- import { ErrorRecoveryService } from '../../services/errorRecovery ' ;
3+ import { AIIntelligenceService } from '../../../ services/aiIntelligenceService ' ;
4+ import { ProgressTracker } from '../../../ services/progressTracker' ;
5+ import { ErrorRecoveryService } from '../../../ services/errorRecoveryService ' ;
66import * as fs from 'fs-extra' ;
77import * as path from 'path' ;
88import { tmpdir } from 'os' ;
99
1010// Mock dependencies
11- jest . mock ( '../../services/aiService ' ) ;
12- jest . mock ( '../../services/progressTracker' ) ;
13- jest . mock ( '../../services/errorRecovery ' ) ;
11+ jest . mock ( '../../../ services/aiIntelligenceService ' ) ;
12+ jest . mock ( '../../../ services/progressTracker' ) ;
13+ jest . mock ( '../../../ services/errorRecoveryService ' ) ;
1414jest . mock ( 'inquirer' ) ;
1515
16- const MockAIService = AIService as jest . MockedClass < typeof AIService > ;
16+ const MockAIService = AIIntelligenceService as jest . MockedClass < typeof AIIntelligenceService > ;
1717const MockProgressTracker = ProgressTracker as jest . MockedClass < typeof ProgressTracker > ;
1818const MockErrorRecoveryService = ErrorRecoveryService as jest . MockedClass <
1919 typeof ErrorRecoveryService
2020> ;
2121
2222// Mock inquirer
23+ jest . mock ( 'inquirer' , ( ) => ( {
24+ prompt : jest . fn ( ) ,
25+ Separator : jest . fn ( )
26+ } ) ) ;
2327import inquirer from 'inquirer' ;
24- jest . mock ( 'inquirer' ) ;
28+ const mockPrompt = inquirer . prompt as unknown as jest . Mock ;
2529
2630// Mock console methods
2731const mockConsoleLog = jest . spyOn ( console , 'log' ) . mockImplementation ( ) ;
2832const mockConsoleError = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
2933
3034describe ( 'Init Command' , ( ) => {
3135 let tempDir : string ;
32- let mockAIService : jest . Mocked < AIService > ;
36+ let mockAIService : jest . Mocked < AIIntelligenceService > ;
3337 let mockProgressTracker : jest . Mocked < ProgressTracker > ;
3438 let mockErrorRecovery : jest . Mocked < ErrorRecoveryService > ;
3539
@@ -40,22 +44,20 @@ describe('Init Command', () => {
4044 // Setup mocks
4145 mockAIService = {
4246 isAIEnabled : jest . fn ( ) . mockReturnValue ( true ) ,
43- analyzeProject : jest . fn ( ) ,
47+ getAIStatus : jest . fn ( ) . mockReturnValue ( 'AI Ready' ) ,
4448 generateSmartDefaults : jest . fn ( ) ,
45- getProjectSuggestions : jest . fn ( ) ,
4649 } as any ;
4750
4851 mockProgressTracker = {
4952 startOperation : jest . fn ( ) . mockResolvedValue ( 'test-operation-id' ) ,
50- addStep : jest . fn ( ) ,
53+ addStep : jest . fn ( ) . mockResolvedValue ( 'step-id' ) ,
54+ completeStep : jest . fn ( ) ,
5155 completeOperation : jest . fn ( ) ,
5256 getProgress : jest . fn ( ) ,
5357 } as any ;
5458
5559 mockErrorRecovery = {
56- recoverFromError : jest . fn ( ) ,
57- analyzeError : jest . fn ( ) ,
58- getRecoveryActions : jest . fn ( ) ,
60+ handleError : jest . fn ( ) ,
5961 } as any ;
6062
6163 MockAIService . mockImplementation ( ( ) => mockAIService ) ;
@@ -75,7 +77,7 @@ describe('Init Command', () => {
7577 describe ( 'basic initialization' , ( ) => {
7678 it ( 'should run interactive setup wizard' , async ( ) => {
7779 // Mock inquirer responses
78- inquirer . prompt
80+ mockPrompt
7981 . mockResolvedValueOnce ( {
8082 projectName : 'test-project' ,
8183 projectType : 'web' ,
@@ -103,15 +105,12 @@ describe('Init Command', () => {
103105 } ) ;
104106
105107 mockAIService . generateSmartDefaults . mockResolvedValue ( {
106- projectType : 'web' ,
107- targetIDEs : [ 'claude' ] ,
108- techStack : { frontend : 'react' , backend : 'nodejs' } ,
108+ suggestions : [ ] ,
109109 confidence : 85 ,
110+ reasoning : 'AI analysis complete' ,
111+ optimizations : [ ] ,
110112 } ) ;
111113
112- mockAIService . getProjectSuggestions . mockResolvedValue ( [
113- { type : 'tech' , message : 'Consider using TypeScript' , confidence : 90 } ,
114- ] ) ;
115114
116115 const program = new Command ( ) ;
117116 program . addCommand ( initCommand ) ;
@@ -128,15 +127,19 @@ describe('Init Command', () => {
128127 ) ;
129128
130129 expect ( mockProgressTracker . addStep ) . toHaveBeenCalledWith ( 'Interactive setup wizard' ) ;
131- expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith ( true , expect . any ( Object ) ) ;
130+ expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
131+ 'test-operation-id' ,
132+ 'completed' ,
133+ expect . any ( Object )
134+ ) ;
132135 } ) ;
133136
134137 it ( 'should handle --quick option with AI defaults' , async ( ) => {
135138 mockAIService . generateSmartDefaults . mockResolvedValue ( {
136- projectType : 'fullstack' ,
137- targetIDEs : [ 'claude' ] ,
138- techStack : { frontend : 'nextjs' , backend : 'fastapi' } ,
139+ suggestions : [ ] ,
139140 confidence : 95 ,
141+ reasoning : 'AI analysis complete' ,
142+ optimizations : [ ] ,
140143 } ) ;
141144
142145 const program = new Command ( ) ;
@@ -151,7 +154,7 @@ describe('Init Command', () => {
151154
152155 it ( 'should handle --no-ai option' , async ( ) => {
153156 // Mock non-AI responses
154- inquirer . prompt . mockResolvedValue ( {
157+ mockPrompt . mockResolvedValue ( {
155158 projectName : 'no-ai-project' ,
156159 projectType : 'api' ,
157160 targetIDEs : [ 'cursor' ] ,
@@ -164,7 +167,7 @@ describe('Init Command', () => {
164167 await program . parseAsync ( [ 'node' , 'test' , 'init' , '--no-ai' ] , { from : 'user' } ) ;
165168
166169 expect ( mockAIService . generateSmartDefaults ) . not . toHaveBeenCalled ( ) ;
167- expect ( mockAIService . getProjectSuggestions ) . not . toHaveBeenCalled ( ) ;
170+ expect ( mockAIService . generateSmartDefaults ) . not . toHaveBeenCalled ( ) ;
168171 expect ( mockProgressTracker . startOperation ) . toHaveBeenCalledWith (
169172 'init' ,
170173 'Project initialization' ,
@@ -179,7 +182,7 @@ describe('Init Command', () => {
179182 it ( 'should handle --output option' , async ( ) => {
180183 const outputDir = path . join ( tempDir , 'custom-output' ) ;
181184
182- inquirer . prompt . mockResolvedValue ( {
185+ mockPrompt . mockResolvedValue ( {
183186 projectName : 'output-test' ,
184187 projectType : 'web' ,
185188 targetIDEs : [ 'claude' ] ,
@@ -196,7 +199,7 @@ describe('Init Command', () => {
196199 } ) ;
197200
198201 it ( 'should handle --ide option' , async ( ) => {
199- inquirer . prompt . mockResolvedValue ( {
202+ mockPrompt . mockResolvedValue ( {
200203 projectName : 'ide-test' ,
201204 projectType : 'web' ,
202205 confirmed : true ,
@@ -241,7 +244,7 @@ describe('Init Command', () => {
241244
242245 describe ( 'file generation' , ( ) => {
243246 it ( 'should generate CLAUDE.md file' , async ( ) => {
244- inquirer . prompt . mockResolvedValue ( {
247+ mockPrompt . mockResolvedValue ( {
245248 projectName : 'file-gen-test' ,
246249 projectType : 'web' ,
247250 description : 'Test project for file generation' ,
@@ -264,7 +267,7 @@ describe('Init Command', () => {
264267 } ) ;
265268
266269 it ( 'should generate PRPs directory' , async ( ) => {
267- inquirer . prompt . mockResolvedValue ( {
270+ mockPrompt . mockResolvedValue ( {
268271 projectName : 'prp-test' ,
269272 projectType : 'fullstack' ,
270273 targetIDEs : [ 'claude' ] ,
@@ -283,7 +286,7 @@ describe('Init Command', () => {
283286 } ) ;
284287
285288 it ( 'should generate .claude directory for Claude IDE' , async ( ) => {
286- inquirer . prompt . mockResolvedValue ( {
289+ mockPrompt . mockResolvedValue ( {
287290 projectName : 'claude-dir-test' ,
288291 projectType : 'web' ,
289292 targetIDEs : [ 'claude' ] ,
@@ -306,39 +309,34 @@ describe('Init Command', () => {
306309 describe ( 'error handling' , ( ) => {
307310 it ( 'should handle setup wizard errors with recovery' , async ( ) => {
308311 const error = new Error ( 'Permission denied' ) ;
309- inquirer . prompt . mockRejectedValue ( error ) ;
312+ mockPrompt . mockRejectedValue ( error ) ;
310313
311- mockErrorRecovery . recoverFromError . mockResolvedValue ( {
312- attempted : 1 ,
313- successful : 1 ,
314- actions : [ ] ,
315- manualActions : [ ] ,
316- } ) ;
317314
318315 const program = new Command ( ) ;
319316 program . addCommand ( initCommand ) ;
320317
321318 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
322319
323- expect ( mockErrorRecovery . recoverFromError ) . toHaveBeenCalledWith ( error , tempDir ) ;
320+ expect ( mockErrorRecovery . handleError ) . toHaveBeenCalled ( ) ;
324321 expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
325- false ,
326- undefined ,
327- expect . stringContaining ( 'Permission denied' )
322+ 'test-operation-id' ,
323+ 'failed' ,
324+ expect . objectContaining ( {
325+ errors : expect . arrayContaining ( [ expect . stringContaining ( 'Permission denied' ) ] )
326+ } )
328327 ) ;
329328 } ) ;
330329
331330 it ( 'should handle file generation errors' , async ( ) => {
332- inquirer . prompt . mockResolvedValue ( {
331+ mockPrompt . mockResolvedValue ( {
333332 projectName : 'error-test' ,
334333 projectType : 'web' ,
335334 targetIDEs : [ 'claude' ] ,
336335 confirmed : true ,
337336 } ) ;
338337
339338 // Mock file system error
340- const originalWriteFile = fs . writeFile ;
341- jest . spyOn ( fs , 'writeFile' ) . mockRejectedValue ( new Error ( 'Disk full' ) ) ;
339+ ( fs . writeFile as any ) = jest . fn ( ) . mockRejectedValue ( new Error ( 'Disk full' ) ) ;
342340
343341 const program = new Command ( ) ;
344342 program . addCommand ( initCommand ) ;
@@ -350,13 +348,13 @@ describe('Init Command', () => {
350348 ) ;
351349
352350 // Restore original function
353- ( fs . writeFile as jest . Mock ) . mockImplementation ( originalWriteFile ) ;
351+ jest . restoreAllMocks ( ) ;
354352 } ) ;
355353
356354 it ( 'should handle AI service errors gracefully' , async ( ) => {
357355 mockAIService . generateSmartDefaults . mockRejectedValue ( new Error ( 'AI service unavailable' ) ) ;
358356
359- inquirer . prompt . mockResolvedValue ( {
357+ mockPrompt . mockResolvedValue ( {
360358 projectName : 'ai-error-test' ,
361359 projectType : 'web' ,
362360 targetIDEs : [ 'claude' ] ,
@@ -369,13 +367,17 @@ describe('Init Command', () => {
369367 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
370368
371369 // Should continue with fallback behavior
372- expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith ( true , expect . any ( Object ) ) ;
370+ expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
371+ 'test-operation-id' ,
372+ 'completed' ,
373+ expect . any ( Object )
374+ ) ;
373375 } ) ;
374376 } ) ;
375377
376378 describe ( 'progress tracking' , ( ) => {
377379 it ( 'should track all major steps' , async ( ) => {
378- inquirer . prompt . mockResolvedValue ( {
380+ mockPrompt . mockResolvedValue ( {
379381 projectName : 'progress-test' ,
380382 projectType : 'fullstack' ,
381383 targetIDEs : [ 'claude' , 'cursor' ] ,
@@ -401,7 +403,7 @@ describe('Init Command', () => {
401403 } ) ;
402404
403405 it ( 'should include metadata in progress tracking' , async ( ) => {
404- inquirer . prompt . mockResolvedValue ( {
406+ mockPrompt . mockResolvedValue ( {
405407 projectName : 'metadata-test' ,
406408 projectType : 'api' ,
407409 targetIDEs : [ 'claude' ] ,
@@ -415,19 +417,19 @@ describe('Init Command', () => {
415417 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
416418
417419 expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
418- true ,
420+ 'test-operation-id' ,
421+ 'completed' ,
419422 expect . objectContaining ( {
420423 filesGenerated : expect . any ( Number ) ,
421- ideConfigurations : expect . any ( Array ) ,
422- features : expect . any ( Array ) ,
424+ targetIDEs : expect . any ( Array )
423425 } )
424426 ) ;
425427 } ) ;
426428 } ) ;
427429
428430 describe ( 'validation' , ( ) => {
429431 it ( 'should validate project name input' , async ( ) => {
430- inquirer . prompt
432+ mockPrompt
431433 . mockResolvedValueOnce ( {
432434 projectName : '' , // Invalid empty name
433435 projectType : 'web' ,
@@ -445,11 +447,15 @@ describe('Init Command', () => {
445447 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
446448
447449 // Should eventually succeed with valid name
448- expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith ( true , expect . any ( Object ) ) ;
450+ expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
451+ 'test-operation-id' ,
452+ 'completed' ,
453+ expect . any ( Object )
454+ ) ;
449455 } ) ;
450456
451457 it ( 'should validate IDE selection' , async ( ) => {
452- inquirer . prompt . mockResolvedValue ( {
458+ mockPrompt . mockResolvedValue ( {
453459 projectName : 'ide-validation-test' ,
454460 projectType : 'web' ,
455461 targetIDEs : [ 'claude' , 'invalid-ide' ] , // Invalid IDE should be filtered
@@ -473,16 +479,8 @@ describe('Init Command', () => {
473479
474480 describe ( 'AI integration' , ( ) => {
475481 it ( 'should use AI suggestions when available' , async ( ) => {
476- mockAIService . getProjectSuggestions . mockResolvedValue ( [
477- {
478- type : 'tech' ,
479- message : 'Consider using TypeScript for better type safety' ,
480- confidence : 90 ,
481- } ,
482- { type : 'config' , message : 'Enable ESLint for code quality' , confidence : 85 } ,
483- ] ) ;
484-
485- inquirer . prompt . mockResolvedValue ( {
482+
483+ mockPrompt . mockResolvedValue ( {
486484 projectName : 'ai-suggestions-test' ,
487485 projectType : 'web' ,
488486 targetIDEs : [ 'claude' ] ,
@@ -494,22 +492,13 @@ describe('Init Command', () => {
494492
495493 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
496494
497- expect ( mockAIService . getProjectSuggestions ) . toHaveBeenCalledWith (
498- expect . objectContaining ( {
499- name : 'ai-suggestions-test' ,
500- type : 'web' ,
501- } )
502- ) ;
503-
504- expect ( mockConsoleLog ) . toHaveBeenCalledWith (
505- expect . stringContaining ( 'Consider using TypeScript' )
506- ) ;
495+ expect ( mockAIService . generateSmartDefaults ) . toHaveBeenCalled ( ) ;
507496 } ) ;
508497
509498 it ( 'should fall back gracefully when AI is disabled' , async ( ) => {
510499 mockAIService . isAIEnabled . mockReturnValue ( false ) ;
511500
512- inquirer . prompt . mockResolvedValue ( {
501+ mockPrompt . mockResolvedValue ( {
513502 projectName : 'no-ai-fallback-test' ,
514503 projectType : 'web' ,
515504 targetIDEs : [ 'claude' ] ,
@@ -522,7 +511,11 @@ describe('Init Command', () => {
522511 await program . parseAsync ( [ 'node' , 'test' , 'init' ] , { from : 'user' } ) ;
523512
524513 expect ( mockAIService . generateSmartDefaults ) . not . toHaveBeenCalled ( ) ;
525- expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith ( true , expect . any ( Object ) ) ;
514+ expect ( mockProgressTracker . completeOperation ) . toHaveBeenCalledWith (
515+ 'test-operation-id' ,
516+ 'completed' ,
517+ expect . any ( Object )
518+ ) ;
526519 } ) ;
527520 } ) ;
528521} ) ;
0 commit comments