Skip to main content
Glama
danielbodnar

VyOS MCP Server

by danielbodnar
vyos-client.test.ts21.2 kB
import { describe, expect, it, beforeEach, afterEach, spyOn } from 'bun:test'; import { VyOSClient } from '../src/vyos-client'; import type { VyOSAuth } from '../src/schemas/vyos-schemas'; /** * @fileoverview Comprehensive test suite for VyOSClient using Bun test runner. * * Tests all VyOS API client functionality including: * - Authentication and connection * - Configuration management * - Operational commands * - System management * - Network diagnostics * - Error handling * * @author VyOS MCP Server Tests * @version 1.0.0 * @since 2025-01-13 */ describe('VyOSClient', () => { let client: VyOSClient; let mockAuth: VyOSAuth; let fetchSpy: ReturnType<typeof spyOn>; beforeEach(() => { mockAuth = { host: 'https://vyos.test.local', apiKey: 'test-api-key', timeout: 5000, verifySSL: false, }; client = new VyOSClient(mockAuth); // Mock global fetch fetchSpy = spyOn(global, 'fetch'); }); afterEach(() => { fetchSpy.mockRestore(); }); describe('constructor', () => { it('should create VyOSClient instance with correct properties', () => { expect(client).toBeInstanceOf(VyOSClient); }); it('should use default values for optional parameters', () => { const minimalAuth = { host: 'https://vyos.minimal.test', apiKey: 'minimal-key', }; const minimalClient = new VyOSClient(minimalAuth); expect(minimalClient).toBeInstanceOf(VyOSClient); }); }); describe('makeRequest', () => { it('should make successful API request', async () => { const mockResponse = { success: true, data: { version: '1.4' } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200, headers: { 'Content-Type': 'application/json' }, })); const result = await client.getSystemInfo(); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/info', expect.objectContaining({ method: 'POST', body: expect.any(FormData), }) ); expect(result).toEqual(mockResponse); }); it('should handle HTTP errors', async () => { fetchSpy.mockResolvedValue(new Response('Not Found', { status: 404, statusText: 'Not Found', })); await expect(client.getSystemInfo()).rejects.toThrow('VyOS API request failed: HTTP 404: Not Found'); }); it('should handle VyOS API errors', async () => { const errorResponse = { error: 'Invalid API key' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(errorResponse), { status: 200, headers: { 'Content-Type': 'application/json' }, })); await expect(client.getSystemInfo()).rejects.toThrow('VyOS API request failed: VyOS API Error: Invalid API key'); }); it('should handle network errors', async () => { fetchSpy.mockRejectedValue(new Error('Network error')); await expect(client.getSystemInfo()).rejects.toThrow('VyOS API request failed: Network error'); }); it('should handle timeout', async () => { fetchSpy.mockRejectedValue(new DOMException('The operation was aborted.', 'AbortError')); await expect(client.getSystemInfo()).rejects.toThrow('VyOS API request failed'); }); }); describe('system information', () => { it('should get system info', async () => { const mockSystemInfo = { version: '1.4-rolling-202501130317', hostname: 'vyos-router', uptime: '1 day, 2 hours', }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockSystemInfo))); const result = await client.getSystemInfo(); expect(result).toEqual(mockSystemInfo); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/info', expect.objectContaining({ method: 'POST', }) ); }); it('should get version', async () => { const mockVersion = { version: '1.4-rolling' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockVersion))); await client.getVersion(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get uptime', async () => { const mockUptime = { uptime: '1 day, 2:30:45' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockUptime))); await client.getUptime(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); }); describe('configuration management', () => { it('should show configuration', async () => { const mockConfig = { interfaces: { ethernet: { eth0: { address: '192.168.1.1/24' } } } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockConfig))); const result = await client.showConfig(['interfaces', 'ethernet', 'eth0']); expect(result).toEqual(mockConfig); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/retrieve', expect.objectContaining({ method: 'POST', }) ); }); it('should show full configuration when no path provided', async () => { const mockConfig = { system: {}, interfaces: {} }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockConfig))); await client.showConfig(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/retrieve', expect.any(Object) ); }); it('should set configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.setConfig(['interfaces', 'ethernet', 'eth0', 'address'], '192.168.1.1/24'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/configure', expect.objectContaining({ method: 'POST', }) ); }); it('should set valueless configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.setConfig(['service', 'ssh']); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/configure', expect.any(Object) ); }); it('should delete configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.deleteConfig(['interfaces', 'ethernet', 'eth1']); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/configure', expect.objectContaining({ method: 'POST', }) ); }); it('should check if configuration exists', async () => { const mockResponse = { exists: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); const exists = await client.configExists(['protocols', 'bgp']); expect(exists).toBe(true); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/retrieve', expect.any(Object) ); }); it('should return configuration values', async () => { const mockResponse = { values: ['eth0', 'eth1', 'eth2'] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); const values = await client.returnValues(['interfaces', 'ethernet']); expect(values).toEqual(['eth0', 'eth1', 'eth2']); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/retrieve', expect.any(Object) ); }); it('should handle empty values response', async () => { const mockResponse = {}; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); const values = await client.returnValues(['nonexistent']); expect(values).toEqual([]); }); it('should commit configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.commit('Test commit', 5); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.any(Object) ); }); it('should commit without comment and timeout', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.commit(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.any(Object) ); }); it('should save configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.save(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.objectContaining({ method: 'POST', }) ); }); it('should load configuration', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.load('/config/backup.boot'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.any(Object) ); }); it('should load default configuration file', async () => { const mockResponse = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResponse))); await client.load(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/config-file', expect.any(Object) ); }); }); describe('operational commands', () => { it('should execute show command', async () => { const mockResult = { interfaces: { eth0: { statistics: { rx_bytes: 1000 } } } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); const result = await client.show(['interfaces', 'ethernet', 'statistics']); expect(result).toEqual(mockResult); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should execute reset command', async () => { const mockResult = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.reset(['interfaces', 'ethernet', 'eth0', 'counters']); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/reset', expect.any(Object) ); }); it('should execute generate command', async () => { const mockResult = { privateKey: 'wg-private-key', publicKey: 'wg-public-key' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); const result = await client.generate(['wireguard', 'keypair']); expect(result).toEqual(mockResult); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/generate', expect.any(Object) ); }); }); describe('system management', () => { it('should reboot system', async () => { const mockResult = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.reboot(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/reboot', expect.objectContaining({ method: 'POST', }) ); }); it('should poweroff system', async () => { const mockResult = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.poweroff(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/poweroff', expect.any(Object) ); }); it('should add system image', async () => { const mockResult = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.addImage('vyos-1.4-rolling'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/image', expect.any(Object) ); }); it('should delete system image', async () => { const mockResult = { success: true }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.deleteImage('old-vyos-image'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/image', expect.any(Object) ); }); }); describe('network diagnostics', () => { it('should execute ping with all options', async () => { const mockResult = { packets_transmitted: 3, packets_received: 3, packet_loss: 0 }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); const options = { count: 3, interval: 1, timeout: 5, size: 64, source: '192.168.1.1', }; await client.ping('8.8.8.8', options); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should execute ping with minimal options', async () => { const mockResult = { packets_transmitted: 1, packets_received: 1 }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.ping('8.8.8.8'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should execute traceroute with options', async () => { const mockResult = { hops: [{ hop: 1, address: '192.168.1.1' }] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); const options = { maxHops: 15, timeout: 3, source: '192.168.1.100', }; await client.traceroute('8.8.8.8', options); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should execute traceroute with minimal options', async () => { const mockResult = { hops: [] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResult))); await client.traceroute('1.1.1.1'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); }); describe('monitoring and statistics', () => { it('should get interfaces', async () => { const mockInterfaces = { ethernet: { eth0: { state: 'up' } } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockInterfaces))); await client.getInterfaces(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get interface statistics for specific interface', async () => { const mockStats = { rx_bytes: 1000, tx_bytes: 500 }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockStats))); await client.getInterfaceStatistics('eth0'); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get all interface statistics', async () => { const mockStats = { eth0: { rx_bytes: 1000 }, eth1: { rx_bytes: 2000 } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockStats))); await client.getInterfaceStatistics(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get routes', async () => { const mockRoutes = [{ destination: '0.0.0.0/0', gateway: '192.168.1.1' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockRoutes))); await client.getRoutes(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get BGP summary', async () => { const mockBGP = { router_id: '1.1.1.1', as_number: 65001 }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockBGP))); await client.getBGPSummary(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get BGP neighbors', async () => { const mockNeighbors = [{ address: '192.168.1.2', state: 'Established' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockNeighbors))); await client.getBGPNeighbors(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get OSPF neighbors', async () => { const mockOSPF = [{ neighbor: '192.168.1.3', state: 'Full' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockOSPF))); await client.getOSPFNeighbors(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get VPN status', async () => { const mockVPN = [{ tunnel: 'peer-192.168.2.1', state: 'up' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockVPN))); await client.getVPNStatus(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get firewall rules', async () => { const mockFirewall = { name: { LAN_LOCAL: { rule: { 10: { action: 'accept' } } } } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockFirewall))); await client.getFirewallRules(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get NAT rules', async () => { const mockNAT = { source: { rule: { 100: { outbound_interface: 'eth0' } } } }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockNAT))); await client.getNATRules(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get DHCP leases', async () => { const mockLeases = [{ ip: '192.168.1.100', mac: '00:11:22:33:44:55' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockLeases))); await client.getDHCPLeases(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get connection tracking', async () => { const mockConntrack = [{ protocol: 'tcp', src: '192.168.1.100', dst: '8.8.8.8' }]; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockConntrack))); await client.getConntrack(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get log with default lines', async () => { const mockLog = { lines: ['Jan 13 12:00:00 vyos sshd: Connection established'] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockLog))); await client.getLog(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get log with custom line count', async () => { const mockLog = { lines: [] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockLog))); await client.getLog(50); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get system resources', async () => { const mockResources = { cpu_usage: 15, load_average: [0.5, 0.4, 0.3] }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockResources))); await client.getSystemResources(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get system memory', async () => { const mockMemory = { total: 2048, used: 512, free: 1536 }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockMemory))); await client.getSystemMemory(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); it('should get system storage', async () => { const mockStorage = { filesystem: '/', size: '10G', used: '2.5G', available: '7.5G' }; fetchSpy.mockResolvedValue(new Response(JSON.stringify(mockStorage))); await client.getSystemStorage(); expect(fetchSpy).toHaveBeenCalledWith( 'https://vyos.test.local/show', expect.any(Object) ); }); }); });

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