import { describe, expect, it } from 'bun:test';
import {
VyOSAuthSchema,
ConfigPathSchema,
ShowConfigRequestSchema,
SetConfigRequestSchema,
DeleteConfigRequestSchema,
ConfigExistsRequestSchema,
ReturnValuesRequestSchema,
CommitRequestSchema,
ShowOperationalRequestSchema,
ResetRequestSchema,
GenerateRequestSchema,
InterfaceSchema,
StaticRouteSchema,
PingRequestSchema,
TracerouteRequestSchema,
HealthCheckRequestSchema,
} from '../src/schemas/vyos-schemas';
/**
* @fileoverview Comprehensive test suite for VyOS Zod schemas.
*
* Tests schema validation for all VyOS API operations including:
* - Authentication schemas
* - Configuration management schemas
* - Operational command schemas
* - Network interface schemas
* - Diagnostic tool schemas
* - Input validation and error handling
*
* @author VyOS MCP Server Tests
* @version 1.0.0
* @since 2025-01-13
*/
describe('VyOS Schemas', () => {
describe('VyOSAuthSchema', () => {
it('should validate correct authentication object', () => {
const validAuth = {
host: 'https://vyos.example.com',
apiKey: 'valid-api-key-12345',
timeout: 30000,
verifySSL: false,
};
const result = VyOSAuthSchema.parse(validAuth);
expect(result).toEqual(validAuth);
});
it('should validate with minimal required fields', () => {
const minimalAuth = {
host: 'https://192.168.1.1',
apiKey: 'key',
};
const result = VyOSAuthSchema.parse(minimalAuth);
expect(result.host).toBe(minimalAuth.host);
expect(result.apiKey).toBe(minimalAuth.apiKey);
expect(result.timeout).toBe(30000); // default
expect(result.verifySSL).toBe(false); // default
});
it('should reject invalid URL format', () => {
const invalidAuth = {
host: 'not-a-valid-url',
apiKey: 'valid-key',
};
expect(() => VyOSAuthSchema.parse(invalidAuth)).toThrow();
});
it('should reject empty API key', () => {
const invalidAuth = {
host: 'https://vyos.test',
apiKey: '',
};
expect(() => VyOSAuthSchema.parse(invalidAuth)).toThrow();
});
it('should reject missing required fields', () => {
const incomplete = {
host: 'https://vyos.test',
// missing apiKey
};
expect(() => VyOSAuthSchema.parse(incomplete)).toThrow();
});
});
describe('ConfigPathSchema', () => {
it('should validate valid configuration paths', () => {
const validPaths = [
['interfaces', 'ethernet', 'eth0'],
['protocols', 'bgp', '65001', 'neighbor', '192.168.1.1'],
['system', 'hostname'],
[],
];
for (const path of validPaths) {
const result = ConfigPathSchema.parse(path);
expect(result).toEqual(path);
}
});
it('should reject non-array inputs', () => {
const invalidPaths = [
'not-an-array',
123,
{ path: 'object' },
null,
];
for (const path of invalidPaths) {
expect(() => ConfigPathSchema.parse(path)).toThrow();
}
});
it('should reject arrays with non-string elements', () => {
const invalidPaths = [
['interfaces', 123, 'eth0'],
['system', true],
[null, 'hostname'],
];
for (const path of invalidPaths) {
expect(() => ConfigPathSchema.parse(path)).toThrow();
}
});
});
describe('ShowConfigRequestSchema', () => {
it('should validate valid show config requests', () => {
const validRequests = [
{ path: ['interfaces'], format: 'json' },
{ path: ['system'], format: 'commands' },
{ format: 'json' }, // no path
{}, // all defaults
];
for (const request of validRequests) {
const result = ShowConfigRequestSchema.parse(request);
expect(result.format).toBeDefined();
}
});
it('should apply default format', () => {
const request = { path: ['interfaces'] };
const result = ShowConfigRequestSchema.parse(request);
expect(result.format).toBe('json');
});
it('should reject invalid format values', () => {
const invalidRequest = {
path: ['system'],
format: 'xml',
};
expect(() => ShowConfigRequestSchema.parse(invalidRequest)).toThrow();
});
});
describe('SetConfigRequestSchema', () => {
it('should validate set config requests with value', () => {
const validRequest = {
path: ['interfaces', 'ethernet', 'eth0', 'address'],
value: '192.168.1.1/24',
comment: 'Set LAN interface IP',
};
const result = SetConfigRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
it('should validate valueless set config requests', () => {
const validRequest = {
path: ['service', 'ssh'],
comment: 'Enable SSH service',
};
const result = SetConfigRequestSchema.parse(validRequest);
expect(result.path).toEqual(['service', 'ssh']);
expect(result.value).toBeUndefined();
});
it('should reject requests without path', () => {
const invalidRequest = {
value: '192.168.1.1/24',
};
expect(() => SetConfigRequestSchema.parse(invalidRequest)).toThrow();
});
});
describe('DeleteConfigRequestSchema', () => {
it('should validate delete config requests', () => {
const validRequest = {
path: ['interfaces', 'ethernet', 'eth1'],
};
const result = DeleteConfigRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
it('should reject requests without path', () => {
expect(() => DeleteConfigRequestSchema.parse({})).toThrow();
});
});
describe('ConfigExistsRequestSchema', () => {
it('should validate config exists requests', () => {
const validRequest = {
path: ['protocols', 'bgp'],
};
const result = ConfigExistsRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
});
describe('ReturnValuesRequestSchema', () => {
it('should validate return values requests', () => {
const validRequest = {
path: ['interfaces', 'ethernet'],
};
const result = ReturnValuesRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
});
describe('CommitRequestSchema', () => {
it('should validate commit requests with all options', () => {
const validRequest = {
comment: 'Network configuration update',
confirmTimeout: 10,
};
const result = CommitRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
it('should validate minimal commit requests', () => {
const result = CommitRequestSchema.parse({});
expect(result.comment).toBeUndefined();
expect(result.confirmTimeout).toBeUndefined();
});
it('should validate commit with only comment', () => {
const validRequest = { comment: 'Test commit' };
const result = CommitRequestSchema.parse(validRequest);
expect(result.comment).toBe('Test commit');
});
it('should validate commit with only timeout', () => {
const validRequest = { confirmTimeout: 15 };
const result = CommitRequestSchema.parse(validRequest);
expect(result.confirmTimeout).toBe(15);
});
});
describe('ShowOperationalRequestSchema', () => {
it('should validate operational show requests', () => {
const validRequests = [
{ path: ['interfaces', 'ethernet', 'statistics'], format: 'json' },
{ path: ['protocols', 'bgp', 'summary'], format: 'raw' },
{ path: ['system', 'uptime'] }, // default format
];
for (const request of validRequests) {
const result = ShowOperationalRequestSchema.parse(request);
expect(result.path).toBeDefined();
expect(result.format).toBeDefined();
}
});
it('should apply default format', () => {
const request = { path: ['system', 'uptime'] };
const result = ShowOperationalRequestSchema.parse(request);
expect(result.format).toBe('json');
});
it('should reject invalid format', () => {
const invalidRequest = {
path: ['system'],
format: 'xml',
};
expect(() => ShowOperationalRequestSchema.parse(invalidRequest)).toThrow();
});
});
describe('ResetRequestSchema', () => {
it('should validate reset requests', () => {
const validRequest = {
path: ['interfaces', 'ethernet', 'eth0', 'counters'],
};
const result = ResetRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
});
describe('GenerateRequestSchema', () => {
it('should validate generate requests', () => {
const validRequest = {
path: ['wireguard', 'keypair'],
};
const result = GenerateRequestSchema.parse(validRequest);
expect(result).toEqual(validRequest);
});
});
describe('InterfaceSchema', () => {
it('should validate complete interface configuration', () => {
const validInterface = {
name: 'eth0',
address: '192.168.1.1/24',
description: 'LAN Interface',
enabled: true,
mtu: 1500,
vlan: 100,
};
const result = InterfaceSchema.parse(validInterface);
expect(result).toEqual(validInterface);
});
it('should validate minimal interface configuration', () => {
const minimalInterface = {
name: 'eth1',
};
const result = InterfaceSchema.parse(minimalInterface);
expect(result.name).toBe('eth1');
expect(result.enabled).toBe(true); // default
});
it('should validate VLAN interface', () => {
const vlanInterface = {
name: 'eth0.100',
address: '10.100.1.1/24',
vlan: 100,
};
const result = InterfaceSchema.parse(vlanInterface);
expect(result.vlan).toBe(100);
});
it('should reject invalid interface names', () => {
const invalidInterface = {
name: '',
address: '192.168.1.1/24',
};
expect(() => InterfaceSchema.parse(invalidInterface)).toThrow();
});
it('should reject missing name', () => {
const invalidInterface = {
address: '192.168.1.1/24',
};
expect(() => InterfaceSchema.parse(invalidInterface)).toThrow();
});
});
describe('StaticRouteSchema', () => {
it('should validate complete static route', () => {
const validRoute = {
destination: '10.0.0.0/24',
nextHop: '192.168.1.1',
distance: 110,
tag: 500,
};
const result = StaticRouteSchema.parse(validRoute);
expect(result).toEqual(validRoute);
});
it('should validate minimal static route', () => {
const minimalRoute = {
destination: '0.0.0.0/0',
nextHop: '192.168.1.1',
};
const result = StaticRouteSchema.parse(minimalRoute);
expect(result.destination).toBe('0.0.0.0/0');
expect(result.nextHop).toBe('192.168.1.1');
});
it('should reject routes without destination', () => {
const invalidRoute = {
nextHop: '192.168.1.1',
};
expect(() => StaticRouteSchema.parse(invalidRoute)).toThrow();
});
it('should reject routes without next hop', () => {
const invalidRoute = {
destination: '10.0.0.0/24',
};
expect(() => StaticRouteSchema.parse(invalidRoute)).toThrow();
});
});
describe('PingRequestSchema', () => {
it('should validate complete ping request', () => {
const validPing = {
host: '8.8.8.8',
count: 5,
interval: 2,
timeout: 10,
size: 64,
source: '192.168.1.100',
};
const result = PingRequestSchema.parse(validPing);
expect(result).toEqual(validPing);
});
it('should validate minimal ping request with defaults', () => {
const minimalPing = {
host: '1.1.1.1',
};
const result = PingRequestSchema.parse(minimalPing);
expect(result.host).toBe('1.1.1.1');
expect(result.count).toBe(3);
expect(result.interval).toBe(1);
expect(result.timeout).toBe(5);
expect(result.size).toBe(56);
});
it('should reject ping without host', () => {
const invalidPing = {
count: 5,
};
expect(() => PingRequestSchema.parse(invalidPing)).toThrow();
});
});
describe('TracerouteRequestSchema', () => {
it('should validate complete traceroute request', () => {
const validTraceroute = {
host: '8.8.8.8',
maxHops: 20,
timeout: 3,
source: '192.168.1.100',
};
const result = TracerouteRequestSchema.parse(validTraceroute);
expect(result).toEqual(validTraceroute);
});
it('should validate minimal traceroute request with defaults', () => {
const minimalTraceroute = {
host: 'google.com',
};
const result = TracerouteRequestSchema.parse(minimalTraceroute);
expect(result.host).toBe('google.com');
expect(result.maxHops).toBe(30);
expect(result.timeout).toBe(5);
});
it('should reject traceroute without host', () => {
const invalidTraceroute = {
maxHops: 15,
};
expect(() => TracerouteRequestSchema.parse(invalidTraceroute)).toThrow();
});
});
describe('HealthCheckRequestSchema', () => {
it('should validate complete health check request', () => {
const validHealthCheck = {
includeInterfaces: true,
includeRouting: true,
includeServices: false,
includeResources: true,
};
const result = HealthCheckRequestSchema.parse(validHealthCheck);
expect(result).toEqual(validHealthCheck);
});
it('should validate minimal health check with defaults', () => {
const minimalHealthCheck = {};
const result = HealthCheckRequestSchema.parse(minimalHealthCheck);
expect(result.includeInterfaces).toBe(true);
expect(result.includeRouting).toBe(true);
expect(result.includeServices).toBe(true);
expect(result.includeResources).toBe(true);
});
it('should validate partial health check options', () => {
const partialHealthCheck = {
includeInterfaces: false,
includeResources: false,
};
const result = HealthCheckRequestSchema.parse(partialHealthCheck);
expect(result.includeInterfaces).toBe(false);
expect(result.includeRouting).toBe(true); // default
expect(result.includeServices).toBe(true); // default
expect(result.includeResources).toBe(false);
});
});
describe('Schema edge cases and error handling', () => {
it('should handle null and undefined values appropriately', () => {
expect(() => VyOSAuthSchema.parse(null)).toThrow();
expect(() => VyOSAuthSchema.parse(undefined)).toThrow();
expect(() => ConfigPathSchema.parse(null)).toThrow();
expect(() => InterfaceSchema.parse(undefined)).toThrow();
});
it('should handle empty objects', () => {
// These should work with defaults
expect(() => ShowConfigRequestSchema.parse({})).not.toThrow();
expect(() => CommitRequestSchema.parse({})).not.toThrow();
expect(() => HealthCheckRequestSchema.parse({})).not.toThrow();
// These require specific fields
expect(() => VyOSAuthSchema.parse({})).toThrow();
expect(() => SetConfigRequestSchema.parse({})).toThrow();
expect(() => InterfaceSchema.parse({})).toThrow();
});
it('should provide meaningful error messages for validation failures', () => {
try {
VyOSAuthSchema.parse({ host: 'invalid-url' });
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect(error.message).toContain('Invalid url');
}
try {
PingRequestSchema.parse({ count: 'not-a-number' });
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect(error.message).toContain('host');
}
});
it('should handle extra properties gracefully', () => {
const authWithExtra = {
host: 'https://vyos.test',
apiKey: 'test-key',
extraProperty: 'should-be-ignored',
};
const result = VyOSAuthSchema.parse(authWithExtra);
expect(result.host).toBe(authWithExtra.host);
expect(result.apiKey).toBe(authWithExtra.apiKey);
expect(result).not.toHaveProperty('extraProperty');
});
});
describe('Schema type inference', () => {
it('should infer correct types from schemas', () => {
const auth = VyOSAuthSchema.parse({
host: 'https://test.vyos',
apiKey: 'key',
});
// TypeScript type checking - these should not cause compilation errors
const host: string = auth.host;
const apiKey: string = auth.apiKey;
const timeout: number = auth.timeout;
const verifySSL: boolean = auth.verifySSL;
expect(host).toBe('https://test.vyos');
expect(apiKey).toBe('key');
expect(timeout).toBe(30000);
expect(verifySSL).toBe(false);
});
it('should handle optional fields correctly', () => {
const iface = InterfaceSchema.parse({ name: 'eth0' });
// These should be optional
const address: string | undefined = iface.address;
const description: string | undefined = iface.description;
const mtu: number | undefined = iface.mtu;
const vlan: number | undefined = iface.vlan;
// This should have a default
const enabled: boolean = iface.enabled;
expect(address).toBeUndefined();
expect(description).toBeUndefined();
expect(mtu).toBeUndefined();
expect(vlan).toBeUndefined();
expect(enabled).toBe(true);
});
});
});