Files
kekbot.js/__tests__/commands/ask.test.js
T
2025-12-25 10:28:44 -05:00

261 lines
7.4 KiB
JavaScript

const { createMockInteraction } = require('../utils/testUtils');
// Mock axios
jest.mock('axios', () => ({
__esModule: true,
default: {
post: jest.fn(),
},
}));
const axios = require('axios').default;
// Mock discord.js
jest.mock('discord.js', () => ({
SlashCommandBuilder: jest.fn().mockReturnValue({
setName: jest.fn().mockReturnThis(),
setDescription: jest.fn().mockReturnThis(),
addStringOption: jest.fn().mockImplementation(callback => {
const option = {
setName: jest.fn().mockReturnThis(),
setDescription: jest.fn().mockReturnThis(),
setRequired: jest.fn().mockReturnThis(),
};
callback(option);
return {
toJSON: jest.fn().mockReturnValue({
name: 'ask',
description: 'Ask a question to the AI',
options: [
{
name: 'prompt',
description: 'Your question or prompt',
type: 3,
required: true,
},
],
}),
};
}),
toJSON: jest.fn(),
}),
}));
const AskCommand = require('../../commands/ask').default;
const askCommand = new AskCommand();
describe('Ask Command', () => {
describe('Command Structure', () => {
it('should have correct name and description', () => {
const commandData = askCommand.defineCommand().toJSON();
expect(commandData.name).toBe('ask');
expect(commandData.description).toBe('Ask a question to the AI');
});
it('should have required command properties', () => {
expect(askCommand).toHaveProperty('defineCommand');
expect(askCommand).toHaveProperty('run');
expect(typeof askCommand.run).toBe('function');
});
it('should have correct option configuration', () => {
const commandData = askCommand.defineCommand().toJSON();
const [promptOption] = commandData.options;
expect(promptOption.name).toBe('prompt');
expect(promptOption.description).toBe('Your question or prompt');
expect(promptOption.required).toBe(true);
});
});
describe('Command Execution', () => {
let interaction;
const mockPrompt = 'What is the meaning of life?';
const mockApiResponse = {
data: {
choices: [
{
message: {
content: 'The meaning of life is 42.',
},
},
],
},
};
beforeEach(() => {
process.env.NANOGPT_API_KEY = 'test-api-key';
jest.clearAllMocks();
interaction = createMockInteraction({
commandName: 'ask',
stringOptions: {
prompt: mockPrompt,
},
});
});
it('should handle successful API response', async () => {
axios.post.mockResolvedValueOnce(mockApiResponse);
await askCommand.run(interaction);
expect(axios.post).toHaveBeenCalledWith(
'https://nano-gpt.com/api/v1/chat/completions',
expect.objectContaining({
model: 'deepseek/deepseek-v3.2',
}),
expect.any(Object),
);
expect(interaction.followUp).toHaveBeenCalledWith({
content: expect.not.stringContaining('Web search enabled'),
split: false,
allowedMentions: { parse: [] },
});
});
it('should handle long responses with proper chunking', async () => {
const longResponse = 'A'.repeat(2500);
axios.post.mockResolvedValueOnce({
data: {
choices: [
{
message: {
content: longResponse,
},
},
],
},
});
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledTimes(2);
expect(interaction.followUp.mock.calls[1][0].content).toContain('(continued)');
});
it('should handle code blocks in chunked responses', async () => {
const responseWithCodeBlock = "Here's a code example:\n```python\nprint('hello')\n```".repeat(
20,
);
axios.post.mockResolvedValueOnce({
data: {
choices: [
{
message: {
content: responseWithCodeBlock,
},
},
],
},
});
await askCommand.run(interaction);
// Verify that code blocks are not split in the middle
interaction.followUp.mock.calls.forEach(call => {
const { content } = call[0];
const openBlocks = (content.match(/```/g) || []).length;
expect(openBlocks % 2).toBe(0); // Should always be even
});
});
it('should handle API errors', async () => {
const error = new Error('API Error');
error.response = { data: 'API Error Details' };
axios.post.mockRejectedValueOnce(error);
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledWith({
content: 'Sorry, there was an error processing your request.',
ephemeral: true,
});
});
it('should handle rate limit errors', async () => {
const error = new Error('Rate Limit Exceeded');
error.response = { status: 429, data: 'Too Many Requests' };
axios.post.mockRejectedValueOnce(error);
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledWith({
content: 'The AI service is currently busy. Please try again in a few moments.',
ephemeral: true,
});
});
it('should handle timeout errors', async () => {
const error = new Error('Timeout');
error.code = 'ETIMEDOUT';
axios.post.mockRejectedValueOnce(error);
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledWith({
content: 'The request timed out. Please try again.',
ephemeral: true,
});
});
it('should handle invalid request errors', async () => {
const error = new Error('Bad Request');
error.response = { status: 400, data: 'Invalid Request' };
axios.post.mockRejectedValueOnce(error);
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledWith({
content: 'Invalid request. Please try rephrasing your question.',
ephemeral: true,
});
});
it('should send API request with correct headers', async () => {
axios.post.mockResolvedValueOnce(mockApiResponse);
await askCommand.run(interaction);
expect(axios.post).toHaveBeenCalledWith(expect.any(String), expect.any(Object), {
headers: {
Authorization: 'Bearer test-api-key',
'Content-Type': 'application/json',
},
});
});
it('should handle defer reply failure', async () => {
interaction = createMockInteraction({
commandName: 'ask',
stringOptions: {
prompt: mockPrompt,
},
deferFails: true,
});
await askCommand.run(interaction);
expect(interaction.followUp).toHaveBeenCalledWith(
expect.objectContaining({
content: 'Sorry, there was an error processing your request.',
ephemeral: true,
}),
);
});
it('should handle follow up failure', async () => {
interaction = createMockInteraction({
commandName: 'ask',
stringOptions: {
prompt: mockPrompt,
},
followUpFails: true,
});
axios.post.mockResolvedValueOnce(mockApiResponse);
await expect(askCommand.run(interaction)).rejects.toThrow('Failed to follow up');
});
});
});