Files
claude-hub/test/unit/routes/github.test.ts
Cheffromspace 12e4589169 Fix: Merge entrypoint scripts and fix auto-tagging tool permissions (#146)
* fix: merge entrypoint scripts and fix auto-tagging tool permissions

- Merged duplicate claudecode-entrypoint.sh and claudecode-tagging-entrypoint.sh scripts
- Added dynamic tool selection based on OPERATION_TYPE environment variable
- Fixed auto-tagging permissions to include required Bash(gh:*) commands
- Removed 95% code duplication between entrypoint scripts
- Simplified claudeService.ts to use unified entrypoint
- Auto-tagging now uses: Read,GitHub,Bash(gh issue edit:*),Bash(gh issue view:*),Bash(gh label list:*)
- General operations continue to use full tool set

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

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

* fix: update Dockerfile to use unified entrypoint script

- Remove references to deleted claudecode-tagging-entrypoint.sh
- Update build process to use single unified entrypoint script

* fix: remove unnecessary async from promisify mock to fix lint error

* feat: add Husky pre-commit hooks with Prettier as primary formatter

- Added Husky for Git pre-commit hooks
- Configured eslint-config-prettier to avoid ESLint/Prettier conflicts
- Prettier handles all formatting, ESLint handles code quality only
- Pre-commit hooks: Prettier format, ESLint check, TypeScript check
- Updated documentation with pre-commit hook setup
- All code quality issues resolved

* feat: consolidate workflows and fix permission issues with clean Docker runners

- Replace 3 complex workflows with 2 lean ones (pull-request.yml, main.yml)
- Add Docker runner configuration for clean, isolated builds
- Remove file permission hacks - use ephemeral containers instead
- Split workload: GitHub-hosted for tests/security, self-hosted for Docker builds
- Add comprehensive pre-commit configuration for security
- Update documentation to be more pragmatic
- Fix credential file permissions and security audit

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

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

* fix: allow Husky prepare script to fail in production builds

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

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

* fix: update CI badge to reference new main.yml workflow

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-05-31 20:53:58 -05:00

136 lines
4.4 KiB
TypeScript

import request from 'supertest';
import express from 'express';
import type { Request, Response } from 'express';
// Mock the controller before importing the router
jest.mock('../../../src/controllers/githubController');
const mockHandleWebhook = jest.fn<(req: Request, res: Response) => void>();
jest.mocked(require('../../../src/controllers/githubController')).handleWebhook = mockHandleWebhook;
// Import router after mocks are set up
import githubRouter from '../../../src/routes/github';
describe('GitHub Routes', () => {
let app: express.Application;
beforeEach(() => {
jest.clearAllMocks();
app = express();
app.use(express.json());
app.use('/api/webhooks/github', githubRouter);
});
describe('POST /api/webhooks/github', () => {
it('should route webhook requests to the controller', async () => {
mockHandleWebhook.mockImplementation((_req: Request, res: Response) => {
res.status(200).json({ message: 'Webhook processed' });
});
const webhookPayload = {
action: 'opened',
issue: {
number: 123,
title: 'Test issue'
}
};
const response = await request(app)
.post('/api/webhooks/github')
.send(webhookPayload)
.set('X-GitHub-Event', 'issues')
.set('X-GitHub-Delivery', 'test-delivery-id');
expect(response.status).toBe(200);
expect(response.body).toEqual({ message: 'Webhook processed' });
expect(mockHandleWebhook).toHaveBeenCalledTimes(1);
expect(mockHandleWebhook).toHaveBeenCalledWith(
expect.objectContaining({
body: webhookPayload,
headers: expect.objectContaining({
'x-github-event': 'issues',
'x-github-delivery': 'test-delivery-id'
})
}),
expect.any(Object),
expect.any(Function)
);
});
it('should handle controller errors', async () => {
mockHandleWebhook.mockImplementation((_req: Request, res: Response) => {
res.status(500).json({ error: 'Internal server error' });
});
const response = await request(app).post('/api/webhooks/github').send({ test: 'data' });
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Internal server error' });
});
it('should pass through all HTTP methods to controller', async () => {
mockHandleWebhook.mockImplementation((_req: Request, res: Response) => {
res.status(200).send('OK');
});
// The router only defines POST, so other methods should return 404
const getResponse = await request(app).get('/api/webhooks/github');
expect(getResponse.status).toBe(404);
expect(mockHandleWebhook).not.toHaveBeenCalled();
// POST should work
jest.clearAllMocks();
const postResponse = await request(app).post('/api/webhooks/github').send({});
expect(postResponse.status).toBe(200);
expect(mockHandleWebhook).toHaveBeenCalledTimes(1);
});
it('should handle different content types', async () => {
mockHandleWebhook.mockImplementation((req: Request, res: Response) => {
res.status(200).json({
contentType: req.get('content-type'),
body: req.body
});
});
// Test with JSON
const jsonResponse = await request(app)
.post('/api/webhooks/github')
.send({ type: 'json' })
.set('Content-Type', 'application/json');
expect(jsonResponse.status).toBe(200);
expect(jsonResponse.body.contentType).toBe('application/json');
// Test with form data
const formResponse = await request(app)
.post('/api/webhooks/github')
.send('type=form')
.set('Content-Type', 'application/x-www-form-urlencoded');
expect(formResponse.status).toBe(200);
});
it('should preserve raw body for signature verification', async () => {
mockHandleWebhook.mockImplementation((req: Request, res: Response) => {
// Check if rawBody is available (would be set by body parser in main app)
res.status(200).json({
hasRawBody: 'rawBody' in req,
bodyType: typeof req.body
});
});
const response = await request(app)
.post('/api/webhooks/github')
.send({ test: 'data' })
.set('X-Hub-Signature-256', 'sha256=test');
expect(response.status).toBe(200);
expect(mockHandleWebhook).toHaveBeenCalled();
});
});
});