From d893fc5ede2869be961e6d9e2619186f2cc2b2b4 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 30 Jan 2026 14:08:33 +0100 Subject: [PATCH] feat(01.1-06): update CLI status command and add integration tests - Update status command to use tRPC client for server communication - Display health status, uptime, and process count - Handle connection errors gracefully with helpful message - Add 7 integration tests proving full CLI-server tRPC flow - Tests cover health procedure, status procedure, and error handling --- src/cli/index.ts | 18 +++- tests/integration/cli-server.test.ts | 123 +++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 tests/integration/cli-server.test.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index 06a9c5e..19c6214 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -11,6 +11,7 @@ import { CoordinationServer } from '../server/index.js'; import { GracefulShutdown } from '../server/shutdown.js'; import { ProcessManager, ProcessRegistry } from '../process/index.js'; import { LogManager } from '../logging/index.js'; +import { createDefaultTrpcClient } from './trpc-client.js'; /** Environment variable for custom port */ const CW_PORT_ENV = 'CW_PORT'; @@ -77,12 +78,23 @@ export function createCli(serverHandler?: (port?: number) => Promise): Com } }); - // Placeholder commands - will be implemented in later phases + // Status command - shows workspace status via tRPC program .command('status') .description('Show workspace status') - .action(() => { - console.log('cw status: not implemented'); + .action(async () => { + try { + const client = createDefaultTrpcClient(); + const health = await client.health.query(); + + console.log('Coordination Server Status'); + console.log('=========================='); + console.log(`Status: ${health.status}`); + console.log(`Uptime: ${health.uptime}s`); + console.log(`Processes: ${health.processCount}`); + } catch { + console.log('Server not running or unreachable. Start with: cw --server'); + } }); program diff --git a/tests/integration/cli-server.test.ts b/tests/integration/cli-server.test.ts new file mode 100644 index 0000000..b96abb6 --- /dev/null +++ b/tests/integration/cli-server.test.ts @@ -0,0 +1,123 @@ +/** + * CLI-Server Integration Tests + * + * Tests the full flow: start server, call tRPC from client, verify response. + * These are INTEGRATION tests, not unit tests - they test the full stack. + */ + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { CoordinationServer } from '../../src/server/index.js'; +import { ProcessManager, ProcessRegistry } from '../../src/process/index.js'; +import { LogManager } from '../../src/logging/index.js'; +import { createEventBus } from '../../src/events/index.js'; +import { createTrpcClient } from '../../src/cli/trpc-client.js'; +import type { TrpcClient } from '../../src/cli/trpc-client.js'; + +describe('CLI-Server Integration', () => { + let server: CoordinationServer; + let client: TrpcClient; + let testPort: number; + + beforeAll(async () => { + // Use a random port to avoid conflicts + testPort = 30000 + Math.floor(Math.random() * 10000); + + // Create dependencies + const registry = new ProcessRegistry(); + const processManager = new ProcessManager(registry); + const logManager = new LogManager(); + const eventBus = createEventBus(); + + // Create and start server + server = new CoordinationServer( + { + port: testPort, + pidFile: `/tmp/cw-test-${testPort}.pid`, + }, + processManager, + logManager, + eventBus + ); + + await server.start(); + + // Create client pointing to test server + client = createTrpcClient(testPort); + }); + + afterAll(async () => { + await server.stop(); + }); + + describe('health procedure', () => { + it('should return health status with correct fields', async () => { + const result = await client.health.query(); + + expect(result).toHaveProperty('status', 'ok'); + expect(result).toHaveProperty('uptime'); + expect(result).toHaveProperty('processCount'); + expect(typeof result.uptime).toBe('number'); + expect(typeof result.processCount).toBe('number'); + }); + + it('should return increasing uptime on subsequent calls', async () => { + const first = await client.health.query(); + + // Wait a small amount + await new Promise((resolve) => setTimeout(resolve, 50)); + + const second = await client.health.query(); + + expect(second.uptime).toBeGreaterThanOrEqual(first.uptime); + }); + + it('should return processCount of 0 initially', async () => { + const result = await client.health.query(); + expect(result.processCount).toBe(0); + }); + }); + + describe('status procedure', () => { + it('should return server info with correct structure', async () => { + const result = await client.status.query(); + + expect(result).toHaveProperty('server'); + expect(result).toHaveProperty('processes'); + expect(Array.isArray(result.processes)).toBe(true); + }); + + it('should return server details with startedAt, uptime, and pid', async () => { + const result = await client.status.query(); + + expect(result.server).toHaveProperty('startedAt'); + expect(result.server).toHaveProperty('uptime'); + expect(result.server).toHaveProperty('pid'); + + // startedAt should be an ISO string + expect(typeof result.server.startedAt).toBe('string'); + expect(() => new Date(result.server.startedAt)).not.toThrow(); + + // uptime should be a non-negative number + expect(typeof result.server.uptime).toBe('number'); + expect(result.server.uptime).toBeGreaterThanOrEqual(0); + + // pid should be a positive integer + expect(typeof result.server.pid).toBe('number'); + expect(result.server.pid).toBeGreaterThan(0); + }); + + it('should return empty processes array initially', async () => { + const result = await client.status.query(); + expect(result.processes).toEqual([]); + }); + }); + + describe('error handling', () => { + it('should throw when server is not running', async () => { + // Create client pointing to wrong port + const badClient = createTrpcClient(testPort + 1); + + await expect(badClient.health.query()).rejects.toThrow(); + }); + }); +});