Skip to main content
Glama
loginmethod-resources.test.ts26.8 kB
import { registerLoginMethodResources } from '../../src/resources/loginmethod-resources'; import { SimplifierClient } from '../../src/client/simplifier-client'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { SimplifierLoginMethodsResponse } 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('LoginMethod 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 = { listLoginMethods: jest.fn(), getLoginMethodDetails: 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 mockLoginMethodsResponse: SimplifierLoginMethodsResponse = { loginMethods: [ { name: 'TestUserCredentials', description: 'Test user credentials login', loginMethodType: { technicalName: 'UserCredentials', i18n: 'loginMethodType_UserCredentials_Caption', descriptionI18n: 'loginMethodType_UserCredentials_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' }, { id: 1, name: 'PROVIDED', i18nName: 'login_method_source_provided', i18nDescription: 'login_method_source_provided_description' } ], targets: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_target_default', i18nDescription: 'login_method_target_undefined_description' } ], supportedConnectors: ['Email', 'MQTT', 'OData', 'REST', 'SOAP', 'SQL'] }, source: 0, target: 0, updateInfo: { created: '2025-05-08T16:01:40+02:00', creator: { loginName: 'admin', firstName: 'Admin', lastName: 'User', platformDomain: 'localhost', differentPlatformDomain: false } }, editable: true, deletable: true }, { name: 'OAuthSpotify', description: 'OAuth for Spotify API', loginMethodType: { technicalName: 'OAuth2', i18n: 'loginMethodType_OAuth2_Caption', descriptionI18n: 'loginMethodType_OAuth2_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' } ], targets: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_target_default', i18nDescription: 'login_method_target_undefined_description' }, { id: 1, name: 'HEADER', i18nName: 'login_method_target_header', i18nDescription: 'login_method_target_header_description' } ], supportedConnectors: ['Email', 'OData', 'REST', 'SOAP'] }, source: 0, target: 1, updateInfo: { created: '2025-07-07T11:53:08+02:00', creator: { loginName: 'volkervonsimplifier', firstName: 'volkervonsimplifier', lastName: 'volkervonsimplifier', platformDomain: 'localhost', differentPlatformDomain: false } }, editable: true, deletable: true }, { name: 'TokenMethod', description: 'Bearer token authentication', loginMethodType: { technicalName: 'Token', i18n: 'loginMethodType_Token_Caption', descriptionI18n: 'loginMethodType_Token_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' } ], targets: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_target_default', i18nDescription: 'login_method_target_undefined_description' }, { id: 1, name: 'HEADER', i18nName: 'login_method_target_header', i18nDescription: 'login_method_target_header_description' } ], supportedConnectors: ['Email', 'MQTT', 'OData', 'ProxyV2', 'REST', 'SAPRFC', 'SOAP', 'SQL'] }, source: 0, target: 1, editable: true, deletable: true } ] }; describe('registerLoginMethodResources', () => { it('should register two login methods resources', () => { registerLoginMethodResources(mockServer, mockClient); expect(mockServer.resource).toHaveBeenCalledTimes(2); // Check that specific resources are registered const calls = mockServer.resource.mock.calls; const resourceNames = calls.map(call => call[0]); expect(resourceNames).toContain('loginmethods-list'); expect(resourceNames).toContain('loginmethod-details'); }); describe('loginmethods list handler', () => { let loginMethodsListHandler: any; beforeEach(() => { registerLoginMethodResources(mockServer, mockClient); loginMethodsListHandler = mockServer.resource.mock.calls[0][3]; // First resource (loginmethods list) }); it('should call wrapResourceResult with correct parameters', async () => { const testUri = new URL('simplifier://loginmethods'); mockWrapResourceResult.mockResolvedValue({ contents: [] }); await loginMethodsListHandler(testUri, {}, createMockExtra()); expect(mockWrapResourceResult).toHaveBeenCalledWith( testUri, expect.any(Function) ); }); it('should return login methods list through wrapper', async () => { const testUri = new URL('simplifier://loginmethods'); mockClient.listLoginMethods.mockResolvedValue(mockLoginMethodsResponse); 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 loginMethodsListHandler(testUri, {}, createMockExtra()); expect(mockClient.listLoginMethods).toHaveBeenCalled(); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.loginMethods).toHaveLength(3); expect(resultData.totalCount).toBe(3); expect(resultData.resourcePatterns).toHaveLength(2); }); it('should return simplified login method information', async () => { const testUri = new URL('simplifier://loginmethods'); mockClient.listLoginMethods.mockResolvedValue(mockLoginMethodsResponse); 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 loginMethodsListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.loginMethods[0].name).toBe('TestUserCredentials'); expect(resultData.loginMethods[0].type).toBe('UserCredentials'); expect(resultData.loginMethods[1].name).toBe('OAuthSpotify'); expect(resultData.loginMethods[1].type).toBe('OAuth2'); expect(resultData.loginMethods[2].name).toBe('TokenMethod'); expect(resultData.loginMethods[2].type).toBe('Token'); }); it('should include only essential fields in the response', async () => { const testUri = new URL('simplifier://loginmethods'); mockClient.listLoginMethods.mockResolvedValue(mockLoginMethodsResponse); 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 loginMethodsListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); const firstLoginMethod = resultData.loginMethods[0]; // Should have these fields expect(firstLoginMethod).toHaveProperty('uri'); expect(firstLoginMethod).toHaveProperty('name'); expect(firstLoginMethod).toHaveProperty('description'); expect(firstLoginMethod).toHaveProperty('type'); expect(firstLoginMethod).toHaveProperty('supportedConnectors'); expect(firstLoginMethod).toHaveProperty('updateInfo'); expect(firstLoginMethod.uri).toBe('simplifier://loginmethod/TestUserCredentials'); expect(firstLoginMethod.type).toBe('UserCredentials'); expect(firstLoginMethod.supportedConnectors).toContain('REST'); }); it('should handle login methods without updateInfo', async () => { const testUri = new URL('simplifier://loginmethods'); const { updateInfo, ...loginMethodWithoutUpdateInfo } = mockLoginMethodsResponse.loginMethods[2]; const responseWithoutUpdateInfo: SimplifierLoginMethodsResponse = { loginMethods: [loginMethodWithoutUpdateInfo as any] }; mockClient.listLoginMethods.mockResolvedValue(responseWithoutUpdateInfo); 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 loginMethodsListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.loginMethods[0].updateInfo).toBeUndefined(); }); it('should handle API errors through wrapper', async () => { const testUri = new URL('simplifier://loginmethods'); const testError = new Error('API Error'); mockClient.listLoginMethods.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 loginMethodsListHandler(testUri, {}, createMockExtra()); expect(mockClient.listLoginMethods).toHaveBeenCalled(); expect(result.contents[0].text).toContain('Failed to fetch'); expect(result.contents[0].text).toContain('API Error'); }); it('should handle empty login methods list', async () => { const testUri = new URL('simplifier://loginmethods'); const emptyResponse: SimplifierLoginMethodsResponse = { loginMethods: [] }; mockClient.listLoginMethods.mockResolvedValue(emptyResponse); 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 loginMethodsListHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.loginMethods).toHaveLength(0); expect(resultData.totalCount).toBe(0); }); }); describe('loginmethod details handler', () => { let loginMethodDetailsHandler: any; beforeEach(() => { registerLoginMethodResources(mockServer, mockClient); loginMethodDetailsHandler = mockServer.resource.mock.calls[1][3]; // Second resource (loginmethod details) }); it('should call wrapResourceResult with correct parameters', async () => { const testUri = new URL('simplifier://loginmethod/TestUserCredentials'); mockWrapResourceResult.mockResolvedValue({ contents: [] }); await loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockWrapResourceResult).toHaveBeenCalledWith( testUri, expect.any(Function) ); }); it('should return UserCredentials login method details with discriminated unions', async () => { const testUri = new URL('simplifier://loginmethod/TestUserCredentials'); const mockRawDetails = { name: 'TestUserCredentials', description: 'Test user credentials login', loginMethodType: { technicalName: 'UserCredentials', i18n: 'loginMethodType_UserCredentials_Caption', descriptionI18n: 'loginMethodType_UserCredentials_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' } ], targets: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_target_default', i18nDescription: 'login_method_target_undefined_description' } ], supportedConnectors: ['Email', 'MQTT', 'OData', 'REST', 'SOAP', 'SQL'] }, source: 0, target: 0, sourceConfiguration: { jsonClass: 'de.itizzimo.simplifier.api.plugin.loginmethod.LoginData', username: 'testuser', password: '*****', changePassword: false }, targetConfiguration: {}, configuration: { convertTargetToBase64: false, convertSourceFromBase64: false } }; mockClient.getLoginMethodDetails.mockResolvedValue(mockRawDetails); 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 loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLoginMethodDetails).toHaveBeenCalledWith('TestUserCredentials', 'MCP Resource: loginmethod-details'); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.name).toBe('TestUserCredentials'); expect(resultData.type).toBe('UserCredentials'); expect(resultData.source.id).toBe(0); expect(resultData.source.name).toBe('DEFAULT'); expect(resultData.target.id).toBe(0); expect(resultData.target.name).toBe('DEFAULT'); // Check that discriminators were added expect(resultData.sourceConfiguration.type).toBe('UserCredentials'); expect(resultData.sourceConfiguration.source).toBe(0); expect(resultData.configuration.type).toBe('UserCredentials'); expect(resultData.targetConfiguration.target).toBe(0); }); it('should return OAuth2 login method details with HEADER target', async () => { const testUri = new URL('simplifier://loginmethod/oAuthSpotify'); const mockRawDetails = { name: 'oAuthSpotify', description: 'OAuth for Spotify API', loginMethodType: { technicalName: 'OAuth2', i18n: 'loginMethodType_OAuth2_Caption', descriptionI18n: 'loginMethodType_OAuth2_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' } ], targets: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_target_default', i18nDescription: 'login_method_target_undefined_description' }, { id: 1, name: 'HEADER', i18nName: 'login_method_target_header', i18nDescription: 'login_method_target_header_description' } ], supportedConnectors: ['Email', 'OData', 'REST', 'SOAP'] }, source: 0, target: 1, sourceConfiguration: { jsonClass: 'de.itizzimo.simplifier.api.plugin.loginmethod.LoginData', clientName: 'SpotifyOAuthClient' }, targetConfiguration: { name: 'Authorization' }, configuration: {} }; mockClient.getLoginMethodDetails.mockResolvedValue(mockRawDetails); 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 loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLoginMethodDetails).toHaveBeenCalledWith('oAuthSpotify', 'MCP Resource: loginmethod-details'); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.name).toBe('oAuthSpotify'); expect(resultData.type).toBe('OAuth2'); expect(resultData.target.id).toBe(1); expect(resultData.target.name).toBe('HEADER'); expect(resultData.targetConfiguration.name).toBe('Authorization'); // Check OAuth2-specific discriminators expect(resultData.sourceConfiguration.type).toBe('OAuth2'); expect(resultData.configuration.type).toBe('OAuth2'); }); it('should return Token login method details', async () => { const testUri = new URL('simplifier://loginmethod/TokenMethod'); const mockRawDetails = { name: 'TokenMethod', description: 'Bearer token authentication', loginMethodType: { technicalName: 'Token', i18n: 'loginMethodType_Token_Caption', descriptionI18n: 'loginMethodType_Token_Description', sources: [ { id: 0, name: 'DEFAULT', i18nName: 'login_method_source_default', i18nDescription: 'login_method_source_default_description' } ], targets: [ { id: 1, name: 'HEADER', i18nName: 'login_method_target_header', i18nDescription: 'login_method_target_header_description' } ], supportedConnectors: ['Email', 'MQTT', 'OData', 'ProxyV2', 'REST', 'SAPRFC', 'SOAP', 'SQL'] }, source: 0, target: 1, sourceConfiguration: {}, targetConfiguration: { name: 'Authorization' }, configuration: { convertTargetToBase64: false, convertSourceFromBase64: false } }; mockClient.getLoginMethodDetails.mockResolvedValue(mockRawDetails); 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 loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLoginMethodDetails).toHaveBeenCalledWith('TokenMethod', 'MCP Resource: loginmethod-details'); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.name).toBe('TokenMethod'); expect(resultData.type).toBe('Token'); expect(resultData.sourceConfiguration.type).toBe('Token'); expect(resultData.configuration.type).toBe('Token'); }); it('should extract login method name from URI path correctly', async () => { const testUri = new URL('simplifier://loginmethod/MyCustomMethod'); mockClient.getLoginMethodDetails.mockResolvedValue({ name: 'MyCustomMethod', description: 'Custom method', loginMethodType: { technicalName: 'UserCredentials', i18n: 'test', descriptionI18n: 'test', sources: [{ id: 0, name: 'DEFAULT', i18nName: 'test', i18nDescription: 'test' }], targets: [{ id: 0, name: 'DEFAULT', i18nName: 'test', i18nDescription: 'test' }], supportedConnectors: [] }, source: 0, target: 0, sourceConfiguration: {}, targetConfiguration: {}, configuration: {} }); mockWrapResourceResult.mockImplementation(async (_uri: URL, fn: () => any) => { await fn(); return { contents: [] }; }); await loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLoginMethodDetails).toHaveBeenCalledWith('MyCustomMethod', 'MCP Resource: loginmethod-details'); }); it('should handle API errors through wrapper', async () => { const testUri = new URL('simplifier://loginmethod/NonExistent'); const testError = new Error('Not Found'); mockClient.getLoginMethodDetails.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 loginMethodDetailsHandler(testUri, {}, createMockExtra()); expect(mockClient.getLoginMethodDetails).toHaveBeenCalled(); expect(result.contents[0].text).toContain('Failed to fetch'); expect(result.contents[0].text).toContain('Not Found'); }); it('should handle unknown source/target IDs gracefully', async () => { const testUri = new URL('simplifier://loginmethod/BadIdsMethod'); const mockRawDetails = { name: 'BadIdsMethod', description: 'Method with invalid IDs', loginMethodType: { technicalName: 'UserCredentials', i18n: 'test', descriptionI18n: 'test', sources: [{ id: 0, name: 'DEFAULT', i18nName: 'test', i18nDescription: 'test' }], targets: [{ id: 0, name: 'DEFAULT', i18nName: 'test', i18nDescription: 'test' }], supportedConnectors: [] }, source: 999, // Invalid target: 888, // Invalid sourceConfiguration: {}, targetConfiguration: {}, configuration: {} }; mockClient.getLoginMethodDetails.mockResolvedValue(mockRawDetails); 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 loginMethodDetailsHandler(testUri, {}, createMockExtra()); const resultData = JSON.parse(result.contents[0].text as string); expect(resultData.source.name).toBe('UNKNOWN'); expect(resultData.target.name).toBe('UNKNOWN'); }); }); }); describe('resource registration configuration', () => { it('should register loginmethods-list as a simple resource', () => { registerLoginMethodResources(mockServer, mockClient); // First call should be simple resource (string URI) expect(mockServer.resource).toHaveBeenNthCalledWith( 1, 'loginmethods-list', 'simplifier://loginmethods', expect.objectContaining({ title: 'List All Login Methods', mimeType: 'application/json' }), expect.any(Function) ); }); it('should have comprehensive description', () => { registerLoginMethodResources(mockServer, mockClient); const firstCall = mockServer.resource.mock.calls[0]; const config = firstCall[2]; expect(config.description).toContain('Login Methods'); expect(config.description).toContain('authentication'); expect(config.description).toContain('connectors'); }); }); });

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/SimplifierIO/simplifier-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server