Files
claude-hub/test/unit/providers/claude/handlers/OrchestrationHandler.test.ts
Cheffromspace bf2a517264 feat: Implement Claude orchestration provider for parallel session management (#171)
* feat: Implement Claude orchestration provider for parallel session management

- Add ClaudeWebhookProvider implementing the webhook provider interface
- Create orchestration system for running multiple Claude containers in parallel
- Implement smart task decomposition to break complex projects into workstreams
- Add session management with dependency tracking between sessions
- Support multiple execution strategies (parallel, sequential, wait_for_core)
- Create comprehensive test suite for all components
- Add documentation for Claude orchestration API and usage

This enables super-charged Claude capabilities for the MCP hackathon by allowing
multiple Claude instances to work on different aspects of a project simultaneously,
with intelligent coordination and result aggregation.

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

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

* feat: Add session management endpoints for MCP integration

- Add SessionHandler for individual session CRUD operations
- Create endpoints: session.create, session.get, session.list, session.start, session.output
- Fix Claude invocation in Docker containers using proper claude chat command
- Add volume mounts for persistent storage across session lifecycle
- Simplify OrchestrationHandler to create single coordination sessions
- Update documentation with comprehensive MCP integration examples
- Add comprehensive unit and integration tests for new endpoints
- Support dependencies and automatic session queuing/starting

This enables Claude Desktop to orchestrate multiple Claude Code sessions via MCP Server tools.

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

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

* fix: Update ClaudeWebhookProvider validation for session endpoints

- Make project fields optional for session management operations
- Add validation for session.create requiring session field
- Update tests to match new validation rules
- Fix failing CI tests

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

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

* fix: Use Promise.reject for validation errors in parsePayload

- Convert synchronous throws to Promise.reject for async consistency
- Fixes failing unit tests expecting rejected promises

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

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

* fix: Mock SessionManager in integration tests to avoid Docker calls in CI

- Add SessionManager mock to prevent Docker operations during tests
- Fix claude-webhook.test.ts to use proper test setup and payload structure
- Ensure all integration tests can run without Docker dependency
- Fix payload structure to include 'data' wrapper

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

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

* fix: Mock child_process to prevent Docker calls in CI tests

- Mock execSync and spawn at child_process level to prevent any Docker commands
- This ensures tests work in CI environment without Docker
- Tests now pass both locally and in CI Docker build

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

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

* fix: Address PR review comments and fix linter warnings

- Move @types/uuid to devDependencies
- Replace timestamp+Math.random with crypto.randomUUID() for better uniqueness
- Extract magic number into EXTRA_SESSIONS_COUNT constant
- Update determineStrategy return type to use literal union
- Fix unnecessary optional chaining warnings
- Handle undefined labels in GitHub transformers
- Make TaskDecomposer.decompose synchronous
- Add proper eslint-disable comments for intentional sync methods
- Fix all TypeScript and formatting issues

* fix: Mock SessionManager in integration tests to prevent Docker calls in CI

- Add SessionManager mocks to claude-session.test.ts
- Add SessionManager mocks to claude-webhook.test.ts
- Prevents 500 errors when running tests in CI without Docker
- All integration tests now pass without requiring Docker runtime

* fix: Run only unit tests in Docker builds to avoid Docker-in-Docker issues

- Change test stage to run 'npm run test:unit' instead of 'npm test'
- Skips integration tests that require Docker runtime
- Prevents CI failures in Docker container builds
- Integration tests still run in regular CI workflow

* fix: Use Dockerfile CMD for tests in Docker build CI

- Remove explicit 'npm test' command from docker run
- Let Docker use the CMD defined in Dockerfile (npm run test:unit)
- This ensures consistency and runs only unit tests in Docker builds

---------

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

187 lines
5.7 KiB
TypeScript

import { OrchestrationHandler } from '../../../../../src/providers/claude/handlers/OrchestrationHandler';
import type { SessionManager } from '../../../../../src/providers/claude/services/SessionManager';
import type { ClaudeWebhookPayload } from '../../../../../src/providers/claude/ClaudeWebhookProvider';
import type { WebhookContext } from '../../../../../src/types/webhook';
// Mock the services
jest.mock('../../../../../src/providers/claude/services/SessionManager');
describe('OrchestrationHandler', () => {
let handler: OrchestrationHandler;
let mockSessionManager: jest.Mocked<SessionManager>;
let mockContext: WebhookContext;
beforeEach(() => {
jest.clearAllMocks();
handler = new OrchestrationHandler();
mockSessionManager = (handler as any).sessionManager;
mockContext = {
provider: 'claude',
timestamp: new Date()
};
});
describe('canHandle', () => {
it('should handle orchestrate events', () => {
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
project: {
repository: 'owner/repo',
requirements: 'Build API'
}
},
metadata: {}
};
expect(handler.canHandle(payload)).toBe(true);
});
it('should not handle session events', () => {
const payload: ClaudeWebhookPayload = {
data: {
type: 'session.create',
project: {
repository: 'owner/repo',
requirements: 'Manage session'
}
} as any,
metadata: {}
};
expect(handler.canHandle(payload)).toBe(false);
});
});
describe('handle', () => {
it('should create orchestration session and start it by default', async () => {
mockSessionManager.createContainer.mockResolvedValue('container-123');
mockSessionManager.startSession.mockResolvedValue();
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
project: {
repository: 'owner/repo',
requirements: 'Build a REST API with authentication'
}
},
metadata: {}
};
const response = await handler.handle(payload, mockContext);
expect(response.success).toBe(true);
expect(response.message).toBe('Orchestration session created');
expect(response.data).toMatchObject({
status: 'initiated',
summary: 'Created orchestration session for owner/repo'
});
// Verify session creation
const createdSession = mockSessionManager.createContainer.mock.calls[0][0];
expect(createdSession).toMatchObject({
type: 'coordination',
status: 'pending',
project: {
repository: 'owner/repo',
requirements: 'Build a REST API with authentication'
},
dependencies: []
});
// Verify session was started
expect(mockSessionManager.startSession).toHaveBeenCalled();
});
it('should use custom session type when provided', async () => {
mockSessionManager.createContainer.mockResolvedValue('container-123');
mockSessionManager.startSession.mockResolvedValue();
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
sessionType: 'analysis',
project: {
repository: 'owner/repo',
requirements: 'Analyze codebase structure'
}
} as any,
metadata: {}
};
const response = await handler.handle(payload, mockContext);
expect(response.success).toBe(true);
const createdSession = mockSessionManager.createContainer.mock.calls[0][0];
expect(createdSession.type).toBe('analysis');
});
it('should not start session when autoStart is false', async () => {
mockSessionManager.createContainer.mockResolvedValue('container-123');
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
autoStart: false,
project: {
repository: 'owner/repo',
requirements: 'Build API'
}
} as any,
metadata: {}
};
const response = await handler.handle(payload, mockContext);
expect(response.success).toBe(true);
expect(mockSessionManager.createContainer).toHaveBeenCalled();
expect(mockSessionManager.startSession).not.toHaveBeenCalled();
});
it('should handle errors gracefully', async () => {
mockSessionManager.createContainer.mockRejectedValue(new Error('Docker error'));
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
project: {
repository: 'owner/repo',
requirements: 'Build API'
}
},
metadata: {}
};
const response = await handler.handle(payload, mockContext);
expect(response.success).toBe(false);
expect(response.error).toBe('Docker error');
});
it('should generate unique orchestration IDs', async () => {
mockSessionManager.createContainer.mockResolvedValue('container-123');
const payload: ClaudeWebhookPayload = {
data: {
type: 'orchestrate',
autoStart: false,
project: {
repository: 'owner/repo',
requirements: 'Build API'
}
} as any,
metadata: {}
};
const response1 = await handler.handle(payload, mockContext);
const response2 = await handler.handle(payload, mockContext);
expect(response1.data?.orchestrationId).toBeDefined();
expect(response2.data?.orchestrationId).toBeDefined();
expect(response1.data?.orchestrationId).not.toBe(response2.data?.orchestrationId);
});
});
});