124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { handleList as runHandleList } from '../src/cli/list-command.js';
|
|
import type { ServerDefinition } from '../src/config.js';
|
|
import type { Runtime } from '../src/runtime.js';
|
|
|
|
const healthyDefinition: ServerDefinition = {
|
|
name: 'healthy',
|
|
command: { kind: 'http', url: new URL('https://healthy.example.com/mcp') },
|
|
};
|
|
|
|
const authDefinition: ServerDefinition = {
|
|
name: 'auth-server',
|
|
command: { kind: 'http', url: new URL('https://auth.example.com/mcp') },
|
|
};
|
|
|
|
function createRuntime(): Runtime {
|
|
const definitions = [healthyDefinition, authDefinition];
|
|
return {
|
|
getDefinitions: () => definitions,
|
|
getDefinition: (name: string) => {
|
|
const definition = definitions.find((entry) => entry.name === name);
|
|
if (!definition) {
|
|
throw new Error(`Unknown server '${name}'`);
|
|
}
|
|
return definition;
|
|
},
|
|
registerDefinition: vi.fn(),
|
|
listTools: vi.fn((name: string) => {
|
|
if (name === 'healthy') {
|
|
return Promise.resolve([{ name: 'list_documents' }]);
|
|
}
|
|
return Promise.reject(new Error('HTTP error 401: auth required'));
|
|
}),
|
|
} as unknown as Runtime;
|
|
}
|
|
|
|
describe('handleList JSON output', () => {
|
|
it('emits aggregated status counts', async () => {
|
|
const runtime = createRuntime();
|
|
const previousExitCode = process.exitCode;
|
|
process.exitCode = undefined;
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
try {
|
|
await runHandleList(runtime, ['--json']);
|
|
|
|
const payload = JSON.parse(logSpy.mock.calls.at(-1)?.[0] ?? '{}');
|
|
expect(payload.mode).toBe('list');
|
|
expect(payload.counts.auth).toBe(1);
|
|
const healthyEntry = payload.servers.find((entry: { name: string }) => entry.name === 'healthy');
|
|
expect(healthyEntry.status).toBe('ok');
|
|
const authEntry = payload.servers.find((entry: { name: string }) => entry.name === 'auth-server');
|
|
expect(authEntry.status).toBe('auth');
|
|
expect(authEntry.issue.kind).toBe('auth');
|
|
expect(process.exitCode).toBeUndefined();
|
|
} finally {
|
|
logSpy.mockRestore();
|
|
process.exitCode = previousExitCode;
|
|
}
|
|
});
|
|
|
|
it('sets a non-zero exit code for unhealthy multi-server checks when requested', async () => {
|
|
const runtime = createRuntime();
|
|
const previousExitCode = process.exitCode;
|
|
process.exitCode = undefined;
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
try {
|
|
await runHandleList(runtime, ['--json', '--exit-code']);
|
|
|
|
const payload = JSON.parse(logSpy.mock.calls.at(-1)?.[0] ?? '{}');
|
|
expect(payload.counts.auth).toBe(1);
|
|
expect(process.exitCode).toBe(1);
|
|
} finally {
|
|
logSpy.mockRestore();
|
|
process.exitCode = previousExitCode;
|
|
}
|
|
});
|
|
|
|
it('suppresses output and sets the exit code for quiet checks', async () => {
|
|
const runtime = createRuntime();
|
|
const previousExitCode = process.exitCode;
|
|
process.exitCode = undefined;
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
|
|
try {
|
|
await runHandleList(runtime, ['--quiet']);
|
|
|
|
expect(logSpy).not.toHaveBeenCalled();
|
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
} finally {
|
|
logSpy.mockRestore();
|
|
warnSpy.mockRestore();
|
|
process.exitCode = previousExitCode;
|
|
}
|
|
});
|
|
|
|
it('emits a concise single-server status payload', async () => {
|
|
const runtime = createRuntime();
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
await runHandleList(runtime, ['healthy', '--status', '--json']);
|
|
|
|
const payload = JSON.parse(logSpy.mock.calls.at(-1)?.[0] ?? '{}');
|
|
expect(payload.mode).toBe('list');
|
|
expect(payload.counts.ok).toBe(1);
|
|
expect(payload.servers).toHaveLength(1);
|
|
expect(payload.servers[0].name).toBe('healthy');
|
|
expect(payload.servers[0].status).toBe('ok');
|
|
|
|
logSpy.mockRestore();
|
|
});
|
|
|
|
it('rejects status checks for configured tool selectors', async () => {
|
|
const runtime = createRuntime();
|
|
|
|
await expect(runHandleList(runtime, ['healthy.list_documents', '--status'])).rejects.toThrow(
|
|
'--status cannot be used with a tool selector.'
|
|
);
|
|
});
|
|
});
|