import { describe, it, expect, vi, beforeEach } from 'vitest'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { registerHealthTool } from '../sage-health.js'; import type { RestClient } from '../../clients/rest-client.js'; import type { SoapClient } from '../../clients/soap-client.js'; import type { SageConfig } from '../../types/index.js'; const mockConfig: SageConfig = { url: 'https://x3.example.com:8124', user: 'admin', password: 'secret123', endpoint: 'SEED', poolAlias: 'SEED', language: 'ENG', rejectUnauthorized: true, }; function createMockRestClient( healthResult?: { status: string; latencyMs: number }, shouldReject = false, ): RestClient { return { healthCheck: shouldReject ? vi.fn().mockRejectedValue(new Error('REST connection refused')) : vi.fn().mockResolvedValue(healthResult ?? { status: 'connected', latencyMs: 42 }), } as unknown as RestClient; } function createMockSoapClient( healthResult?: { status: string; latencyMs: number }, shouldReject = false, ): SoapClient { return { healthCheck: shouldReject ? vi.fn().mockRejectedValue(new Error('SOAP connection refused')) : vi.fn().mockResolvedValue(healthResult ?? { status: 'connected', latencyMs: 55 }), } as unknown as SoapClient; } describe('registerHealthTool', () => { let server: McpServer; let registerToolSpy: ReturnType; beforeEach(() => { server = new McpServer({ name: 'test-server', version: '1.0.0' }); registerToolSpy = vi.spyOn(server, 'registerTool'); }); it('registers sage_health tool with correct metadata', () => { const restClient = createMockRestClient(); const soapClient = createMockSoapClient(); registerHealthTool(server, restClient, soapClient, mockConfig); expect(registerToolSpy).toHaveBeenCalledOnce(); const [name, config] = registerToolSpy.mock.calls[0]; expect(name).toBe('sage_health'); expect(config).toMatchObject({ description: expect.stringContaining('Check connectivity'), annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }); }); describe('handler', () => { async function callHealthHandler( restClient: RestClient, soapClient: SoapClient, ): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> { registerHealthTool(server, restClient, soapClient, mockConfig); const handler = registerToolSpy.mock.calls[0][2] as () => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean; }>; return handler(); } it('returns health status with rest and soap sections on success', async () => { const restClient = createMockRestClient({ status: 'connected', latencyMs: 42 }); const soapClient = createMockSoapClient({ status: 'connected', latencyMs: 55 }); const result = await callHealthHandler(restClient, soapClient); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const parsed = JSON.parse(result.content[0].text); expect(parsed).toEqual({ rest: { status: 'connected', latencyMs: 42, endpoint: 'SEED' }, soap: { status: 'connected', latencyMs: 55, poolAlias: 'SEED' }, }); }); it('handles REST failure gracefully via allSettled', async () => { const restClient = createMockRestClient(undefined, true); const soapClient = createMockSoapClient({ status: 'connected', latencyMs: 30 }); const result = await callHealthHandler(restClient, soapClient); expect(result.content).toHaveLength(1); const parsed = JSON.parse(result.content[0].text); expect(parsed.rest).toEqual({ status: 'error', latencyMs: 0, endpoint: 'SEED' }); expect(parsed.soap).toEqual({ status: 'connected', latencyMs: 30, poolAlias: 'SEED' }); }); it('handles SOAP failure gracefully via allSettled', async () => { const restClient = createMockRestClient({ status: 'connected', latencyMs: 25 }); const soapClient = createMockSoapClient(undefined, true); const result = await callHealthHandler(restClient, soapClient); expect(result.content).toHaveLength(1); const parsed = JSON.parse(result.content[0].text); expect(parsed.rest).toEqual({ status: 'connected', latencyMs: 25, endpoint: 'SEED' }); expect(parsed.soap).toEqual({ status: 'error', latencyMs: 0, poolAlias: 'SEED' }); }); it('handles both REST and SOAP failures', async () => { const restClient = createMockRestClient(undefined, true); const soapClient = createMockSoapClient(undefined, true); const result = await callHealthHandler(restClient, soapClient); expect(result.content).toHaveLength(1); const parsed = JSON.parse(result.content[0].text); expect(parsed.rest).toEqual({ status: 'error', latencyMs: 0, endpoint: 'SEED' }); expect(parsed.soap).toEqual({ status: 'error', latencyMs: 0, poolAlias: 'SEED' }); }); it('returns error status string from client healthCheck', async () => { const restClient = createMockRestClient({ status: 'error: ECONNREFUSED', latencyMs: 5 }); const soapClient = createMockSoapClient({ status: 'error: WSDL not found', latencyMs: 10 }); const result = await callHealthHandler(restClient, soapClient); const parsed = JSON.parse(result.content[0].text); expect(parsed.rest.status).toBe('error: ECONNREFUSED'); expect(parsed.soap.status).toBe('error: WSDL not found'); }); it('includes endpoint and poolAlias from config', async () => { const restClient = createMockRestClient(); const soapClient = createMockSoapClient(); const result = await callHealthHandler(restClient, soapClient); const parsed = JSON.parse(result.content[0].text); expect(parsed.rest.endpoint).toBe('SEED'); expect(parsed.soap.poolAlias).toBe('SEED'); }); it('returns valid MCP content format', async () => { const restClient = createMockRestClient(); const soapClient = createMockSoapClient(); const result = await callHealthHandler(restClient, soapClient); expect(result).toHaveProperty('content'); expect(Array.isArray(result.content)).toBe(true); expect(result.content[0]).toHaveProperty('type', 'text'); expect(result.content[0]).toHaveProperty('text'); expect(() => JSON.parse(result.content[0].text)).not.toThrow(); }); }); }); describe('createServer', () => { it('creates server with sage_health tool registered', async () => { const { createServer } = await import('../../server.js'); const server = createServer(mockConfig); expect(server).toBeInstanceOf(McpServer); }); });