import { ReviewCodeUseCaseImpl } from '../../../src/domain/usecases/ReviewCodeUseCase'; import { DiffProviderPort } from '../../../src/domain/ports/DiffProviderPort'; import { LLMPort } from '../../../src/domain/ports/LLMPort'; import { OutputPort } from '../../../src/domain/ports/OutputPort'; import { Diff, ReviewComment } from '../../../src/domain/entities'; describe('ReviewCodeUseCase', () => { let useCase: ReviewCodeUseCaseImpl; let mockDiffProvider: jest.Mocked; let mockLLMPort: jest.Mocked; let mockOutputPort: jest.Mocked; beforeEach(() => { mockDiffProvider = { getDiffs: jest.fn(), getFileDiff: jest.fn(), getCommitDiff: jest.fn(), getStagedDiff: jest.fn(), getUnstagedDiff: jest.fn(), hasChanges: jest.fn(), getCommitInfo: jest.fn(), }; mockLLMPort = { generateResponse: jest.fn(), generateStreamingResponse: jest.fn(), getAvailableModels: jest.fn(), isAvailable: jest.fn(), getModelInfo: jest.fn(), }; mockOutputPort = { displayReviewReport: jest.fn(), displaySecurityIssues: jest.fn(), displayTestSuggestions: jest.fn(), displayDocSuggestions: jest.fn(), displayMessage: jest.fn(), displayError: jest.fn(), displayWarning: jest.fn(), displaySuccess: jest.fn(), displayProgress: jest.fn(), clearProgress: jest.fn(), displayTable: jest.fn(), exportToFile: jest.fn(), }; useCase = new ReviewCodeUseCaseImpl(mockDiffProvider, mockLLMPort, mockOutputPort); }); it('should execute code review successfully', async () => { const mockDiff: Diff = { filePath: 'src/test.ts', oldContent: 'function test() { return true; }', newContent: 'function test() { console.log("test"); return true; }', hunks: [], language: 'typescript', isNewFile: false, isDeletedFile: false, isBinary: false, }; const mockLLMResponse = { content: JSON.stringify([ { message: 'Consider adding error handling', severity: 'suggestion', category: 'maintainability', lineNumber: 1, suggestion: 'Add try-catch blocks for better error handling', }, ]), usage: { promptTokens: 100, completionTokens: 50, totalTokens: 150, }, }; mockDiffProvider.getDiffs.mockResolvedValue([mockDiff]); mockLLMPort.generateResponse.mockResolvedValue(mockLLMResponse); mockOutputPort.displayReviewReport.mockResolvedValue(); const result = await useCase.execute(); expect(mockDiffProvider.getDiffs).toHaveBeenCalled(); expect(mockLLMPort.generateResponse).toHaveBeenCalled(); expect(mockOutputPort.displayReviewReport).toHaveBeenCalled(); expect(result.comments).toHaveLength(1); expect(result.comments[0].message).toBe('Consider adding error handling'); }); it('should handle no changes found', async () => { mockDiffProvider.getDiffs.mockResolvedValue([]); mockOutputPort.displayError.mockResolvedValue(); await expect(useCase.execute()).rejects.toThrow('No changes found to review'); expect(mockOutputPort.displayError).toHaveBeenCalled(); }); it('should handle LLM service failure gracefully', async () => { const mockDiff: Diff = { filePath: 'src/test.ts', oldContent: 'function test() { return true; }', newContent: 'function test() { console.log("test"); return true; }', hunks: [], language: 'typescript', isNewFile: false, isDeletedFile: false, isBinary: false, }; mockDiffProvider.getDiffs.mockResolvedValue([mockDiff]); mockLLMPort.generateResponse.mockRejectedValue(new Error('LLM service unavailable')); mockOutputPort.displayReviewReport.mockResolvedValue(); const result = await useCase.execute(); expect(result.comments).toHaveLength(1); expect(result.comments[0].message).toContain('Unable to analyze this file due to LLM service issues'); }); it('should skip binary files', async () => { const mockDiff: Diff = { filePath: 'image.png', oldContent: '', newContent: '', hunks: [], language: 'binary', isNewFile: true, isDeletedFile: false, isBinary: true, }; mockDiffProvider.getDiffs.mockResolvedValue([mockDiff]); mockOutputPort.displayReviewReport.mockResolvedValue(); const result = await useCase.execute(); expect(mockLLMPort.generateResponse).not.toHaveBeenCalled(); expect(result.comments).toHaveLength(0); }); });