Files
claude-hub/cli/__tests__/utils/dockerUtils.simple.test.ts
Cheffromspace f0edb5695f feat: Add CLI for managing autonomous Claude Code container sessions (#166)
* feat: Add CLI for managing autonomous Claude Code container sessions

This commit implements a new CLI tool 'claude-hub' for managing autonomous Claude Code container sessions. The CLI provides commands for:

- Starting autonomous sessions (start)
- Listing active/completed sessions (list)
- Viewing session logs (logs)
- Continuing sessions with new commands (continue)
- Stopping sessions (stop)

Each session runs in an isolated Docker container and maintains its state across interactions. The implementation includes session management, Docker container operations, and a comprehensive command-line interface.

Resolves #133

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: Complete autonomous CLI feature implementation

This commit adds the following enhancements to the autonomous Claude CLI:

- Add --issue flag to start command for GitHub issue context
- Implement start-batch command with tasks.yaml support
- Enhance PR flag functionality for better context integration
- Implement session recovery mechanism with recover and sync commands
- Add comprehensive documentation for all CLI commands

Resolves all remaining requirements from issue #133

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: Add comprehensive test coverage for CLI

- Add unit tests for SessionManager utility
- Add simplified unit tests for DockerUtils utility
- Add integration tests for start and start-batch commands
- Configure Jest with TypeScript support
- Add test mocks for Docker API and filesystem
- Add test fixtures for batch processing
- Document testing approach in README
- Add code coverage reporting

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* ci: Add CLI tests workflow and configure stable test suite

- Create dedicated GitHub workflow for CLI tests
- Update CLI test script to run only stable tests
- Add test:all script for running all tests locally

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Improve CLI with TypeScript fixes and CI enhancements

- Fix TypeScript Promise handling in list.ts and stop.ts
- Update CI workflow to add build step and run all tests
- Move ora dependency from devDependencies to dependencies
- Update Docker build path to use repository root
- Improve CLI script organization in package.json

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Skip Docker-dependent tests in CI

- Update test scripts to exclude dockerUtils tests
- Add SKIP_DOCKER_TESTS environment variable to CI workflow
- Remove dockerUtils.simple.test.ts from specific tests

This prevents timeouts in CI caused by Docker tests.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Refine test patterns to exclude only full Docker tests

- Replace testPathIgnorePatterns with more precise glob patterns
- Ensure dockerUtils.simple.test.ts is still included in the test runs
- Keep specific tests command with all relevant tests

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Update Jest test patterns to correctly match test files

The previous glob pattern '__tests__/\!(utils/dockerUtils.test).ts' was not finding any tests because it was looking for .ts files directly in the __tests__ folder, but all test files are in subdirectories. Fixed by using Jest's testPathIgnorePatterns option instead.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: Add tests for CLI list and continue commands

Added comprehensive test coverage for the CLI list and continue commands:
- Added list.test.ts with tests for all filtering options and edge cases
- Added continue.test.ts with tests for successful continuation and error cases
- Both files achieve full coverage of their respective commands

These new tests help improve the overall test coverage for the CLI commands module.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: Add comprehensive tests for CLI logs, recover, and stop commands

Added test coverage for remaining CLI commands:
- logs.test.ts - tests for logs command functionality (94.54% coverage)
- recover.test.ts - tests for recover and sync commands (100% coverage)
- stop.test.ts - tests for stop command with single and all sessions (95.71% coverage)

These tests dramatically improve the overall commands module coverage from 56% to 97%.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: Align PR review prompt header with test expectations

The PR review prompt header in githubController.ts now matches what the test expects in
githubController-check-suite.test.js, fixing the failing test.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-02 12:03:20 -05:00

137 lines
4.7 KiB
TypeScript

import { DockerUtils } from '../../src/utils/dockerUtils';
import { promisify } from 'util';
// Mock the child_process module
jest.mock('child_process', () => ({
exec: jest.fn(),
execFile: jest.fn(),
spawn: jest.fn(() => ({
stdout: { pipe: jest.fn() },
stderr: { pipe: jest.fn() },
on: jest.fn()
}))
}));
// Mock promisify to return our mocked exec/execFile functions
jest.mock('util', () => ({
promisify: jest.fn((fn) => fn)
}));
describe('DockerUtils - Simple Tests', () => {
let dockerUtils: DockerUtils;
const mockExec = require('child_process').exec;
const mockExecFile = require('child_process').execFile;
beforeEach(() => {
jest.clearAllMocks();
// Setup mock implementations
mockExec.mockImplementation((command: string, callback?: (error: Error | null, result: {stdout: string, stderr: string}) => void) => {
if (callback) callback(null, { stdout: 'Mock exec output', stderr: '' });
return Promise.resolve({ stdout: 'Mock exec output', stderr: '' });
});
mockExecFile.mockImplementation((file: string, args: string[], options?: any, callback?: (error: Error | null, result: {stdout: string, stderr: string}) => void) => {
if (callback) callback(null, { stdout: 'Mock execFile output', stderr: '' });
return Promise.resolve({ stdout: 'Mock execFile output', stderr: '' });
});
// Create a new instance for each test
dockerUtils = new DockerUtils();
});
describe('isDockerAvailable', () => {
it('should check if Docker is available', async () => {
mockExec.mockResolvedValueOnce({ stdout: 'Docker version 20.10.7', stderr: '' });
const result = await dockerUtils.isDockerAvailable();
expect(result).toBe(true);
expect(mockExec).toHaveBeenCalledWith('docker --version');
});
it('should return false if Docker is not available', async () => {
mockExec.mockRejectedValueOnce(new Error('Docker not found'));
const result = await dockerUtils.isDockerAvailable();
expect(result).toBe(false);
expect(mockExec).toHaveBeenCalledWith('docker --version');
});
});
describe('doesImageExist', () => {
it('should check if the Docker image exists', async () => {
mockExecFile.mockResolvedValueOnce({ stdout: 'Image exists', stderr: '' });
const result = await dockerUtils.doesImageExist();
expect(result).toBe(true);
expect(mockExecFile).toHaveBeenCalledWith('docker', ['inspect', expect.any(String)]);
});
it('should return false if the Docker image does not exist', async () => {
mockExecFile.mockRejectedValueOnce(new Error('No such image'));
const result = await dockerUtils.doesImageExist();
expect(result).toBe(false);
expect(mockExecFile).toHaveBeenCalledWith('docker', ['inspect', expect.any(String)]);
});
});
describe('startContainer', () => {
it('should start a Docker container', async () => {
mockExecFile.mockResolvedValueOnce({ stdout: 'container-id', stderr: '' });
const result = await dockerUtils.startContainer(
'test-container',
{ REPO_FULL_NAME: 'owner/repo', COMMAND: 'test command' }
);
expect(result).toBe('container-id');
expect(mockExecFile).toHaveBeenCalled();
});
it('should return null if container start fails', async () => {
mockExecFile.mockRejectedValueOnce(new Error('Failed to start container'));
const result = await dockerUtils.startContainer(
'test-container',
{ REPO_FULL_NAME: 'owner/repo', COMMAND: 'test command' }
);
expect(result).toBeNull();
expect(mockExecFile).toHaveBeenCalled();
});
});
describe('stopContainer', () => {
it('should stop a container', async () => {
mockExecFile.mockResolvedValueOnce({ stdout: '', stderr: '' });
const result = await dockerUtils.stopContainer('container-id');
expect(result).toBe(true);
expect(mockExecFile).toHaveBeenCalledWith('docker', ['stop', 'container-id']);
});
it('should kill a container when force is true', async () => {
mockExecFile.mockResolvedValueOnce({ stdout: '', stderr: '' });
const result = await dockerUtils.stopContainer('container-id', true);
expect(result).toBe(true);
expect(mockExecFile).toHaveBeenCalledWith('docker', ['kill', 'container-id']);
});
it('should return false if container stop fails', async () => {
mockExecFile.mockRejectedValueOnce(new Error('Failed to stop container'));
const result = await dockerUtils.stopContainer('container-id');
expect(result).toBe(false);
expect(mockExecFile).toHaveBeenCalled();
});
});
});