import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { mkdtempSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { createStatusCommand } from '../../src/commands/status.js'; import { saveConfig, DEFAULT_CONFIG } from '../../src/config/index.js'; import { saveCredentials } from '../../src/auth/index.js'; let tempDir: string; let output: string[]; function log(...args: string[]) { output.push(args.join(' ')); } beforeEach(() => { tempDir = mkdtempSync(join(tmpdir(), 'mcpctl-status-test-')); output = []; }); afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); }); describe('status command', () => { it('shows status in table format', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); const out = output.join('\n'); expect(out).toContain('mcpctl v'); expect(out).toContain('mcplocal:'); expect(out).toContain('mcpd:'); expect(out).toContain('connected'); }); it('shows unreachable when daemons are down', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => false, }); await cmd.parseAsync([], { from: 'user' }); expect(output.join('\n')).toContain('unreachable'); }); it('shows not logged in when no credentials', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); expect(output.join('\n')).toContain('not logged in'); }); it('shows logged in user when credentials exist', async () => { saveCredentials({ token: 'tok', mcpdUrl: 'http://x:3100', user: 'alice@example.com' }, { configDir: tempDir }); const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); expect(output.join('\n')).toContain('logged in as alice@example.com'); }); it('shows status in JSON format', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync(['-o', 'json'], { from: 'user' }); const parsed = JSON.parse(output[0]) as Record; expect(parsed['version']).toBe('0.1.0'); expect(parsed['mcplocalReachable']).toBe(true); expect(parsed['mcpdReachable']).toBe(true); }); it('shows status in YAML format', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => false, }); await cmd.parseAsync(['-o', 'yaml'], { from: 'user' }); expect(output[0]).toContain('mcplocalReachable: false'); }); it('checks correct URLs from config', async () => { saveConfig({ ...DEFAULT_CONFIG, mcplocalUrl: 'http://local:3200', mcpdUrl: 'http://remote:3100' }, { configDir: tempDir }); const checkedUrls: string[] = []; const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async (url) => { checkedUrls.push(url); return false; }, }); await cmd.parseAsync([], { from: 'user' }); expect(checkedUrls).toContain('http://local:3200'); expect(checkedUrls).toContain('http://remote:3100'); }); it('shows registries from config', async () => { saveConfig({ ...DEFAULT_CONFIG, registries: ['official'] }, { configDir: tempDir }); const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); expect(output.join('\n')).toContain('official'); expect(output.join('\n')).not.toContain('glama'); }); it('shows LLM not configured hint when no LLM is set', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); const out = output.join('\n'); expect(out).toContain('LLM:'); expect(out).toContain('not configured'); expect(out).toContain('mcpctl config setup'); }); it('shows configured LLM provider and model', async () => { saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'anthropic', model: 'claude-haiku-3-5-20241022' } }, { configDir: tempDir }); const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); const out = output.join('\n'); expect(out).toContain('LLM:'); expect(out).toContain('anthropic / claude-haiku-3-5-20241022'); }); it('shows not configured when LLM provider is none', async () => { saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'none' } }, { configDir: tempDir }); const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync([], { from: 'user' }); expect(output.join('\n')).toContain('not configured'); }); it('includes llm field in JSON output', async () => { saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir }); const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync(['-o', 'json'], { from: 'user' }); const parsed = JSON.parse(output[0]) as Record; expect(parsed['llm']).toBe('gemini-cli / gemini-2.5-flash'); }); it('includes null llm in JSON output when not configured', async () => { const cmd = createStatusCommand({ configDeps: { configDir: tempDir }, credentialsDeps: { configDir: tempDir }, log, checkHealth: async () => true, }); await cmd.parseAsync(['-o', 'json'], { from: 'user' }); const parsed = JSON.parse(output[0]) as Record; expect(parsed['llm']).toBeNull(); }); });