Files
sage-mcp-server/src/tools/__tests__/sage-health.test.ts

178 lines
6.7 KiB
TypeScript

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<typeof vi.spyOn>;
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);
});
});