450 lines
14 KiB
TypeScript
450 lines
14 KiB
TypeScript
import 'reflect-metadata';
|
|
import request from 'supertest';
|
|
import express, { Application } from 'express';
|
|
import { container } from 'tsyringe';
|
|
import createNLPRoutes from '../../src/api/routes/nlp.routes';
|
|
import { NLPService } from '../../src/services/nlp/nlp.service';
|
|
import { TranslationService } from '../../src/services/nlp/translation.service';
|
|
import { LanguageDetectorService } from '../../src/services/nlp/language-detector.service';
|
|
import { authMiddleware } from '../../src/api/middleware/auth.middleware';
|
|
|
|
// Mock services
|
|
jest.mock('../../src/services/nlp/nlp.service');
|
|
jest.mock('../../src/services/nlp/translation.service');
|
|
jest.mock('../../src/services/nlp/language-detector.service');
|
|
jest.mock('../../src/api/middleware/auth.middleware', () => ({
|
|
authMiddleware: jest.fn((req, res, next) => {
|
|
req.user = {
|
|
id: 'test-user',
|
|
email: 'test@example.com',
|
|
role: 'ADMIN',
|
|
permissions: ['CREATE_TASK', 'UPDATE_TASK'],
|
|
};
|
|
next();
|
|
}),
|
|
}));
|
|
|
|
describe('NLP API Integration Tests', () => {
|
|
let app: Application;
|
|
let mockNLPService: jest.Mocked<NLPService>;
|
|
let mockTranslationService: jest.Mocked<TranslationService>;
|
|
let mockLanguageDetector: jest.Mocked<LanguageDetectorService>;
|
|
|
|
beforeEach(() => {
|
|
// Clear all mocks
|
|
jest.clearAllMocks();
|
|
container.clearInstances();
|
|
|
|
// Create Express app
|
|
app = express();
|
|
app.use(express.json());
|
|
|
|
// Create mock services
|
|
mockNLPService = {
|
|
processText: jest.fn(),
|
|
processTextWithTranslation: jest.fn(),
|
|
getCacheStats: jest.fn(),
|
|
clearCache: jest.fn(),
|
|
} as any;
|
|
|
|
mockTranslationService = {
|
|
translate: jest.fn(),
|
|
getCacheStats: jest.fn(),
|
|
clearCache: jest.fn(),
|
|
} as any;
|
|
|
|
mockLanguageDetector = {
|
|
detectLanguage: jest.fn(),
|
|
getSupportedLanguages: jest.fn(),
|
|
} as any;
|
|
|
|
// Register mocks with container
|
|
container.registerInstance(NLPService, mockNLPService);
|
|
container.registerInstance(TranslationService, mockTranslationService);
|
|
container.registerInstance(LanguageDetectorService, mockLanguageDetector);
|
|
|
|
// Setup routes
|
|
app.use('/api/nlp', createNLPRoutes());
|
|
});
|
|
|
|
describe('POST /api/nlp/parse', () => {
|
|
it('should parse natural language text successfully', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
success: true,
|
|
entities: {
|
|
title: 'Implement user authentication',
|
|
priority: 'high',
|
|
deadline: '2025-01-20',
|
|
},
|
|
classification: {
|
|
taskType: 'feature',
|
|
urgency: 'medium',
|
|
},
|
|
structuredData: {
|
|
title: 'Implement user authentication',
|
|
description: 'Add user authentication feature',
|
|
},
|
|
language: 'en',
|
|
processingTime: 150,
|
|
tokensUsed: 200,
|
|
errors: [],
|
|
};
|
|
|
|
mockNLPService.processTextWithTranslation.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/parse')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Create a high-priority task to implement user authentication by Monday',
|
|
language: 'en',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.entities.title).toBe('Implement user authentication');
|
|
expect(response.body.data.classification.taskType).toBe('feature');
|
|
expect(mockNLPService.processTextWithTranslation).toHaveBeenCalledWith({
|
|
text: 'Create a high-priority task to implement user authentication by Monday',
|
|
language: 'en',
|
|
projectId: undefined,
|
|
userId: undefined,
|
|
context: undefined,
|
|
});
|
|
});
|
|
|
|
it('should handle validation errors', async () => {
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/parse')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
// Missing required 'text' field
|
|
language: 'en',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toBe('Validation Error');
|
|
expect(response.body.details).toBeDefined();
|
|
});
|
|
|
|
it('should handle service errors', async () => {
|
|
// Arrange
|
|
mockNLPService.processTextWithTranslation.mockRejectedValue(
|
|
new Error('Processing failed')
|
|
);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/parse')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Test text',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(500);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toBe('Failed to parse text');
|
|
expect(response.body.message).toBe('Processing failed');
|
|
});
|
|
|
|
it('should include optional context in processing', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
success: true,
|
|
entities: { title: 'Task' },
|
|
classification: { taskType: 'task' },
|
|
structuredData: { title: 'Task' },
|
|
language: 'en',
|
|
processingTime: 100,
|
|
tokensUsed: 150,
|
|
errors: [],
|
|
};
|
|
|
|
mockNLPService.processTextWithTranslation.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/parse')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Create task',
|
|
projectId: 'project-123',
|
|
userId: 'user-456',
|
|
context: { custom: 'data' },
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(mockNLPService.processTextWithTranslation).toHaveBeenCalledWith({
|
|
text: 'Create task',
|
|
language: undefined,
|
|
projectId: 'project-123',
|
|
userId: 'user-456',
|
|
context: { custom: 'data' },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('POST /api/nlp/extract-entities', () => {
|
|
it('should extract entities from text', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
success: true,
|
|
entities: {
|
|
title: 'Code review',
|
|
assignees: ['John', 'Sarah'],
|
|
deadline: '2025-01-18',
|
|
confidence: 0.9,
|
|
},
|
|
classification: {},
|
|
structuredData: {},
|
|
language: 'en',
|
|
processingTime: 120,
|
|
};
|
|
|
|
mockNLPService.processText.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/extract-entities')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'John and Sarah should review the code by Friday',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.entities.assignees).toEqual(['John', 'Sarah']);
|
|
expect(response.body.metadata.confidence).toBe(0.9);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/nlp/classify', () => {
|
|
it('should classify task type and attributes', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
success: true,
|
|
entities: {},
|
|
classification: {
|
|
taskType: 'bug',
|
|
priority: 'high',
|
|
severity: 'critical',
|
|
confidence: 0.95,
|
|
},
|
|
structuredData: {},
|
|
language: 'en',
|
|
processingTime: 100,
|
|
};
|
|
|
|
mockNLPService.processText.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/classify')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Fix critical bug in payment processing',
|
|
entities: {
|
|
title: 'Fix payment bug',
|
|
priority: 'high',
|
|
},
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.classification.taskType).toBe('bug');
|
|
expect(response.body.data.classification.severity).toBe('critical');
|
|
expect(response.body.metadata.confidence).toBe(0.95);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/nlp/translate', () => {
|
|
it('should translate text between languages', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
originalText: 'Créer une nouvelle tâche',
|
|
translatedText: 'Create a new task',
|
|
sourceLanguage: 'fr',
|
|
targetLanguage: 'en',
|
|
confidence: 0.92,
|
|
alternatives: ['Make a new task', 'Add a new task'],
|
|
};
|
|
|
|
mockTranslationService.translate.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/translate')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Créer une nouvelle tâche',
|
|
targetLanguage: 'en',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.translatedText).toBe('Create a new task');
|
|
expect(response.body.data.sourceLanguage).toBe('fr');
|
|
expect(response.body.metadata.alternatives).toContain('Make a new task');
|
|
});
|
|
|
|
it('should validate required target language', async () => {
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/translate')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Test text',
|
|
// Missing targetLanguage
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toBe('Validation Error');
|
|
});
|
|
});
|
|
|
|
describe('POST /api/nlp/detect-language', () => {
|
|
it('should detect language of input text', async () => {
|
|
// Arrange
|
|
const mockResult: any = {
|
|
language: 'es',
|
|
confidence: 0.98,
|
|
alternatives: [
|
|
{ language: 'pt', confidence: 0.15 },
|
|
{ language: 'it', confidence: 0.08 },
|
|
],
|
|
};
|
|
|
|
mockLanguageDetector.detectLanguage.mockResolvedValue(mockResult);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.post('/api/nlp/detect-language')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.send({
|
|
text: 'Necesito crear una tarea urgente',
|
|
});
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.language).toBe('es');
|
|
expect(response.body.data.confidence).toBe(0.98);
|
|
expect(response.body.metadata.alternatives).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/nlp/languages', () => {
|
|
it('should return supported languages', async () => {
|
|
// Arrange
|
|
const mockLanguages: any = [
|
|
{ code: 'en', name: 'English' },
|
|
{ code: 'es', name: 'Spanish' },
|
|
{ code: 'fr', name: 'French' },
|
|
{ code: 'de', name: 'German' },
|
|
{ code: 'pt', name: 'Portuguese' },
|
|
{ code: 'it', name: 'Italian' },
|
|
{ code: 'zh', name: 'Chinese' },
|
|
{ code: 'ja', name: 'Japanese' },
|
|
];
|
|
|
|
mockLanguageDetector.getSupportedLanguages.mockReturnValue(mockLanguages);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.get('/api/nlp/languages')
|
|
.set('Authorization', 'Bearer test-token');
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.languages).toHaveLength(8);
|
|
expect(response.body.data.languages[0].code).toBe('en');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/nlp/cache/stats', () => {
|
|
it('should return cache statistics', async () => {
|
|
// Arrange
|
|
const mockNLPStats: any = {
|
|
size: 25,
|
|
maxAge: 3600000,
|
|
};
|
|
|
|
const mockTranslationStats: any = {
|
|
size: 15,
|
|
maxAge: 3600000,
|
|
};
|
|
|
|
mockNLPService.getCacheStats.mockReturnValue(mockNLPStats);
|
|
mockTranslationService.getCacheStats.mockReturnValue(mockTranslationStats);
|
|
|
|
// Act
|
|
const response = await request(app)
|
|
.get('/api/nlp/cache/stats')
|
|
.set('Authorization', 'Bearer test-token');
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.nlpCache.size).toBe(25);
|
|
expect(response.body.data.translationCache.size).toBe(15);
|
|
});
|
|
});
|
|
|
|
describe('DELETE /api/nlp/cache', () => {
|
|
it('should clear NLP caches', async () => {
|
|
// Act
|
|
const response = await request(app)
|
|
.delete('/api/nlp/cache')
|
|
.set('Authorization', 'Bearer test-token');
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.message).toBe('Cache cleared successfully');
|
|
expect(mockNLPService.clearCache).toHaveBeenCalled();
|
|
expect(mockTranslationService.clearCache).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Rate Limiting', () => {
|
|
it('should apply rate limiting to NLP endpoints', async () => {
|
|
// Note: This would require mocking the rate limiter
|
|
// For now, we'll just verify the endpoint accepts the rate limit header
|
|
const response = await request(app)
|
|
.post('/api/nlp/parse')
|
|
.set('Authorization', 'Bearer test-token')
|
|
.set('X-RateLimit-Limit', '100')
|
|
.send({
|
|
text: 'Test rate limiting',
|
|
});
|
|
|
|
// Just verify the endpoint is accessible
|
|
expect([200, 429, 500]).toContain(response.status);
|
|
});
|
|
});
|
|
|
|
describe('Authentication', () => {
|
|
it('should require authentication for all endpoints', async () => {
|
|
// Remove auth middleware mock for this test
|
|
jest.resetModules();
|
|
|
|
// This test would verify that without proper auth, requests are rejected
|
|
// For now, we verify the middleware is called
|
|
expect(authMiddleware).toBeDefined();
|
|
});
|
|
});
|
|
}); |