import { describe, expect, it, beforeEach, afterEach, spyOn } from 'bun:test';
import server, { __testUtils__ } from '../src/index';
/**
* @fileoverview Integration tests for the VyOS MCP server.
*
* Tests the actual MCP server functionality including:
* - SSE endpoint for MCP transport
* - Messages endpoint for MCP communication
* - Server error handling
* - CORS functionality
*
* @author VyOS MCP Server Tests
* @version 1.0.0
* @since 2025-01-13
*/
describe('VyOS MCP Server', () => {
let fetchSpy: ReturnType<typeof spyOn>;
beforeEach(() => {
// Mock global fetch for VyOSClient requests
fetchSpy = spyOn(global, 'fetch');
// Reset any global transport state that might exist from other tests
// This is needed because the transport is stored in a module-level variable
__testUtils__.resetTransport();
});
afterEach(() => {
fetchSpy.mockRestore();
});
describe('Server Setup', () => {
it('should be a Hono server instance', () => {
expect(server).toBeDefined();
expect(typeof server.fetch).toBe('function');
});
});
describe('MCP Endpoints', () => {
describe('GET /sse', () => {
it('should provide SSE endpoint for MCP transport', async () => {
const response = await server.request('/sse', {
method: 'GET',
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
},
});
expect(response.status).toBe(200);
expect(response.headers.get('Content-Type')).toContain('text/event-stream');
});
it('should set proper SSE headers', async () => {
const response = await server.request('/sse', {
method: 'GET',
headers: {
'Accept': 'text/event-stream',
},
});
expect(response.headers.get('Content-Type')).toContain('text/event-stream');
expect(response.headers.get('Cache-Control')).toBe('no-cache');
expect(response.headers.get('Connection')).toBe('keep-alive');
});
});
describe('POST /messages', () => {
it('should return error when no transport initialized', async () => {
const response = await server.request('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'ping',
}),
});
// Should return 500 error since transport is not initialized
expect(response.status).toBe(500);
});
it('should handle non-existent endpoint with GET', async () => {
const response = await server.request('/messages', {
method: 'GET',
});
expect(response.status).toBe(404); // Route not found, not method not allowed
});
});
});
describe('Error Handling', () => {
it('should handle requests to non-existent endpoints', async () => {
const response = await server.request('/non-existent-endpoint', {
method: 'POST',
});
expect(response.status).toBe(404);
});
it('should handle malformed JSON in messages endpoint', async () => {
const response = await server.request('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: 'invalid-json{',
});
// Should return 500 error since transport is not initialized
expect(response.status).toBe(500);
});
});
describe('CORS Support', () => {
it('should handle CORS preflight requests', async () => {
const response = await server.request('/sse', {
method: 'OPTIONS',
headers: {
'Origin': 'https://claude.ai',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'Content-Type',
},
});
expect(response.headers.get('Access-Control-Allow-Origin')).toBeDefined();
});
it('should include CORS headers in responses', 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')).toBeDefined();
});
});
describe('Transport Initialization', () => {
it('should not allow messages before SSE connection', async () => {
// Try to send message without establishing SSE connection first
const response = await server.request('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ test: 'message' }),
});
// Should return 500 error since transport is not initialized
expect(response.status).toBe(500);
});
});
describe('Server Functionality', () => {
it('should handle SSE endpoint correctly', async () => {
// SSE connection should work
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');
});
it('should reject requests to messages without transport', async () => {
// Messages endpoint should require proper MCP transport setup
const response = await server.request('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/list',
}),
});
// Should fail without proper transport initialization
expect(response.status).toBe(500);
});
});
});