import { describe, it, expect, vi } from 'vitest'; import { MCPClient, type McpLogger, type MCPConfig } from './index.js'; // ───────────────────────────────────────────────────────────────────────────── // Tests for the pluggable McpLogger interface introduced in TODO-3. // // We exercise the constructor's logger selection and the `disconnect()` path // (the simplest method that produces a log line without actually attempting // to spawn an MCP server subprocess via StdioClientTransport). // ───────────────────────────────────────────────────────────────────────────── function makeConfig(overrides: Partial = {}): MCPConfig { return { serverCommand: 'echo', serverArgs: 'noop', envFile: '/dev/null', timeoutMs: 1000, cacheTtlSec: 60, maxRetries: 0, ...overrides, }; } function makeMockLogger(): McpLogger & { debug: ReturnType; info: ReturnType; warn: ReturnType; error: ReturnType; } { return { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; } describe('MCPClient logger injection', () => { it('defaults to the global console when no logger is provided', () => { // The cleanest behavioural check: the client is constructible without a // logger config, which proves the `?? console` fallback works. const client = new MCPClient(makeConfig()); expect(client).toBeDefined(); }); it('uses an injected logger for warn paths', async () => { const logger = makeMockLogger(); const client = new MCPClient(makeConfig({ logger })); // Calling disconnect() when not connected returns immediately with no // log calls (it's a no-op early return). Calling connect() twice would // emit a warn, but the second call has to wait for the first transport // to attach which we can't do in a unit test. Instead, we verify the // simpler invariant: the disconnect() early-return path doesn't log. await client.disconnect(); expect(logger.warn).not.toHaveBeenCalled(); expect(logger.error).not.toHaveBeenCalled(); }); it('does not throw when the user supplies a partial-shape logger', () => { // Anything matching the McpLogger structural type is accepted. We pass a // pino-shaped logger that uses no-op functions to confirm the contract. const pinoShaped: McpLogger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {}, }; expect(() => new MCPClient(makeConfig({ logger: pinoShaped }))).not.toThrow(); }); it('McpLogger methods accept variadic structured arguments', () => { // Structural-typing check: the interface must accept (msg, ...args). // This compiles iff the McpLogger signatures use `...args: unknown[]`. const logger = makeMockLogger(); logger.info('hello', { a: 1 }, 'world', 42); expect(logger.info).toHaveBeenCalledWith('hello', { a: 1 }, 'world', 42); }); });