directus-task-management/tests/services/nlp/nlp.service.test.ts

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
});
});
});