Files
claude-hub/test/unit/providers/github/handlers/IssueHandler.test.ts
Jonathan Flatt 346199ebbd feat: Implement combined test coverage for main project and CLI
- Add combined coverage script to merge lcov reports
- Update GitHub workflows to generate and upload combined coverage
- Install missing CLI dependencies (ora, yaml, cli-table3, mock-fs)
- Add initial tests for SessionManager and IssueHandler
- Exclude type-only files from coverage metrics
- Update jest config to exclude type files from coverage

This ensures Codecov receives coverage data from both the main project
and CLI subdirectory, providing accurate overall project coverage metrics.
2025-06-03 22:43:20 +00:00

132 lines
3.8 KiB
TypeScript

import { IssueHandler } from '../../../../../src/providers/github/handlers/IssueHandler';
import { WebhookProcessor } from '../../../../../src/core/webhook/WebhookProcessor';
import type { IssuesEvent } from '@octokit/webhooks-types';
// Mock dependencies
jest.mock('../../../../../src/utils/logger', () => ({
createLogger: () => ({
info: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
warn: jest.fn()
})
}));
jest.mock('../../../../../src/utils/secureCredentials', () => ({
SecureCredentials: jest.fn().mockImplementation(() => ({
loadCredentials: jest.fn(),
getCredential: jest.fn().mockReturnValue('mock-value')
})),
secureCredentials: {
loadCredentials: jest.fn(),
getCredential: jest.fn().mockReturnValue('mock-value')
}
}));
jest.mock('../../../../../src/services/claudeService');
jest.mock('../../../../../src/services/githubService');
const claudeService = require('../../../../../src/services/claudeService');
const githubService = require('../../../../../src/services/githubService');
describe('IssueHandler', () => {
let handler: IssueHandler;
let processor: WebhookProcessor;
beforeEach(() => {
jest.clearAllMocks();
handler = new IssueHandler();
processor = new WebhookProcessor({
webhookPath: '/test',
secret: 'test-secret'
});
});
describe('handleIssue', () => {
const mockEvent: IssuesEvent = {
action: 'opened',
issue: {
id: 123,
number: 1,
title: 'Test Issue',
body: 'This is a test issue about authentication and API integration',
labels: [],
state: 'open',
user: {
login: 'testuser',
id: 1
},
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
repository: {
id: 456,
name: 'test-repo',
full_name: 'owner/test-repo',
owner: {
login: 'owner',
id: 2
},
private: false
},
sender: {
login: 'testuser',
id: 1
}
} as any;
it('should analyze and label new issues', async () => {
githubService.addLabelsToIssue = jest.fn().mockResolvedValue(undefined);
claudeService.analyzeIssueForLabels = jest.fn().mockResolvedValue({
priority: 'medium',
type: 'feature',
complexity: 'moderate',
component: 'api,auth'
});
await handler.handleIssue(mockEvent, processor);
expect(claudeService.analyzeIssueForLabels).toHaveBeenCalledWith(
'owner/test-repo',
1,
'Test Issue',
'This is a test issue about authentication and API integration'
);
expect(githubService.addLabelsToIssue).toHaveBeenCalledWith('owner/test-repo', 1, [
'priority:medium',
'type:feature',
'complexity:moderate',
'component:api',
'component:auth'
]);
});
it('should handle errors gracefully', async () => {
claudeService.analyzeIssueForLabels = jest
.fn()
.mockRejectedValue(new Error('Analysis failed'));
await expect(handler.handleIssue(mockEvent, processor)).resolves.not.toThrow();
});
it('should skip non-opened events', async () => {
const editEvent = { ...mockEvent, action: 'edited' } as IssuesEvent;
await handler.handleIssue(editEvent, processor);
expect(claudeService.analyzeIssueForLabels).not.toHaveBeenCalled();
expect(githubService.addLabelsToIssue).not.toHaveBeenCalled();
});
it('should handle empty label analysis', async () => {
claudeService.analyzeIssueForLabels = jest.fn().mockResolvedValue({});
githubService.addLabelsToIssue = jest.fn();
await handler.handleIssue(mockEvent, processor);
expect(githubService.addLabelsToIssue).toHaveBeenCalledWith('owner/test-repo', 1, []);
});
});
});