import { registerConnectorResources } from '../../src/resources/connector-resources';
import { SimplifierClient } from '../../src/client/simplifier-client';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import {
SimplifierConnectorListResponse,
SimplifierConnectorDetails,
SimplifierConnectorCallsResponse,
SimplifierConnectorCallDetails
} from '../../src/client/types';
// Mock the resourcesresult wrapper
jest.mock('../../src/resources/resourcesresult', () => ({
wrapResourceResult: jest.fn()
}));
// Mock the SimplifierClient
jest.mock('../../src/client/simplifier-client');
describe('Connector Resources', () => {
let mockServer: jest.Mocked<McpServer>;
let mockClient: jest.Mocked<SimplifierClient>;
let mockWrapResourceResult: jest.MockedFunction<any>;
beforeEach(() => {
// Create mock server with resource method
mockServer = {
resource: jest.fn(),
} as any;
// Create mock client
mockClient = {
listConnectors: jest.fn(),
getConnector: jest.fn(),
listConnectorCalls: jest.fn(),
getConnectorCall: jest.fn(),
} as any;
// Get the mocked wrapResourceResult
mockWrapResourceResult = require('../../src/resources/resourcesresult').wrapResourceResult;
mockWrapResourceResult.mockClear();
});
const createMockExtra = () => ({
signal: new AbortController().signal,
requestId: 'test-request-id',
sendNotification: jest.fn(),
sendRequest: jest.fn()
});
afterEach(() => {
jest.clearAllMocks();
});
const mockConnectorListResponse: SimplifierConnectorListResponse = {
connectors: [
{
name: 'TestConnector',
description: 'A test connector for API testing',
connectorType: {
technicalName: 'REST',
i18n: 'REST API',
descriptionI18n: 'REST API Connector'
},
active: true,
timeoutTime: 30000,
amountOfCalls: 3,
editable: true,
deletable: true,
tags: ['test', 'api'],
assignedProjects: {
projectsBefore: ['project1'],
projectsAfterChange: ['project1']
}
},
{
name: 'DatabaseConnector',
description: 'Database connector',
connectorType: {
technicalName: 'SQL',
i18n: 'SQL Database',
descriptionI18n: 'SQL Database Connector'
},
active: true,
timeoutTime: 60000,
amountOfCalls: 5,
editable: true,
deletable: false,
tags: ['database'],
assignedProjects: {
projectsBefore: [],
projectsAfterChange: []
}
}
]
};
const mockConnectorDetails: SimplifierConnectorDetails = {
name: 'TestConnector',
description: 'A test connector for API testing',
connectorType: {
technicalName: 'REST',
i18n: 'REST API',
descriptionI18n: 'REST API Connector'
},
active: true,
timeoutTime: 30000,
amountOfCalls: 3,
editable: true,
deletable: true,
tags: ['test', 'api'],
assignedProjects: {
projectsBefore: ['project1'],
projectsAfterChange: ['project1']
},
configuration: {
endpoints: [
{
endpointName: 'production',
endpointType: 'REST'
}
]
}
};
const mockConnectorCallsResponse: SimplifierConnectorCallsResponse = {
connectorCalls: [
{
name: 'getData',
description: 'Fetch data from API',
validateIn: true,
validateOut: true,
editable: true,
executable: true,
autoGenerated: false,
amountOfInputParameters: 2,
amountOfOutputParameters: 1
},
{
name: 'postData',
description: 'Send data to API',
validateIn: true,
validateOut: false,
editable: true,
executable: true,
autoGenerated: false,
amountOfInputParameters: 3,
amountOfOutputParameters: 0
}
]
};
const mockConnectorCallDetails: SimplifierConnectorCallDetails = {
name: 'getData',
description: 'Fetch data from API',
validateIn: true,
validateOut: true,
editable: true,
executable: true,
autoGenerated: false,
async: false,
connectorName: 'TestConnector',
connectorCallParameters: [
{
name: 'id',
alias: 'recordId',
isInput: true,
dataType: {
name: 'String',
category: 'base'
},
optional: false,
position: 0
},
{
name: 'includeDetails',
isInput: true,
dataType: {
name: 'Boolean',
category: 'base'
},
optional: true,
position: 1
},
{
name: 'result',
isInput: false,
dataType: {
name: 'DataRecord',
nameSpace: 'con/TestConnector',
category: 'struct'
},
optional: false,
position: 0
}
]
};
describe('registerConnectorResources', () => {
it('should register six connector resources', () => {
registerConnectorResources(mockServer, mockClient);
expect(mockServer.resource).toHaveBeenCalledTimes(6);
// Check that specific resources are registered
const calls = mockServer.resource.mock.calls;
const resourceNames = calls.map(call => call[0]);
expect(resourceNames).toContain('connectors-list');
expect(resourceNames).toContain('connector-details');
expect(resourceNames).toContain('connector-calls-list');
expect(resourceNames).toContain('connector-call-details');
expect(resourceNames).toContain('connector-wsdl');
expect(resourceNames).toContain('connector-wizard-search-functions');
});
describe('connectors list handler', () => {
let connectorsListHandler: any;
beforeEach(() => {
registerConnectorResources(mockServer, mockClient);
connectorsListHandler = mockServer.resource.mock.calls[0][3]; // First resource (connectors list)
});
it('should call wrapResourceResult with correct parameters', async () => {
const testUri = new URL('simplifier://connectors');
mockWrapResourceResult.mockResolvedValue({ contents: [] });
await connectorsListHandler(testUri, {}, createMockExtra());
expect(mockWrapResourceResult).toHaveBeenCalledWith(
testUri,
expect.any(Function)
);
});
it('should return connector list through wrapper', async () => {
const testUri = new URL('simplifier://connectors');
mockClient.listConnectors.mockResolvedValue(mockConnectorListResponse);
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
const result = await fn();
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result, null, 2),
mimeType: 'application/json'
}]
};
});
const result = await connectorsListHandler(testUri, {}, createMockExtra());
expect(mockClient.listConnectors).toHaveBeenCalled();
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.connectors).toHaveLength(2);
expect(resultData.totalCount).toBe(2);
expect(resultData.connectors[0].uri).toBe('simplifier://connector/TestConnector');
expect(resultData.connectors[0].name).toBe('TestConnector');
expect(resultData.resourcePatterns).toHaveLength(5);
});
it('should handle API errors through wrapper', async () => {
const testUri = new URL('simplifier://connectors');
const testError = new Error('API Error');
mockClient.listConnectors.mockRejectedValue(testError);
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
try {
await fn();
return { contents: [] };
} catch (e) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({ error: `Failed to fetch: ${e}` }),
mimeType: 'application/json'
}]
};
}
});
const result = await connectorsListHandler(testUri, {}, createMockExtra());
expect(mockClient.listConnectors).toHaveBeenCalled();
expect(result.contents[0].text).toContain('Failed to fetch');
expect(result.contents[0].text).toContain('API Error');
});
});
describe('connector details handler', () => {
let connectorDetailsHandler: any;
beforeEach(() => {
registerConnectorResources(mockServer, mockClient);
connectorDetailsHandler = mockServer.resource.mock.calls[1][3]; // Second resource (connector details)
});
it('should return connector details', async () => {
const testUri = new URL('simplifier://connector/TestConnector');
mockClient.getConnector.mockResolvedValue(mockConnectorDetails);
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
const result = await fn();
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result, null, 2),
mimeType: 'application/json'
}]
};
});
const result = await connectorDetailsHandler(testUri, {}, createMockExtra());
expect(mockClient.getConnector).toHaveBeenCalledWith('TestConnector', 'MCP Resource: connector-details', true);
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.connector.name).toBe('TestConnector');
expect(resultData.connector.active).toBe(true);
expect(resultData.relatedResources).toHaveLength(1);
expect(resultData.relatedResources[0].uri).toBe('simplifier://connector/TestConnector/calls');
});
it('should throw error if connector name is missing', async () => {
const testUri = new URL('simplifier://connector/');
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
try {
await fn();
return { contents: [] };
} catch (e: any) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({ error: e.message }),
mimeType: 'application/json'
}]
};
}
});
const result = await connectorDetailsHandler(testUri, {}, createMockExtra());
expect(mockClient.getConnector).not.toHaveBeenCalled();
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.error).toBe('Connector name is required');
});
});
describe('connector calls list handler', () => {
let connectorCallsListHandler: any;
beforeEach(() => {
registerConnectorResources(mockServer, mockClient);
connectorCallsListHandler = mockServer.resource.mock.calls[2][3]; // Third resource (calls list)
});
it('should return connector calls list', async () => {
const testUri = new URL('simplifier://connector/TestConnector/calls');
mockClient.listConnectorCalls.mockResolvedValue(mockConnectorCallsResponse);
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
const result = await fn();
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result, null, 2),
mimeType: 'application/json'
}]
};
});
const result = await connectorCallsListHandler(testUri, {}, createMockExtra());
expect(mockClient.listConnectorCalls).toHaveBeenCalledWith('TestConnector', 'MCP Resource: connector-calls-list');
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.connectorName).toBe('TestConnector');
expect(resultData.calls).toHaveLength(2);
expect(resultData.calls[0].name).toBe('getData');
expect(resultData.calls[0].uri).toBe('simplifier://connector/TestConnector/call/getData');
expect(resultData.totalCount).toBe(2);
});
});
describe('connector call details handler', () => {
let connectorCallDetailsHandler: any;
beforeEach(() => {
registerConnectorResources(mockServer, mockClient);
connectorCallDetailsHandler = mockServer.resource.mock.calls[3][3]; // Fourth resource (call details)
});
it('should return connector call details', async () => {
const testUri = new URL('simplifier://connector/TestConnector/call/getData');
mockClient.getConnectorCall.mockResolvedValue(mockConnectorCallDetails);
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
const result = await fn();
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result, null, 2),
mimeType: 'application/json'
}]
};
});
const result = await connectorCallDetailsHandler(testUri, {}, createMockExtra());
expect(mockClient.getConnectorCall).toHaveBeenCalledWith('TestConnector', 'getData', 'MCP Resource: connector-call-details');
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.call.name).toBe('getData');
expect(resultData.call.connectorCallParameters).toHaveLength(3);
expect(resultData.usage.note).toContain('Business Object functions');
expect(resultData.usage.example).toContain('Simplifier.Connector.TestConnector.getData');
});
it('should throw error if connector name is missing', async () => {
const testUri = new URL('simplifier://connector//call/getData');
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
try {
await fn();
return { contents: [] };
} catch (e: any) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({ error: e.message }),
mimeType: 'application/json'
}]
};
}
});
const result = await connectorCallDetailsHandler(testUri, {}, createMockExtra());
expect(mockClient.getConnectorCall).not.toHaveBeenCalled();
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.error).toBe('Connector name and call name are required');
});
it('should throw error if call name is missing', async () => {
const testUri = new URL('simplifier://connector/TestConnector/call/');
mockWrapResourceResult.mockImplementation(async (uri: URL, fn: () => any) => {
try {
await fn();
return { contents: [] };
} catch (e: any) {
return {
contents: [{
uri: uri.href,
text: JSON.stringify({ error: e.message }),
mimeType: 'application/json'
}]
};
}
});
const result = await connectorCallDetailsHandler(testUri, {}, createMockExtra());
expect(mockClient.getConnectorCall).not.toHaveBeenCalled();
const resultData = JSON.parse(result.contents[0].text as string);
expect(resultData.error).toBe('Connector name and call name are required');
});
});
});
describe('resource registration configuration', () => {
it('should register connectors-list as a simple resource', () => {
registerConnectorResources(mockServer, mockClient);
// First call should be simple resource (string URI)
expect(mockServer.resource).toHaveBeenNthCalledWith(
1,
'connectors-list',
'simplifier://connectors',
expect.objectContaining({
title: 'List All Connectors',
mimeType: 'application/json'
}),
expect.any(Function)
);
});
it('should register connector-details as a template resource', () => {
registerConnectorResources(mockServer, mockClient);
// Second call should be template resource
const secondCall = mockServer.resource.mock.calls[1];
expect(secondCall[0]).toBe('connector-details');
expect(secondCall[1]).toHaveProperty('uriTemplate');
expect(secondCall[2]).toHaveProperty('title', 'Connector Details');
});
it('should register connector-calls-list as a template resource', () => {
registerConnectorResources(mockServer, mockClient);
// Third call should be template resource
const thirdCall = mockServer.resource.mock.calls[2];
expect(thirdCall[0]).toBe('connector-calls-list');
expect(thirdCall[1]).toHaveProperty('uriTemplate');
expect(thirdCall[2]).toHaveProperty('title', 'Connector Calls List');
});
it('should register connector-call-details as a template resource', () => {
registerConnectorResources(mockServer, mockClient);
// Fourth call should be template resource
const fourthCall = mockServer.resource.mock.calls[3];
expect(fourthCall[0]).toBe('connector-call-details');
expect(fourthCall[1]).toHaveProperty('uriTemplate');
expect(fourthCall[2]).toHaveProperty('title', 'Connector Call Details');
});
});
});