@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
339 lines (282 loc) • 11 kB
text/typescript
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import express, { Application } from 'express';
import request from 'supertest';
import { setupMetricsAPI } from '../../api/metrics.js';
import { PerformanceMonitor } from '../../../utils/performance-monitor.js';
// Mock the PerformanceMonitor
vi.mock('../../../utils/performance-monitor.js', () => ({
PerformanceMonitor: {
getInstance: vi.fn()
}
}));
describe('Metrics API', () => {
let app: Application;
let mockPerformanceMonitor: any;
beforeEach(() => {
app = express();
app.use(express.json());
// Create mock performance monitor
mockPerformanceMonitor = {
getSystemMetrics: vi.fn(),
getToolMetrics: vi.fn(),
getQualityMetrics: vi.fn(),
getPerformanceAlerts: vi.fn(),
generateAlerts: vi.fn(), // Add missing method
recordToolExecution: vi.fn(),
recordModulePerformance: vi.fn(),
recordAPICall: vi.fn(),
exportMetrics: vi.fn(),
recordQualityEvaluation: vi.fn(),
getModuleMetrics: vi.fn(),
analyzeBottlenecks: vi.fn(),
generatePerformanceReport: vi.fn(),
setAlertThreshold: vi.fn(),
clearMetrics: vi.fn()
};
// Mock getInstance to return our mock
(PerformanceMonitor.getInstance as any).mockReturnValue(mockPerformanceMonitor);
// Setup the API with exportEnabled parameter
setupMetricsAPI(app, mockPerformanceMonitor, true);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('GET /api/metrics/overview', () => {
it('should return system metrics successfully', async () => {
const mockSystemMetrics = {
cpu: { usage: 45.5, count: 8 },
memory: { used: 1024, total: 8192, percentage: 12.5 },
uptime: 3600,
timestamp: new Date().toISOString()
};
mockPerformanceMonitor.getSystemMetrics.mockResolvedValue(mockSystemMetrics);
const response = await request(app).get('/api/metrics/overview');
expect(response.status).toBe(200);
expect(response.body).toEqual({
success: true,
data: mockSystemMetrics,
timestamp: expect.any(String)
});
expect(mockPerformanceMonitor.getSystemMetrics).toHaveBeenCalled();
});
it('should handle errors gracefully', async () => {
mockPerformanceMonitor.getSystemMetrics.mockRejectedValue(new Error('System error'));
const response = await request(app).get('/api/metrics/overview');
expect(response.status).toBe(500);
expect(response.body).toEqual({
success: false,
error: 'Failed to fetch system metrics',
message: 'System error'
});
});
});
describe('GET /api/metrics/tools', () => {
it('should return tool metrics with filters', async () => {
const mockToolMetrics = [
{
toolName: 'create_epic',
executions: 10,
avgResponseTime: 150,
successRate: 90,
lastExecuted: new Date().toISOString()
}
];
mockPerformanceMonitor.getToolMetrics.mockResolvedValue(mockToolMetrics);
const response = await request(app)
.get('/api/metrics/tools')
.query({ toolName: 'create_epic', timeRange: '24h' });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toEqual({
toolName: 'create_epic',
metrics: mockToolMetrics,
timeRange: '24h'
});
expect(mockPerformanceMonitor.getToolMetrics).toHaveBeenCalledWith('create_epic');
});
});
describe('GET /api/metrics/quality', () => {
it('should return quality metrics', async () => {
const mockQualityMetrics = {
testCoverage: 80,
codeQuality: 85,
buildSuccess: 95,
deploymentFrequency: 5
};
mockPerformanceMonitor.getQualityMetrics.mockResolvedValue(mockQualityMetrics);
const response = await request(app).get('/api/metrics/quality');
expect(response.status).toBe(200);
expect(response.body.data).toEqual({
toolName: 'all',
timeRange: '7d',
qualityMetrics: mockQualityMetrics
});
});
});
describe('GET /api/metrics/alerts', () => {
it('should return performance alerts filtered by severity', async () => {
const mockAlerts = [
{
id: '1',
severity: 'high',
metric: 'cpu',
message: 'High CPU usage',
timestamp: new Date().toISOString()
}
];
mockPerformanceMonitor.generateAlerts.mockResolvedValue(mockAlerts);
const response = await request(app)
.get('/api/metrics/alerts')
.query({ severity: 'high', active: 'true' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual({
alerts: mockAlerts,
count: mockAlerts.length
});
expect(mockPerformanceMonitor.generateAlerts).toHaveBeenCalled();
});
});
describe('POST /api/metrics/record', () => {
it('should record tool execution metrics', async () => {
const executionData = {
tool: 'create_epic',
success: true,
duration: 150,
metadata: { epicId: '123' }
};
const response = await request(app)
.post('/api/metrics/record')
.send(executionData);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(mockPerformanceMonitor.recordToolExecution).toHaveBeenCalledWith(
'create_epic',
150,
true
);
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/api/metrics/record')
.send({ tool: 'test_tool' }); // missing success field
expect(response.status).toBe(400);
expect(response.body.error).toBe('Tool name and success status are required');
});
});
describe('GET /api/metrics/export', () => {
it('should export metrics for specified time range', async () => {
const mockExportData = {
timeRange: { start: '2024-01-01', end: '2024-01-31' },
metrics: { tool: [], system: [], quality: [] }
};
mockPerformanceMonitor.exportMetrics.mockResolvedValue(mockExportData);
const response = await request(app)
.get('/api/metrics/export')
.query({ format: 'json', startDate: '2024-01-01', endDate: '2024-01-31' });
expect(response.status).toBe(200);
expect(response.body.data).toHaveProperty('exportPath');
expect(response.body.data).toHaveProperty('format', 'json');
expect(mockPerformanceMonitor.exportMetrics).toHaveBeenCalledWith('json');
});
});
describe('GET /api/metrics/trends', () => {
it('should return performance trends (currently mock data)', async () => {
const response = await request(app)
.get('/api/metrics/trends')
.query({ metric: 'response_time', timeRange: '7d' });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('dataPoints');
expect(response.body.data).toHaveProperty('timeRange', '7d');
expect(response.body.data).toHaveProperty('metric', 'response_time');
});
});
describe('POST /api/metrics/quality/evaluate', () => {
it('should record quality evaluation', async () => {
const qualityData = {
toolName: 'test_tool',
quality: 8,
feedback: 'Good quality output'
};
const response = await request(app)
.post('/api/metrics/quality/evaluate')
.send(qualityData);
expect(response.status).toBe(200);
expect(mockPerformanceMonitor.recordQualityEvaluation).toHaveBeenCalledWith({
toolName: 'test_tool',
outputQuality: 8,
appropriatenessScore: 8,
completenessScore: 8,
accuracyScore: 8,
comments: 'Good quality output',
evaluationCriteria: ['user_feedback']
});
});
});
describe('GET /api/metrics/modules', () => {
it('should return module-specific metrics', async () => {
const mockModuleMetrics = {
'agile-management': { calls: 100, avgTime: 50 },
'kanban': { calls: 150, avgTime: 30 }
};
mockPerformanceMonitor.getModuleMetrics.mockResolvedValue(mockModuleMetrics);
const response = await request(app)
.get('/api/metrics/modules')
.query({ moduleName: 'agile-management' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual(mockModuleMetrics);
expect(mockPerformanceMonitor.getModuleMetrics).toHaveBeenCalledWith('agile-management');
});
});
describe('GET /api/metrics/bottlenecks', () => {
it('should analyze and return performance bottlenecks', async () => {
const mockBottlenecks = {
slowestTools: ['complex_analysis', 'generate_report'],
highMemoryUsage: ['data_processing'],
recommendations: ['Consider caching for complex_analysis']
};
mockPerformanceMonitor.analyzeBottlenecks.mockResolvedValue(mockBottlenecks);
const response = await request(app)
.get('/api/metrics/bottlenecks')
.query({ threshold: '500' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual(mockBottlenecks);
expect(mockPerformanceMonitor.analyzeBottlenecks).toHaveBeenCalledWith({ threshold: 500 });
});
});
describe('GET /api/metrics/report', () => {
it('should generate performance report', async () => {
const mockReport = {
summary: 'System performing well',
metrics: {},
recommendations: []
};
mockPerformanceMonitor.generatePerformanceReport.mockResolvedValue(mockReport);
const response = await request(app)
.get('/api/metrics/report')
.query({ format: 'json', includeRecommendations: 'true' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual(mockReport);
expect(mockPerformanceMonitor.generatePerformanceReport).toHaveBeenCalledWith({
format: 'json',
includeRecommendations: true
});
});
});
describe('DELETE /api/metrics/clear', () => {
it('should clear metrics with confirmation', async () => {
mockPerformanceMonitor.clearMetrics.mockResolvedValue({ cleared: true });
const response = await request(app)
.delete('/api/metrics/clear')
.query({ confirm: 'yes', before: '2024-01-01' });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(mockPerformanceMonitor.clearMetrics).toHaveBeenCalledWith({ before: '2024-01-01' });
});
it('should require confirmation', async () => {
const response = await request(app).delete('/api/metrics/clear');
expect(response.status).toBe(400);
expect(response.body.error).toBe('Confirmation required to clear metrics');
});
});
});