osc-mcp-server
Version:
Model Context Protocol server for OSC (Open Sound Control) endpoint management
341 lines • 13.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const server_1 = require("../server");
const manager_1 = require("../osc/manager");
jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
Server: jest.fn(),
}));
jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
StdioServerTransport: jest.fn(),
}));
jest.mock('@modelcontextprotocol/sdk/types.js', () => ({
CallToolRequestSchema: { method: 'tools/call' },
ListToolsRequestSchema: { method: 'tools/list' },
ErrorCode: {
InvalidParams: 'InvalidParams',
InternalError: 'InternalError',
MethodNotFound: 'MethodNotFound',
},
McpError: class McpError extends Error {
code;
details;
constructor(code, message, details) {
super(message);
this.code = code;
this.details = details;
}
},
}));
describe('Error Handling Integration Tests', () => {
let server;
let mockMCPServer;
let mockTransport;
let callToolHandler;
beforeEach(() => {
jest.clearAllMocks();
mockMCPServer = {
connect: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
setRequestHandler: jest.fn(),
};
mockTransport = {};
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
Server.mockImplementation(() => mockMCPServer);
StdioServerTransport.mockImplementation(() => mockTransport);
server = new server_1.OSCMCPServer();
const calls = mockMCPServer.setRequestHandler.mock.calls;
const callToolCall = calls.find((call) => call[0].method === 'tools/call');
callToolHandler = callToolCall[1];
});
describe('Parameter Validation Integration', () => {
it('should handle invalid port validation end-to-end', async () => {
const request = {
params: {
name: 'create_osc_endpoint',
arguments: { port: 100 },
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InvalidParams');
expect(error.message).toContain("Parameter 'port' value 100 is out of range");
expect(error.details).toBeDefined();
}
});
it('should handle missing required parameters', async () => {
const request = {
params: {
name: 'create_osc_endpoint',
arguments: {},
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InvalidParams');
expect(error.message).toContain("Missing required parameter: 'port'");
}
});
it('should handle invalid address filters', async () => {
const request = {
params: {
name: 'create_osc_endpoint',
arguments: {
port: 8000,
addressFilters: ['invalid-pattern'],
},
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InvalidParams');
expect(error.message).toContain('Invalid OSC address pattern');
}
});
it('should handle invalid endpoint ID for stop operation', async () => {
const request = {
params: {
name: 'stop_osc_endpoint',
arguments: { endpointId: 123 },
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InvalidParams');
expect(error.message).toContain("Invalid parameter 'endpointId': expected string");
}
});
});
describe('Network Error Integration', () => {
it('should handle port in use errors through the full stack', async () => {
const firstRequest = {
params: {
name: 'create_osc_endpoint',
arguments: { port: 8000 },
},
};
const firstResult = await callToolHandler(firstRequest);
const firstResponse = JSON.parse(firstResult.content[0].text);
expect(firstResponse.status).toBe('active');
const secondRequest = {
params: {
name: 'create_osc_endpoint',
arguments: { port: 8000 },
},
};
try {
await callToolHandler(secondRequest);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InternalError');
expect(error.message).toContain('Port 8000 is already in use');
}
});
});
describe('Endpoint Management Error Integration', () => {
it('should handle stopping non-existent endpoints', async () => {
const request = {
params: {
name: 'stop_osc_endpoint',
arguments: { endpointId: 'non-existent' },
},
};
const result = await callToolHandler(request);
const response = JSON.parse(result.content[0].text);
expect(response.message).toContain("Endpoint 'non-existent' not found");
});
it('should handle querying messages from non-existent endpoints', async () => {
const request = {
params: {
name: 'get_osc_messages',
arguments: { endpointId: 'non-existent' },
},
};
const result = await callToolHandler(request);
const response = JSON.parse(result.content[0].text);
expect(response.messages).toEqual([]);
expect(response.totalCount).toBe(0);
});
});
describe('Complex Validation Scenarios', () => {
it('should validate complex get_messages parameters', async () => {
const invalidRequests = [
{
arguments: { timeWindowSeconds: 'invalid' },
},
{
arguments: { timeWindowSeconds: 100000 },
},
{
arguments: { limit: 'invalid' },
},
{
arguments: { limit: 2000 },
},
{
arguments: { addressPattern: 'invalid' },
},
];
for (const args of invalidRequests) {
const request = {
params: {
name: 'get_osc_messages',
arguments: args.arguments,
},
};
try {
await callToolHandler(request);
fail(`Expected error for arguments: ${JSON.stringify(args.arguments)}`);
}
catch (error) {
expect(error.code).toBe('InvalidParams');
}
}
});
it('should validate buffer size limits', async () => {
const invalidRequests = [
{ port: 8000, bufferSize: 0 },
{ port: 8001, bufferSize: 20000 },
{ port: 8002, bufferSize: 'invalid' },
];
for (const args of invalidRequests) {
const request = {
params: {
name: 'create_osc_endpoint',
arguments: args,
},
};
try {
await callToolHandler(request);
fail(`Expected error for arguments: ${JSON.stringify(args)}`);
}
catch (error) {
expect(error.code).toBe('InvalidParams');
}
}
});
});
describe('Error Recovery and Suggestions', () => {
it('should provide helpful error messages with suggestions', async () => {
const request = {
params: {
name: 'create_osc_endpoint',
arguments: { port: 100 },
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.message).toContain("Parameter 'port' value 100 is out of range");
expect(error.message).toContain('Must be between 1024 and 65535');
expect(error.details).toBeDefined();
expect(error.details.validRange).toEqual({ min: 1024, max: 65535 });
}
});
it('should handle permission denied scenarios gracefully', async () => {
const oscManager = (0, manager_1.createOSCManager)();
jest.spyOn(oscManager, 'createEndpoint').mockResolvedValue({
endpointId: '',
port: 80,
status: 'error',
message: 'Permission denied to bind to port 80. Try using a port number above 1024 or run with appropriate privileges.',
});
const response = await oscManager.createEndpoint({ port: 80 });
expect(response.status).toBe('error');
expect(response.message).toContain('Permission denied');
expect(response.message).toContain('Try using a port number above 1024');
});
});
describe('Tool Error Handling', () => {
it('should handle unknown tool names gracefully', async () => {
const request = {
params: {
name: 'unknown_tool',
arguments: {},
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('MethodNotFound');
expect(error.message).toContain('Unknown tool: unknown_tool');
}
});
it('should convert unexpected errors to proper MCP errors', async () => {
const oscManager = server.getOSCManager();
jest
.spyOn(oscManager, 'createEndpoint')
.mockRejectedValue(new Error('Unexpected system error'));
const request = {
params: {
name: 'create_osc_endpoint',
arguments: { port: 8000 },
},
};
try {
await callToolHandler(request);
fail('Expected error to be thrown');
}
catch (error) {
expect(error.code).toBe('InternalError');
expect(error.message).toContain("Operation 'create_osc_endpoint' failed");
}
});
});
describe('Concurrent Error Scenarios', () => {
it('should handle multiple simultaneous endpoint creation attempts', async () => {
const requests = Array.from({ length: 3 }, () => ({
params: {
name: 'create_osc_endpoint',
arguments: { port: 8000 },
},
}));
const results = await Promise.allSettled(requests.map(req => callToolHandler(req)));
let successCount = 0;
let errorCount = 0;
for (const result of results) {
if (result.status === 'fulfilled') {
const response = JSON.parse(result.value.content[0].text);
if (response.status === 'active') {
successCount++;
}
else if (response.status === 'error') {
errorCount++;
}
}
else {
errorCount++;
}
}
expect(successCount).toBeGreaterThanOrEqual(1);
expect(errorCount).toBeGreaterThan(0);
expect(successCount + errorCount).toBe(3);
});
});
afterEach(async () => {
try {
const oscManager = server.getOSCManager();
await oscManager.shutdown();
}
catch (_error) {
}
});
});
//# sourceMappingURL=integration.test.js.map