Skip to main content
Glama
danielbodnar

VyOS MCP Server

by danielbodnar
integration.test.ts12.6 kB
import { describe, expect, it, beforeEach, afterEach, spyOn } from 'bun:test'; import server from '../src/index'; import { VyOSClient } from '../src/vyos-client'; /** * @fileoverview End-to-end integration tests for the VyOS MCP server. * * Tests complete workflows including: * - VyOSClient integration with mocked VyOS API * - MCP server functionality * - Network configuration scenarios (mocked) * - Error recovery and resilience * - Performance and reliability * * @author VyOS MCP Server Tests * @version 1.0.0 * @since 2025-01-13 */ describe('VyOS MCP Server Integration Tests', () => { let fetchSpy: ReturnType<typeof spyOn>; let vyosClient: VyOSClient; beforeEach(() => { fetchSpy = spyOn(global, 'fetch'); // Create VyOSClient instance for testing vyosClient = new VyOSClient({ host: 'https://vyos.test.local', apiKey: 'test-api-key', timeout: 5000, verifySSL: false, }); }); afterEach(() => { fetchSpy.mockRestore(); }); describe('MCP Server Integration', () => { it('should provide working SSE endpoint', async () => { // Test SSE endpoint const sseResponse = await server.request('/sse', { method: 'GET', headers: { 'Accept': 'text/event-stream' }, }); expect(sseResponse.status).toBe(200); expect(sseResponse.headers.get('Content-Type')).toContain('text/event-stream'); // Test that messages endpoint requires proper setup const msgResponse = await server.request('/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping', }), }); // Should return 500 since transport isn't properly initialized in test environment expect(msgResponse.status).toBe(500); }); it('should handle CORS for cross-origin MCP clients', async () => { const response = await server.request('/sse', { method: 'GET', headers: { 'Origin': 'https://claude.ai', 'Accept': 'text/event-stream', }, }); expect(response.headers.get('Access-Control-Allow-Origin')).toBeTruthy(); }); }); describe('VyOSClient Integration Tests', () => { describe('Authentication and Connection', () => { it('should handle successful VyOS authentication', async () => { const mockResponse = { success: true, data: { authenticated: true }, }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200, headers: { 'Content-Type': 'application/json' }, })); const result = await vyosClient.getSystemInfo(); expect(result).toEqual(mockResponse); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/info', expect.objectContaining({ method: 'POST', }) ); }); it('should handle VyOS authentication failures', async () => { fetchSpy.mockResolvedValue(new Response('Unauthorized', { status: 401, statusText: 'Unauthorized', })); await expect(vyosClient.getSystemInfo()).rejects.toThrow('VyOS API request failed: HTTP 401: Unauthorized'); }); it('should handle network connectivity issues', async () => { fetchSpy.mockRejectedValue(new Error('Network error: Connection refused')); await expect(vyosClient.getSystemInfo()).rejects.toThrow('VyOS API request failed: Network error: Connection refused'); }); }); describe('Configuration Management Workflow', () => { it('should complete interface configuration workflow', async () => { // Mock successful responses for the workflow - create new Response for each call fetchSpy.mockImplementation(() => Promise.resolve(new Response(JSON.stringify({ success: true })))); // Step 1: Set interface configuration await vyosClient.setConfig(['interfaces', 'ethernet', 'eth0', 'address'], '192.168.1.1/24'); // Step 2: Set interface description await vyosClient.setConfig(['interfaces', 'ethernet', 'eth0', 'description'], 'LAN Interface'); // Step 3: Commit configuration await vyosClient.commit('Configure LAN interface'); // Step 4: Save configuration await vyosClient.save(); // Verify all API calls were made expect(fetchSpy).toHaveBeenCalledTimes(4); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/configure', expect.objectContaining({ method: 'POST' }) ); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.objectContaining({ method: 'POST' }) ); }); it('should handle configuration rollback scenario', async () => { // Mock successful responses - create new Response for each call fetchSpy.mockImplementation(() => Promise.resolve(new Response(JSON.stringify({ success: true })))); // Make risky configuration change await vyosClient.setConfig(['interfaces', 'ethernet', 'eth0', 'address'], '10.0.1.1/24'); // Commit with confirm timeout for safety await vyosClient.commit('Risky IP change - needs confirmation', 5); // In real scenario, if connectivity fails, commit would auto-rollback // Here we simulate manual rollback await vyosClient.setConfig(['interfaces', 'ethernet', 'eth0', 'address'], '192.168.1.1/24'); await vyosClient.commit('Rollback to working configuration'); expect(fetchSpy).toHaveBeenCalledTimes(4); }); }); describe('Network Diagnostics Integration', () => { it('should perform comprehensive network diagnostics', async () => { // Mock ping result const mockPingResult = { packets_transmitted: 3, packets_received: 3, packet_loss: 0, rtt_avg: 1.5, }; // Mock traceroute result const mockTraceResult = { hops: [ { hop: 1, address: '192.168.1.1', rtt: [1.2, 1.1, 1.3] }, { hop: 2, address: '8.8.8.8', rtt: [8.1, 8.3, 8.0] }, ], }; fetchSpy .mockResolvedValueOnce(new Response(JSON.stringify(mockPingResult))) .mockResolvedValueOnce(new Response(JSON.stringify(mockTraceResult))); // Test connectivity const pingResult = await vyosClient.ping('8.8.8.8', { count: 3 }); expect(pingResult).toEqual(mockPingResult); // Trace network path const traceResult = await vyosClient.traceroute('8.8.8.8', { maxHops: 10 }); expect(traceResult).toEqual(mockTraceResult); expect(fetchSpy).toHaveBeenCalledTimes(2); }); it('should handle network diagnostic failures gracefully', async () => { // Mock failed ping const mockFailedPing = { packets_transmitted: 3, packets_received: 0, packet_loss: 100, }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockFailedPing))); const result = await vyosClient.ping('192.168.99.1', { count: 3 }); expect(result).toEqual(mockFailedPing); expect(result).toHaveProperty('packet_loss', 100); }); }); describe('System Management Integration', () => { it('should retrieve system information and status', async () => { const mockSystemInfo = { version: '1.4-rolling-202501130317', hostname: 'vyos-router', uptime: '1 day, 2 hours, 30 minutes', load_average: [0.15, 0.10, 0.08], }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockSystemInfo))); const info = await vyosClient.getSystemInfo(); expect(info).toEqual(mockSystemInfo); expect(info).toHaveProperty('version'); expect(info).toHaveProperty('hostname'); }); it('should handle system management operations', async () => { fetchSpy.mockResolvedValue(new Response(JSON.stringify({ success: true }))); // Test reboot command (would be dangerous in real scenario) await vyosClient.reboot(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/reboot', expect.objectContaining({ method: 'POST' }) ); }); }); describe('Monitoring and Statistics Integration', () => { it('should retrieve interface statistics', async () => { const mockInterfaceStats = { eth0: { rx_bytes: 1000000, tx_bytes: 500000, rx_packets: 1000, tx_packets: 800, rx_errors: 0, tx_errors: 0, }, }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockInterfaceStats))); const stats = await vyosClient.getInterfaceStatistics(); expect(stats).toEqual(mockInterfaceStats); expect(stats.eth0).toHaveProperty('rx_bytes'); expect(stats.eth0.rx_bytes).toBeGreaterThan(0); }); it('should retrieve routing information', async () => { const mockRoutes = [ { destination: '0.0.0.0/0', gateway: '192.168.1.1', interface: 'eth0', metric: 1, }, { destination: '192.168.1.0/24', gateway: '0.0.0.0', interface: 'eth0', metric: 0, }, ]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockRoutes))); const routes = await vyosClient.getRoutes(); expect(routes).toEqual(mockRoutes); expect(Array.isArray(routes)).toBe(true); expect(routes.length).toBeGreaterThan(0); }); }); }); describe('Error Recovery Scenarios', () => { it('should handle VyOS API timeouts gracefully', async () => { fetchSpy.mockRejectedValue(new DOMException('The operation was aborted.', 'AbortError')); await expect(vyosClient.getSystemInfo()).rejects.toThrow('VyOS API request failed'); }); it('should handle invalid VyOS API responses', async () => { fetchSpy.mockResolvedValue(new Response('Internal Server Error', { status: 500, statusText: 'Internal Server Error', })); await expect(vyosClient.getSystemInfo()).rejects.toThrow('VyOS API request failed: HTTP 500: Internal Server Error'); }); it('should handle VyOS API errors in response', async () => { const errorResponse = { error: 'Invalid command syntax' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(errorResponse), { status: 200, headers: { 'Content-Type': 'application/json' }, })); await expect(vyosClient.getSystemInfo()).rejects.toThrow('VyOS API request failed: VyOS API Error: Invalid command syntax'); }); }); describe('Performance and Reliability', () => { it('should handle multiple concurrent VyOS API requests', async () => { const mockResponse = { success: true, data: {} }; fetchSpy.mockImplementation(() => Promise.resolve(new Response(JSON.stringify(mockResponse)))); // Create multiple concurrent requests const requests = [ vyosClient.getSystemInfo(), vyosClient.getInterfaces(), vyosClient.getRoutes(), vyosClient.showConfig(['system']), ]; const responses = await Promise.all(requests); // All requests should succeed for (const response of responses) { expect(response).toEqual(mockResponse); } // Should have made 4 concurrent fetch calls expect(fetchSpy).toHaveBeenCalledTimes(4); }); it('should handle large configuration responses', async () => { // Generate large mock configuration const largeConfig = { interfaces: {}, protocols: {}, system: {}, }; // Generate 50 interfaces for large response test for (let i = 0; i < 50; i++) { largeConfig.interfaces[`eth${i}`] = { address: `192.168.${Math.floor(i / 254)}.${i % 254 + 1}/24`, description: `Interface ${i}`, mtu: 1500, }; } fetchSpy.mockResolvedValue(new Response(JSON.stringify(largeConfig))); const result = await vyosClient.showConfig(); expect(result).toEqual(largeConfig); expect(Object.keys(result.interfaces)).toHaveLength(50); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/danielbodnar/vyos-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server