334 lines
11 KiB
TypeScript
334 lines
11 KiB
TypeScript
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<TextParserService>;
|
|
let mockEntityExtractor: jest.Mocked<EntityExtractorService>;
|
|
let mockTaskClassifier: jest.Mocked<TaskClassifierService>;
|
|
let mockStructuredDataGenerator: jest.Mocked<StructuredDataGeneratorService>;
|
|
let mockLanguageDetector: jest.Mocked<LanguageDetectorService>;
|
|
let mockTranslationService: jest.Mocked<TranslationService>;
|
|
|
|
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
|
|
});
|
|
});
|
|
}); |