UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

426 lines (351 loc) 13.8 kB
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'); }); }); });