import 'reflect-metadata'; import { container } from 'tsyringe'; import { NLPService } from '../../../src/services/nlp/nlp.service'; import { TextParserService } from '../../../src/services/nlp/parsers/text-parser.service'; import { EntityExtractorService } from '../../../src/services/nlp/extractors/entity-extractor.service'; import { TaskClassifierService } from '../../../src/services/nlp/classifiers/task-classifier.service'; import { StructuredDataGeneratorService } from '../../../src/services/nlp/generators/structured-data.service'; import { LanguageDetectorService } from '../../../src/services/nlp/language-detector.service'; import { TranslationService } from '../../../src/services/nlp/translation.service'; // Mock dependencies jest.mock('../../../src/services/nlp/parsers/text-parser.service'); jest.mock('../../../src/services/nlp/extractors/entity-extractor.service'); jest.mock('../../../src/services/nlp/classifiers/task-classifier.service'); jest.mock('../../../src/services/nlp/generators/structured-data.service'); jest.mock('../../../src/services/nlp/language-detector.service'); jest.mock('../../../src/services/nlp/translation.service'); describe('NLPService', () => { let service: NLPService; let mockTextParser: jest.Mocked; let mockEntityExtractor: jest.Mocked; let mockTaskClassifier: jest.Mocked; let mockStructuredDataGenerator: jest.Mocked; let mockLanguageDetector: jest.Mocked; let mockTranslationService: jest.Mocked; beforeEach(() => { // Clear all mocks jest.clearAllMocks(); container.clearInstances(); // Create mock services mockTextParser = { parse: jest.fn(), } as any; mockEntityExtractor = { extract: jest.fn(), } as any; mockTaskClassifier = { classify: jest.fn(), } as any; mockStructuredDataGenerator = { generate: jest.fn(), } as any; mockLanguageDetector = { detectLanguage: jest.fn(), } as any; mockTranslationService = { translate: jest.fn(), } as any; // Register mocks with container container.registerInstance(TextParserService, mockTextParser); container.registerInstance(EntityExtractorService, mockEntityExtractor); container.registerInstance(TaskClassifierService, mockTaskClassifier); container.registerInstance(StructuredDataGeneratorService, mockStructuredDataGenerator); container.registerInstance(LanguageDetectorService, mockLanguageDetector); container.registerInstance(TranslationService, mockTranslationService); // Create service service = container.resolve(NLPService); }); describe('processText', () => { it('should process text through the full NLP pipeline', async () => { // Arrange const input = { text: 'Create a high-priority task to implement user authentication by Friday', language: 'en', }; const mockParsedData: any = { originalText: input.text, sentences: [{ text: input.text, tokens: [] }], partsOfSpeech: [], syntacticStructure: {}, language: 'en', keywords: ['authentication', 'user'], }; const mockEntities: any = { title: 'Implement user authentication', priority: 'high' as const, deadline: new Date('2025-01-17'), estimatedHours: 8, confidence: 0.9, }; const mockClassification: any = { type: 'feature' as const, urgency: 'normal' as const, sentiment: 'neutral' as const, complexity: 'moderate' as const, confidence: 0.85, }; const mockStructuredData: any = { title: 'Implement user authentication', description: 'High-priority task for user authentication', priority: 'high', deadline: '2025-01-17', taskType: 'feature', estimatedHours: 8, }; mockTextParser.parse.mockResolvedValue(mockParsedData); mockEntityExtractor.extract.mockResolvedValue(mockEntities); mockTaskClassifier.classify.mockResolvedValue(mockClassification); mockStructuredDataGenerator.generate.mockResolvedValue(mockStructuredData); // Act const result = await service.processText(input); // Assert expect(result.success).toBe(true); expect(result.entities).toEqual(mockEntities); expect(result.classification).toEqual(mockClassification); expect(result.structuredData).toEqual(mockStructuredData); expect(result.language).toBe('en'); expect(mockTextParser.parse).toHaveBeenCalledWith(input.text, 'en'); expect(mockEntityExtractor.extract).toHaveBeenCalledWith({ text: input.text, parsedData: mockParsedData, language: 'en', }); expect(mockTaskClassifier.classify).toHaveBeenCalledWith({ text: input.text, entities: mockEntities, language: 'en', }); expect(mockStructuredDataGenerator.generate).toHaveBeenCalledWith({ entities: mockEntities, classification: mockClassification, originalText: input.text, }); }); it('should handle errors gracefully', async () => { // Arrange const input = { text: 'Test text', language: 'en', }; mockTextParser.parse.mockRejectedValue(new Error('Parser error')); // Act const result = await service.processText(input); // Assert expect(result.success).toBe(false); expect(result.errors).toContain('Parser error'); }); it('should use cache for repeated requests', async () => { // Arrange const input = { text: 'Cached request', language: 'en', }; const mockParsedData: any = { originalText: input.text, sentences: [{ text: input.text, tokens: [] }], partsOfSpeech: [], syntacticStructure: {}, language: 'en', keywords: [], }; const mockEntities: any = { title: 'Cached request' }; const mockClassification: any = { type: 'task' as const }; const mockStructuredData: any = { title: 'Cached request' }; mockTextParser.parse.mockResolvedValue(mockParsedData); mockEntityExtractor.extract.mockResolvedValue(mockEntities); mockTaskClassifier.classify.mockResolvedValue(mockClassification); mockStructuredDataGenerator.generate.mockResolvedValue(mockStructuredData); // Act - First call await service.processText(input); // Act - Second call (should use cache) const result = await service.processText(input); // Assert expect(result.success).toBe(true); expect(mockTextParser.parse).toHaveBeenCalledTimes(1); // Only called once due to cache }); }); describe('processTextWithTranslation', () => { it('should detect language and translate if needed', async () => { // Arrange const input = { text: 'Créer une tâche urgente pour demain', }; const mockDetectedLanguage = { language: 'fr', confidence: 0.95, }; const mockTranslation = { originalText: input.text, translatedText: 'Create an urgent task for tomorrow', sourceLanguage: 'fr', targetLanguage: 'en', confidence: 0.9, }; const mockParsedData: any = { originalText: 'Create an urgent task for tomorrow', sentences: [{ text: 'Create an urgent task for tomorrow', tokens: [] }], partsOfSpeech: [], syntacticStructure: {}, language: 'en', keywords: ['urgent', 'task'], }; const mockEntities: any = { title: 'Urgent task', priority: 'high' as const }; const mockClassification: any = { type: 'task' as const, urgency: 'urgent' as const }; const mockStructuredData: any = { title: 'Urgent task' }; mockLanguageDetector.detectLanguage.mockResolvedValue(mockDetectedLanguage); mockTranslationService.translate.mockResolvedValue(mockTranslation); mockTextParser.parse.mockResolvedValue(mockParsedData); mockEntityExtractor.extract.mockResolvedValue(mockEntities); mockTaskClassifier.classify.mockResolvedValue(mockClassification); mockStructuredDataGenerator.generate.mockResolvedValue(mockStructuredData); // Act const result = await service.processTextWithTranslation(input); // Assert expect(result.success).toBe(true); expect(result.language).toBe('fr'); expect(mockLanguageDetector.detectLanguage).toHaveBeenCalledWith(input.text); expect(mockTranslationService.translate).toHaveBeenCalledWith({ text: input.text, sourceLanguage: 'fr', targetLanguage: 'en', preserveFormatting: true, context: 'task management', }); }); it('should skip translation for English text', async () => { // Arrange const input = { text: 'Create a task', language: 'en', }; const mockParsedData: any = { originalText: input.text, sentences: [{ text: input.text, tokens: [] }], partsOfSpeech: [], syntacticStructure: {}, language: 'en', keywords: [], }; const mockEntities: any = { title: 'Create a task' }; const mockClassification: any = { type: 'task' as const }; const mockStructuredData: any = { title: 'Create a task' }; mockTextParser.parse.mockResolvedValue(mockParsedData); mockEntityExtractor.extract.mockResolvedValue(mockEntities); mockTaskClassifier.classify.mockResolvedValue(mockClassification); mockStructuredDataGenerator.generate.mockResolvedValue(mockStructuredData); // Act const result = await service.processTextWithTranslation(input); // Assert expect(result.success).toBe(true); expect(result.language).toBe('en'); expect(mockTranslationService.translate).not.toHaveBeenCalled(); }); }); describe('getCacheStats', () => { it('should return cache statistics', () => { // Act const stats = service.getCacheStats(); // Assert expect(stats).toHaveProperty('hits'); expect(stats).toHaveProperty('misses'); expect(stats).toHaveProperty('size'); expect(stats).toHaveProperty('hitRate'); }); }); describe('clearCache', () => { it('should clear the cache', async () => { // Arrange const input = { text: 'Test for cache clearing', language: 'en', }; const mockParsedData: any = { originalText: input.text, sentences: [{ text: input.text, tokens: [] }], partsOfSpeech: [], syntacticStructure: {}, language: 'en', keywords: [], }; const mockEntities: any = { title: 'Test' }; const mockClassification: any = { type: 'task' as const }; const mockStructuredData: any = { title: 'Test' }; mockTextParser.parse.mockResolvedValue(mockParsedData); mockEntityExtractor.extract.mockResolvedValue(mockEntities); mockTaskClassifier.classify.mockResolvedValue(mockClassification); mockStructuredDataGenerator.generate.mockResolvedValue(mockStructuredData); // Act await service.processText(input); // First call service.clearCache(); await service.processText(input); // Second call after cache clear // Assert expect(mockTextParser.parse).toHaveBeenCalledTimes(2); // Called twice due to cache clear }); }); });