455 lines
18 KiB
JavaScript
455 lines
18 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
require("reflect-metadata");
|
|
const supertest_1 = __importDefault(require("supertest"));
|
|
const express_1 = __importDefault(require("express"));
|
|
// Mock services
|
|
const mockAITaskService = {
|
|
createTaskFromNaturalLanguage: jest.fn(),
|
|
updateTaskContext: jest.fn(),
|
|
getTaskSuggestions: jest.fn(),
|
|
breakdownTask: jest.fn(),
|
|
getHealthStatus: jest.fn(),
|
|
getUsageStats: jest.fn(),
|
|
warmCache: jest.fn(),
|
|
};
|
|
const mockOpenAIService = {
|
|
testConnection: jest.fn(),
|
|
};
|
|
const mockLangChainService = {
|
|
testConnection: jest.fn(),
|
|
};
|
|
// Mock DI container before importing routes
|
|
jest.mock('tsyringe', () => ({
|
|
container: {
|
|
resolve: jest.fn((token) => {
|
|
if (token === 'AITaskService')
|
|
return mockAITaskService;
|
|
if (token === 'OpenAIService')
|
|
return mockOpenAIService;
|
|
if (token === 'LangChainService')
|
|
return mockLangChainService;
|
|
return null;
|
|
}),
|
|
register: jest.fn(),
|
|
},
|
|
injectable: jest.fn(() => (target) => target),
|
|
inject: jest.fn(() => (target, key, index) => { }),
|
|
}));
|
|
// Import routes after mocking
|
|
const aiRoutes = require('../../src/api/routes/ai.routes').default;
|
|
describe('AI API Integration Tests', () => {
|
|
let app;
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
// Create Express app
|
|
app = (0, express_1.default)();
|
|
app.use(express_1.default.json());
|
|
app.use('/api/ai', aiRoutes);
|
|
// Add error handler
|
|
app.use((err, req, res, next) => {
|
|
res.status(err.status || 500).json({
|
|
success: false,
|
|
error: err.message || 'Internal server error',
|
|
});
|
|
});
|
|
});
|
|
describe('POST /api/ai/create-task', () => {
|
|
it('should create a task from natural language successfully', async () => {
|
|
const mockTask = {
|
|
title: 'Implement user authentication',
|
|
description: 'Add JWT-based authentication to the application',
|
|
priority: 'high',
|
|
complexity: 'major',
|
|
estimatedHours: 16,
|
|
acceptanceCriteria: [
|
|
'Users can register',
|
|
'Users can login',
|
|
'JWT tokens are secure',
|
|
],
|
|
tags: ['auth', 'security'],
|
|
};
|
|
mockAITaskService.createTaskFromNaturalLanguage.mockResolvedValue(mockTask);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
prompt: 'Implement user authentication with JWT',
|
|
projectId: 'project-123',
|
|
context: {
|
|
projectDescription: 'E-commerce platform',
|
|
currentTasks: ['Database setup', 'API structure'],
|
|
},
|
|
})
|
|
.expect(201);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
data: mockTask,
|
|
message: 'Task created successfully from natural language',
|
|
});
|
|
expect(mockAITaskService.createTaskFromNaturalLanguage).toHaveBeenCalledWith({
|
|
prompt: 'Implement user authentication with JWT',
|
|
projectId: 'project-123',
|
|
context: {
|
|
projectDescription: 'E-commerce platform',
|
|
currentTasks: ['Database setup', 'API structure'],
|
|
},
|
|
});
|
|
});
|
|
it('should return 400 for invalid request', async () => {
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
// Missing required 'prompt' field
|
|
projectId: 'project-123',
|
|
})
|
|
.expect(400);
|
|
expect(response.body).toMatchObject({
|
|
success: false,
|
|
error: 'Validation error',
|
|
details: expect.any(Array),
|
|
});
|
|
expect(mockAITaskService.createTaskFromNaturalLanguage).not.toHaveBeenCalled();
|
|
});
|
|
it('should handle rate limiting', async () => {
|
|
mockAITaskService.createTaskFromNaturalLanguage.mockRejectedValue(new Error('Rate limit exceeded for task_creation'));
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
prompt: 'Create a task',
|
|
})
|
|
.expect(429);
|
|
expect(response.body).toEqual({
|
|
success: false,
|
|
error: 'Rate limit exceeded',
|
|
message: 'Too many requests. Please try again later.',
|
|
});
|
|
});
|
|
});
|
|
describe('PUT /api/ai/update-context/:taskId', () => {
|
|
it('should update task context with feedback', async () => {
|
|
mockAITaskService.updateTaskContext.mockResolvedValue(undefined);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.put('/api/ai/update-context/task-123')
|
|
.send({
|
|
feedback: 'The task needs more specific acceptance criteria',
|
|
})
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
message: 'Task context updated successfully',
|
|
});
|
|
expect(mockAITaskService.updateTaskContext).toHaveBeenCalledWith('task-123', 'The task needs more specific acceptance criteria');
|
|
});
|
|
it('should validate feedback is provided', async () => {
|
|
const response = await (0, supertest_1.default)(app)
|
|
.put('/api/ai/update-context/task-123')
|
|
.send({})
|
|
.expect(400);
|
|
expect(response.body).toMatchObject({
|
|
success: false,
|
|
error: 'Validation error',
|
|
});
|
|
});
|
|
});
|
|
describe('GET /api/ai/suggestions/:projectId', () => {
|
|
it('should get task suggestions for a project', async () => {
|
|
const mockSuggestions = [
|
|
{
|
|
id: 'suggestion-1',
|
|
title: 'Add error handling',
|
|
description: 'Implement comprehensive error handling',
|
|
rationale: 'Improves user experience and debugging',
|
|
priority: 'medium',
|
|
confidence: 0.85,
|
|
},
|
|
{
|
|
id: 'suggestion-2',
|
|
title: 'Setup logging',
|
|
description: 'Add structured logging system',
|
|
rationale: 'Essential for production monitoring',
|
|
priority: 'high',
|
|
confidence: 0.92,
|
|
},
|
|
];
|
|
mockAITaskService.getTaskSuggestions.mockResolvedValue(mockSuggestions);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.get('/api/ai/suggestions/project-123')
|
|
.query({
|
|
projectDescription: 'E-commerce platform',
|
|
completedTasks: 'Setup database,Create API structure',
|
|
currentTasks: 'Build UI components',
|
|
goals: 'Launch MVP,Scale to 1000 users',
|
|
})
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
data: mockSuggestions,
|
|
projectId: 'project-123',
|
|
});
|
|
expect(mockAITaskService.getTaskSuggestions).toHaveBeenCalledWith('project-123', {
|
|
projectDescription: 'E-commerce platform',
|
|
completedTasks: ['Setup database', 'Create API structure'],
|
|
currentTasks: ['Build UI components'],
|
|
goals: ['Launch MVP', 'Scale to 1000 users'],
|
|
});
|
|
});
|
|
it('should handle missing query parameters', async () => {
|
|
const response = await (0, supertest_1.default)(app)
|
|
.get('/api/ai/suggestions/project-123')
|
|
.expect(400);
|
|
expect(response.body).toMatchObject({
|
|
success: false,
|
|
error: 'Validation error',
|
|
});
|
|
});
|
|
});
|
|
describe('POST /api/ai/breakdown/:taskId', () => {
|
|
it('should break down a task into subtasks', async () => {
|
|
const mockBreakdown = {
|
|
title: 'Implement authentication',
|
|
description: 'Create secure authentication system',
|
|
subtasks: [
|
|
{
|
|
id: '1',
|
|
title: 'Setup database schema',
|
|
description: 'Create user tables and indexes',
|
|
estimatedHours: 2,
|
|
},
|
|
{
|
|
id: '2',
|
|
title: 'Implement JWT logic',
|
|
description: 'Create token generation and validation',
|
|
estimatedHours: 4,
|
|
},
|
|
{
|
|
id: '3',
|
|
title: 'Create API endpoints',
|
|
description: 'Build login, register, and refresh endpoints',
|
|
estimatedHours: 3,
|
|
},
|
|
],
|
|
complexity: 'major',
|
|
estimatedTotalHours: 9,
|
|
suggestedApproach: 'Use industry-standard JWT with refresh tokens',
|
|
};
|
|
mockAITaskService.breakdownTask.mockResolvedValue(mockBreakdown);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/breakdown/task-456')
|
|
.send({
|
|
taskDescription: 'Implement user authentication with JWT tokens',
|
|
context: 'Node.js Express application with PostgreSQL database',
|
|
})
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
data: mockBreakdown,
|
|
taskId: 'task-456',
|
|
});
|
|
expect(mockAITaskService.breakdownTask).toHaveBeenCalledWith('task-456', 'Implement user authentication with JWT tokens', 'Node.js Express application with PostgreSQL database');
|
|
});
|
|
});
|
|
describe('GET /api/ai/health', () => {
|
|
it('should return healthy status when all services are operational', async () => {
|
|
mockAITaskService.getHealthStatus.mockResolvedValue({
|
|
openai: true,
|
|
langchain: true,
|
|
redis: true,
|
|
rateLimiter: true,
|
|
});
|
|
const response = await (0, supertest_1.default)(app)
|
|
.get('/api/ai/health')
|
|
.expect(200);
|
|
expect(response.body).toMatchObject({
|
|
success: true,
|
|
healthy: true,
|
|
services: {
|
|
openai: true,
|
|
langchain: true,
|
|
redis: true,
|
|
rateLimiter: true,
|
|
},
|
|
timestamp: expect.any(String),
|
|
});
|
|
});
|
|
it('should return 503 when services are degraded', async () => {
|
|
mockAITaskService.getHealthStatus.mockResolvedValue({
|
|
openai: false,
|
|
langchain: true,
|
|
redis: true,
|
|
rateLimiter: true,
|
|
});
|
|
const response = await (0, supertest_1.default)(app)
|
|
.get('/api/ai/health')
|
|
.expect(503);
|
|
expect(response.body).toMatchObject({
|
|
success: true,
|
|
healthy: false,
|
|
services: expect.any(Object),
|
|
});
|
|
});
|
|
});
|
|
describe('GET /api/ai/usage', () => {
|
|
it('should return usage statistics', async () => {
|
|
const mockStats = {
|
|
openai: {
|
|
requestCount: 150,
|
|
totalTokensUsed: 75000,
|
|
estimatedCost: 1.5,
|
|
},
|
|
totalContextsSaved: 300,
|
|
cacheHitRate: 0.68,
|
|
};
|
|
mockAITaskService.getUsageStats.mockResolvedValue(mockStats);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.get('/api/ai/usage')
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
data: mockStats,
|
|
timestamp: expect.any(String),
|
|
});
|
|
});
|
|
});
|
|
describe('POST /api/ai/warm-cache', () => {
|
|
it('should initiate cache warming for a project', async () => {
|
|
mockAITaskService.warmCache.mockResolvedValue(undefined);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/warm-cache')
|
|
.send({
|
|
projectId: 'project-789',
|
|
})
|
|
.expect(202);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
message: 'Cache warming initiated',
|
|
projectId: 'project-789',
|
|
});
|
|
expect(mockAITaskService.warmCache).toHaveBeenCalledWith('project-789');
|
|
});
|
|
it('should require projectId', async () => {
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/warm-cache')
|
|
.send({})
|
|
.expect(400);
|
|
expect(response.body).toEqual({
|
|
success: false,
|
|
error: 'Project ID is required',
|
|
});
|
|
});
|
|
});
|
|
describe('POST /api/ai/test-connection', () => {
|
|
it('should test connections to all AI services', async () => {
|
|
mockOpenAIService.testConnection.mockResolvedValue(true);
|
|
mockLangChainService.testConnection.mockResolvedValue(true);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/test-connection')
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
connections: {
|
|
openai: true,
|
|
langchain: true,
|
|
},
|
|
message: 'All AI services are connected',
|
|
});
|
|
});
|
|
it('should report partial connectivity', async () => {
|
|
mockOpenAIService.testConnection.mockResolvedValue(true);
|
|
mockLangChainService.testConnection.mockResolvedValue(false);
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/test-connection')
|
|
.expect(200);
|
|
expect(response.body).toEqual({
|
|
success: true,
|
|
connections: {
|
|
openai: true,
|
|
langchain: false,
|
|
},
|
|
message: 'Some AI services are not available',
|
|
});
|
|
});
|
|
});
|
|
describe('Error handling', () => {
|
|
it('should handle internal server errors gracefully', async () => {
|
|
mockAITaskService.createTaskFromNaturalLanguage.mockRejectedValue(new Error('Database connection failed'));
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
prompt: 'Create a task',
|
|
})
|
|
.expect(500);
|
|
expect(response.body).toMatchObject({
|
|
success: false,
|
|
error: 'Database connection failed',
|
|
});
|
|
});
|
|
it('should validate request data types', async () => {
|
|
const response = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
prompt: 123, // Should be string
|
|
})
|
|
.expect(400);
|
|
expect(response.body).toMatchObject({
|
|
success: false,
|
|
error: 'Validation error',
|
|
});
|
|
});
|
|
});
|
|
describe('End-to-end workflow', () => {
|
|
it('should complete full task creation and breakdown flow', async () => {
|
|
// Step 1: Create task from natural language
|
|
const createdTask = {
|
|
id: 'task-new-123',
|
|
title: 'Build user dashboard',
|
|
description: 'Create interactive dashboard for users',
|
|
priority: 'high',
|
|
complexity: 'major',
|
|
};
|
|
mockAITaskService.createTaskFromNaturalLanguage.mockResolvedValue(createdTask);
|
|
const createResponse = await (0, supertest_1.default)(app)
|
|
.post('/api/ai/create-task')
|
|
.send({
|
|
prompt: 'Build a user dashboard with charts and metrics',
|
|
})
|
|
.expect(201);
|
|
expect(createResponse.body.success).toBe(true);
|
|
expect(createResponse.body.data).toMatchObject(createdTask);
|
|
// Step 2: Break down the created task
|
|
const breakdown = {
|
|
title: createdTask.title,
|
|
description: createdTask.description,
|
|
subtasks: [
|
|
{ id: '1', title: 'Design dashboard layout' },
|
|
{ id: '2', title: 'Implement chart components' },
|
|
{ id: '3', title: 'Add metrics calculations' },
|
|
],
|
|
complexity: 'major',
|
|
estimatedTotalHours: 24,
|
|
};
|
|
mockAITaskService.breakdownTask.mockResolvedValue(breakdown);
|
|
const breakdownResponse = await (0, supertest_1.default)(app)
|
|
.post(`/api/ai/breakdown/${createdTask.id}`)
|
|
.send({
|
|
taskDescription: createdTask.description,
|
|
context: 'React application with D3.js for charts',
|
|
})
|
|
.expect(200);
|
|
expect(breakdownResponse.body.success).toBe(true);
|
|
expect(breakdownResponse.body.data.subtasks).toHaveLength(3);
|
|
// Step 3: Update context with feedback
|
|
mockAITaskService.updateTaskContext.mockResolvedValue(undefined);
|
|
const updateResponse = await (0, supertest_1.default)(app)
|
|
.put(`/api/ai/update-context/${createdTask.id}`)
|
|
.send({
|
|
feedback: 'Focus on real-time data updates for the dashboard',
|
|
})
|
|
.expect(200);
|
|
expect(updateResponse.body.success).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=ai.integration.test.js.map
|