fix: logs command resolves server names, proper replica handling
- `mcpctl logs <server-name>` resolves to first RUNNING instance - `mcpctl logs <server-name> -i <N>` selects specific replica - Shows "instance N/M" hint when server has multiple replicas - Added 5 proper tests: server name resolution, RUNNING preference, replica selection, out-of-range error, no instances error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,16 +68,79 @@ describe('logs command', () => {
|
||||
output = [];
|
||||
});
|
||||
|
||||
it('shows logs', async () => {
|
||||
vi.mocked(client.get).mockResolvedValue({ stdout: 'hello world\n', stderr: '' });
|
||||
it('shows logs by instance ID', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockResolvedValueOnce({ id: 'inst-1', status: 'RUNNING' } as never) // instance lookup
|
||||
.mockResolvedValueOnce({ stdout: 'hello world\n', stderr: '' } as never); // logs
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await cmd.parseAsync(['inst-1'], { from: 'user' });
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1');
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
|
||||
expect(output.join('\n')).toContain('hello world');
|
||||
});
|
||||
|
||||
it('resolves server name to instance ID', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockRejectedValueOnce(new Error('not found')) // instance lookup fails
|
||||
.mockResolvedValueOnce([{ id: 'srv-1', name: 'my-grafana' }] as never) // servers list
|
||||
.mockResolvedValueOnce([{ id: 'inst-1', status: 'RUNNING', containerId: 'abc' }] as never) // instances for server
|
||||
.mockResolvedValueOnce({ stdout: 'grafana logs\n', stderr: '' } as never); // logs
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await cmd.parseAsync(['my-grafana'], { from: 'user' });
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
|
||||
expect(output.join('\n')).toContain('grafana logs');
|
||||
});
|
||||
|
||||
it('picks RUNNING instance over others', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockRejectedValueOnce(new Error('not found'))
|
||||
.mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'inst-err', status: 'ERROR', containerId: null },
|
||||
{ id: 'inst-ok', status: 'RUNNING', containerId: 'abc' },
|
||||
] as never)
|
||||
.mockResolvedValueOnce({ stdout: 'running instance\n', stderr: '' } as never);
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await cmd.parseAsync(['ha-mcp'], { from: 'user' });
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-ok/logs');
|
||||
});
|
||||
|
||||
it('selects specific replica with --instance', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockRejectedValueOnce(new Error('not found'))
|
||||
.mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'inst-0', status: 'RUNNING', containerId: 'a' },
|
||||
{ id: 'inst-1', status: 'RUNNING', containerId: 'b' },
|
||||
] as never)
|
||||
.mockResolvedValueOnce({ stdout: 'replica 1\n', stderr: '' } as never);
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await cmd.parseAsync(['ha-mcp', '-i', '1'], { from: 'user' });
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
|
||||
});
|
||||
|
||||
it('throws on out-of-range --instance index', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockRejectedValueOnce(new Error('not found'))
|
||||
.mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
|
||||
.mockResolvedValueOnce([{ id: 'inst-0', status: 'RUNNING' }] as never);
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await expect(cmd.parseAsync(['ha-mcp', '-i', '5'], { from: 'user' })).rejects.toThrow('out of range');
|
||||
});
|
||||
|
||||
it('throws when server has no instances', async () => {
|
||||
vi.mocked(client.get)
|
||||
.mockRejectedValueOnce(new Error('not found'))
|
||||
.mockResolvedValueOnce([{ id: 'srv-1', name: 'empty-srv' }] as never)
|
||||
.mockResolvedValueOnce([] as never);
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await expect(cmd.parseAsync(['empty-srv'], { from: 'user' })).rejects.toThrow('No instances found');
|
||||
});
|
||||
|
||||
it('passes tail option', async () => {
|
||||
vi.mocked(client.get).mockResolvedValue({ stdout: '', stderr: '' });
|
||||
vi.mocked(client.get)
|
||||
.mockResolvedValueOnce({ id: 'inst-1' } as never)
|
||||
.mockResolvedValueOnce({ stdout: '', stderr: '' } as never);
|
||||
const cmd = createLogsCommand({ client, log });
|
||||
await cmd.parseAsync(['inst-1', '-t', '50'], { from: 'user' });
|
||||
expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs?tail=50');
|
||||
|
||||
Reference in New Issue
Block a user