@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
426 lines (351 loc) • 13.8 kB
text/typescript
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import express, { Application } from 'express';
import request from 'supertest';
import { setupSecurityAPI } from '../../api/security.js';
import { SecurityManager } from '../../../utils/security-manager.js';
// Mock the SecurityManager
vi.mock('../../../utils/security-manager.js', () => ({
SecurityManager: {
getInstance: vi.fn()
}
}));
describe('Security API', () => {
let app: Application;
let mockSecurityManager: any;
beforeEach(() => {
app = express();
app.use(express.json());
// Create mock security manager with all required methods
mockSecurityManager = {
getSecurityStatus: vi.fn(),
getSecurityEvents: vi.fn(),
getPendingApprovals: vi.fn(),
processApproval: vi.fn(),
configureSecurityPolicy: vi.fn(),
validateToolAccess: vi.fn(),
generateSecurityMetrics: vi.fn(),
getSecurityAlerts: vi.fn(),
generateSecurityReport: vi.fn(),
auditToolExecution: vi.fn(),
setSecurityLevel: vi.fn(),
getBlockedTools: vi.fn(),
addToolRestriction: vi.fn(),
removeToolRestriction: vi.fn(),
checkCompliance: vi.fn()
};
// Mock getInstance to return our mock
(SecurityManager.getInstance as any).mockReturnValue(mockSecurityManager);
// Setup the API
setupSecurityAPI(app, mockSecurityManager);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('GET /api/security/status', () => {
it('should return security status', async () => {
const mockStatus = {
securityLevel: 'high',
activeApprovals: 5,
blockedActions: 2,
lastSecurityEvent: new Date().toISOString(),
complianceStatus: 'compliant'
};
mockSecurityManager.getSecurityStatus.mockResolvedValue(mockStatus);
const response = await request(app).get('/api/security/status');
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toEqual(mockStatus);
expect(mockSecurityManager.getSecurityStatus).toHaveBeenCalled();
});
it('should handle errors gracefully', async () => {
mockSecurityManager.getSecurityStatus.mockRejectedValue(new Error('Security error'));
const response = await request(app).get('/api/security/status');
expect(response.status).toBe(500);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Failed to fetch security status');
});
});
describe('GET /api/security/events', () => {
it('should return security events with filters', async () => {
const mockEvents = [
{
id: 'event-1',
type: 'access_denied',
severity: 'high',
timestamp: new Date().toISOString(),
details: { tool: 'dangerous_tool', user: 'user1' }
}
];
mockSecurityManager.getSecurityEvents.mockResolvedValue(mockEvents);
const response = await request(app)
.get('/api/security/events')
.query({
type: 'access_denied',
severity: 'high',
limit: '50',
startDate: '2024-01-01'
});
expect(response.status).toBe(200);
expect(response.body.data).toEqual(mockEvents);
expect(mockSecurityManager.getSecurityEvents).toHaveBeenCalledWith({
type: 'access_denied',
severity: 'high',
limit: 50,
startDate: '2024-01-01'
});
});
});
describe('GET /api/security/approvals', () => {
it('should return pending approvals', async () => {
const mockApprovals = [
{
id: 'approval-1',
toolName: 'system_modify',
requestedBy: 'user1',
status: 'pending',
createdAt: new Date().toISOString()
}
];
mockSecurityManager.getPendingApprovals.mockResolvedValue(mockApprovals);
const response = await request(app)
.get('/api/security/approvals')
.query({ status: 'pending', toolName: 'system_modify' });
expect(response.status).toBe(200);
expect(response.body.data.approvals).toEqual(mockApprovals);
expect(mockSecurityManager.getPendingApprovals).toHaveBeenCalledWith({
status: 'pending',
toolName: 'system_modify'
});
});
it('should return mock data when getPendingApprovals is not implemented', async () => {
mockSecurityManager.getPendingApprovals = undefined;
const response = await request(app).get('/api/security/approvals');
expect(response.status).toBe(200);
expect(response.body.data.approvals).toBeInstanceOf(Array);
expect(response.body.data.approvals[0]).toHaveProperty('id');
expect(response.body.data.approvals[0]).toHaveProperty('status', 'pending');
});
});
describe('POST /api/security/approvals/:id', () => {
it('should process approval decision', async () => {
const approvalData = {
decision: 'approved',
reason: 'Legitimate use case',
restrictions: ['read_only']
};
mockSecurityManager.processApproval.mockResolvedValue({
id: 'approval-1',
status: 'approved',
processedAt: new Date().toISOString()
});
const response = await request(app)
.post('/api/security/approvals/approval-1')
.send(approvalData);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(mockSecurityManager.processApproval).toHaveBeenCalledWith({
approvalId: 'approval-1',
decision: 'approve',
reason: 'Legitimate use case'
});
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/api/security/approvals/approval-1')
.send({ reason: 'Test' }); // missing decision
expect(response.status).toBe(400);
expect(response.body.error).toBe('Decision is required (approved/denied)');
});
});
describe('PUT /api/security/policy', () => {
it('should update security policy', async () => {
const policyUpdate = {
requireApproval: ['system_modify', 'delete_data'],
autoApprove: ['read_only'],
securityLevel: 'high'
};
mockSecurityManager.configureSecurityPolicy.mockResolvedValue({
updated: true,
policy: policyUpdate
});
const response = await request(app)
.put('/api/security/policy')
.send(policyUpdate);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(mockSecurityManager.configureSecurityPolicy).toHaveBeenCalledWith(policyUpdate);
});
it('should return mock data when configureSecurityPolicy is not implemented', async () => {
mockSecurityManager.configureSecurityPolicy = undefined;
const response = await request(app)
.put('/api/security/policy')
.send({ securityLevel: 'medium' });
expect(response.status).toBe(200);
expect(response.body.data).toHaveProperty('requireApproval');
expect(response.body.data).toHaveProperty('blockedTools');
});
});
describe('POST /api/security/validate', () => {
it('should validate tool access', async () => {
const validationRequest = {
tool: 'create_file',
context: { path: '/safe/path/file.txt' },
user: 'user1'
};
mockSecurityManager.validateToolAccess.mockResolvedValue({
allowed: true,
requiresApproval: false
});
const response = await request(app)
.post('/api/security/validate')
.send(validationRequest);
expect(response.status).toBe(200);
expect(response.body.data.allowed).toBe(true);
expect(mockSecurityManager.validateToolAccess).toHaveBeenCalledWith(
'create_file',
{ path: '/safe/path/file.txt' }
);
});
it('should require tool name', async () => {
const response = await request(app)
.post('/api/security/validate')
.send({ context: {} });
expect(response.status).toBe(400);
expect(response.body.error).toBe('Tool name is required');
});
});
describe('GET /api/security/metrics', () => {
it('should return security metrics', async () => {
const mockMetrics = {
totalEvents: 150,
blockedActions: 25,
approvalRate: 85,
averageApprovalTime: 300,
securityScore: 92
};
mockSecurityManager.generateSecurityMetrics.mockResolvedValue(mockMetrics);
const response = await request(app)
.get('/api/security/metrics')
.query({ timeRange: '7d' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual(mockMetrics);
expect(mockSecurityManager.generateSecurityMetrics).toHaveBeenCalled();
});
it('should return 500 when generateSecurityMetrics fails', async () => {
mockSecurityManager.generateSecurityMetrics.mockRejectedValue(new Error('Metrics error'));
const response = await request(app).get('/api/security/metrics');
expect(response.status).toBe(500);
expect(response.body.error).toBe('Failed to fetch security metrics');
});
});
describe('GET /api/security/alerts', () => {
it('should return security alerts', async () => {
const mockStatus = {
alerts: [
{
id: 'alert-1',
type: 'suspicious_activity',
severity: 'critical',
message: 'Multiple failed access attempts',
timestamp: new Date().toISOString()
}
]
};
mockSecurityManager.getSecurityStatus.mockResolvedValue(mockStatus);
const response = await request(app)
.get('/api/security/alerts')
.query({ active: 'true', severity: 'critical' });
expect(response.status).toBe(200);
expect(response.body.data).toEqual({ alerts: mockStatus.alerts });
expect(mockSecurityManager.getSecurityStatus).toHaveBeenCalled();
});
});
describe('GET /api/security/report', () => {
it('should return 404 for security report (not implemented)', async () => {
const response = await request(app)
.get('/api/security/report')
.query({
format: 'json',
period: 'monthly',
includeRecommendations: 'true'
});
expect(response.status).toBe(404);
expect(response.body.error).toBe('Security report generation not available');
});
it('should return 404 for CSV report (not implemented)', async () => {
const response = await request(app)
.get('/api/security/report')
.query({ format: 'csv' });
expect(response.status).toBe(404);
expect(response.body.error).toBe('Security report generation not available');
});
});
describe('POST /api/security/audit', () => {
it('should return 404 for audit (not implemented)', async () => {
const auditData = {
tool: 'delete_file',
action: 'execution',
result: 'blocked',
reason: 'Unauthorized path'
};
const response = await request(app)
.post('/api/security/audit')
.send(auditData);
expect(response.status).toBe(404);
expect(response.body.error).toBe('Audit functionality not available');
});
});
describe('PUT /api/security/level', () => {
it('should return 404 for security level update (not implemented)', async () => {
const response = await request(app)
.put('/api/security/level')
.send({ level: 'high' });
expect(response.status).toBe(404);
expect(response.body.error).toBe('Security level configuration not available');
});
it('should validate security level', async () => {
const response = await request(app)
.put('/api/security/level')
.send({ level: 'invalid' });
expect(response.status).toBe(400);
expect(response.body.error).toBe('Invalid security level. Must be one of: low, medium, high, critical');
});
});
describe('GET /api/security/blocked-tools', () => {
it('should return 404 for blocked tools (not implemented)', async () => {
const response = await request(app).get('/api/security/blocked-tools');
expect(response.status).toBe(404);
expect(response.body.error).toBe('Blocked tools list not available');
});
});
describe('POST /api/security/restrictions', () => {
it('should return 404 for adding restrictions (not implemented)', async () => {
const restriction = {
tool: 'file_write',
restriction: 'path_pattern',
value: '/restricted/*'
};
const response = await request(app)
.post('/api/security/restrictions')
.send(restriction);
expect(response.status).toBe(404);
expect(response.body.error).toBe('Tool restriction management not available');
});
});
describe('DELETE /api/security/restrictions/:id', () => {
it('should return 404 for removing restrictions (not implemented)', async () => {
const response = await request(app).delete('/api/security/restrictions/restriction-1');
expect(response.status).toBe(404);
expect(response.body.error).toBe('Tool restriction management not available');
});
});
describe('GET /api/security/compliance', () => {
it('should return 404 for compliance check (not implemented)', async () => {
const response = await request(app)
.get('/api/security/compliance')
.query({ standards: 'SOC2,ISO27001' });
expect(response.status).toBe(404);
expect(response.body.error).toBe('Compliance checking not available');
});
});
});