directus-task-management/tests/api/nlp.integration.test.ts

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