Files
claude-hub/test/unit/services/claudeService.test.js
Jonathan Flatt 9cac28bdff test: add more mocks and fix unit tests
- Add mock for secureCredentials
- Add mock for logger
- Add mock for child_process
- Fix claudeService.test.js to use proper mocks
- Ensure all mocks use clearly fake test credentials
2025-05-28 17:36:26 -05:00

245 lines
7.4 KiB
JavaScript

// Set up environment variables before requiring modules
process.env.BOT_USERNAME = '@TestBot';
process.env.NODE_ENV = 'test';
process.env.GITHUB_TOKEN = 'ghp_test_token'; // Use token format that passes validation
// Mock dependencies
jest.mock('child_process', () => ({
execFileSync: jest.fn().mockReturnValue('mocked output'),
execFile: jest.fn(),
exec: jest.fn()
}));
jest.mock('util', () => ({
promisify: jest.fn(fn => {
if (fn.name === 'execFile') {
return jest.fn().mockResolvedValue({
stdout: 'Claude response from container',
stderr: ''
});
}
return fn;
})
}));
jest.mock('fs', () => ({
writeFileSync: jest.fn(),
unlinkSync: jest.fn()
}));
jest.mock('../../../src/utils/logger', () => ({
createLogger: () => ({
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn()
})
}));
jest.mock('../../../src/utils/sanitize', () => ({
sanitizeBotMentions: jest.fn(input => input)
}));
jest.mock('../../../src/utils/secureCredentials');
jest.mock('../../../src/utils/awsCredentialProvider');
jest.mock('../../../src/utils/startup-metrics');
// Now require the module under test
const { execFileSync } = require('child_process');
const { promisify } = require('util');
const { sanitizeBotMentions } = require('../../../src/utils/sanitize');
const claudeService =
require('../../../src/services/claudeService').default ||
require('../../../src/services/claudeService');
describe('Claude Service', () => {
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();
});
test('processCommand should handle test mode correctly', async () => {
// Force test mode
process.env.NODE_ENV = 'test';
const result = await claudeService.processCommand({
repoFullName: 'test/repo',
issueNumber: 123,
command: 'Test command',
isPullRequest: false
});
// Verify test mode response
expect(result).toContain('Hello! I\'m Claude responding to your request.');
expect(result).toContain('test/repo');
expect(sanitizeBotMentions).toHaveBeenCalled();
// Verify no Docker commands were executed
expect(execFileSync).not.toHaveBeenCalled();
});
test('processCommand should properly set up Docker command in production mode', async () => {
// Mock for this test only
const originalProcessCommand = claudeService.processCommand;
// Override the actual function with a test implementation
claudeService.processCommand = async options => {
// Set production mode for this function
const originalNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
// Mock dependencies needed in production mode
execFileSync.mockImplementation((cmd, args, _options) => {
if (args[0] === 'inspect') return '{}';
return 'mocked output';
});
// Configure execFileAsync mock
const execFileAsync = promisify(require('child_process').execFile);
execFileAsync.mockResolvedValue({
stdout: 'Claude response from container',
stderr: ''
});
// Call the original implementation to test it
const result = await originalProcessCommand(options);
// Restore env
process.env.NODE_ENV = originalNodeEnv;
return result;
};
try {
// Call the overridden function
await claudeService.processCommand({
repoFullName: 'test/repo',
issueNumber: 123,
command: 'Test command',
isPullRequest: false
});
// Our assertions happen in the override function
// We just need to verify the execFileSync was called
expect(execFileSync).toHaveBeenCalled();
} finally {
// Restore the original function
claudeService.processCommand = originalProcessCommand;
}
});
test('processCommand should handle errors properly', async () => {
// Save original function for restoration
const originalProcessCommand = claudeService.processCommand;
// Create a testing implementation
claudeService.processCommand = async options => {
// Set test environment variables
const originalNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
// Mock the Docker inspect to succeed
execFileSync.mockImplementation((cmd, args, _options) => {
if (args[0] === 'inspect') return '{}';
if (args[0] === 'logs') return 'error logs';
if (args[0] === 'kill') return '';
return 'mocked output';
});
// Mock execFileAsync to throw an error
const execFileAsync = promisify(require('child_process').execFile);
execFileAsync.mockRejectedValue({
message: 'Docker execution failed',
stderr: 'Error: container exited with non-zero status',
stdout: ''
});
// Throw error from original implementation
await originalProcessCommand(options);
// Restore environment
process.env.NODE_ENV = originalNodeEnv;
};
try {
// Call the function and expect it to throw
await expect(
claudeService.processCommand({
repoFullName: 'test/repo',
issueNumber: 123,
command: 'Test command',
isPullRequest: false
})
).rejects.toThrow();
// Verify execFileSync was called
expect(execFileSync).toHaveBeenCalled();
} finally {
// Restore original function
claudeService.processCommand = originalProcessCommand;
}
});
test('processCommand should handle long commands properly', async () => {
// Save original function for restoration
const originalProcessCommand = claudeService.processCommand;
// Create a testing implementation that checks for long command handling
claudeService.processCommand = async options => {
// Set up test environment
const originalNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
// Mock the Docker inspect to succeed
execFileSync.mockImplementation((cmd, args, _options) => {
if (args[0] === 'inspect') return '{}';
return 'mocked output';
});
// Configure execFileAsync mock
const execFileAsync = promisify(require('child_process').execFile);
execFileAsync.mockResolvedValue({
stdout: 'Claude response for long command',
stderr: ''
});
// Call the original implementation
const result = await originalProcessCommand(options);
// Verify Docker was called with the command as an environment variable
expect(execFileAsync).toHaveBeenCalled();
const dockerArgs = execFileAsync.mock.calls[0][1];
// Check that COMMAND env var is present in the docker args
// The format is ['-e', 'COMMAND=value']
const commandEnvIndex = dockerArgs.findIndex(
arg => typeof arg === 'string' && arg.startsWith('COMMAND=')
);
expect(commandEnvIndex).toBeGreaterThan(-1);
// Restore environment
process.env.NODE_ENV = originalNodeEnv;
return result;
};
try {
// Call the function with a long command
const longCommand = 'A'.repeat(1000);
const result = await claudeService.processCommand({
repoFullName: 'test/repo',
issueNumber: 123,
command: longCommand,
isPullRequest: false
});
// Verify we got a response
expect(result).toBe('Claude response for long command');
} finally {
// Restore original function
claudeService.processCommand = originalProcessCommand;
}
});
});