Initial commit
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
const axios = require('axios');
|
||||
const { createMockInteraction } = require('../utils/testUtils');
|
||||
|
||||
// Mock axios
|
||||
jest.mock('axios');
|
||||
|
||||
// Mock the discord.js module
|
||||
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 {
|
||||
addBooleanOption: jest.fn().mockImplementation(boolCallback => {
|
||||
const boolOption = {
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
setRequired: jest.fn().mockReturnThis(),
|
||||
};
|
||||
boolCallback(boolOption);
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: 'websearch',
|
||||
description:
|
||||
'Enable web search for more up-to-date information',
|
||||
type: 5,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
toJSON: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const askCommand = require('../../commands/ask');
|
||||
|
||||
describe('Ask Command', () => {
|
||||
describe('Command Structure', () => {
|
||||
it('should have correct name and description', () => {
|
||||
const commandData = askCommand.data.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('data');
|
||||
expect(askCommand).toHaveProperty('execute');
|
||||
expect(typeof askCommand.execute).toBe('function');
|
||||
});
|
||||
|
||||
it('should have correct option configuration', () => {
|
||||
const commandData = askCommand.data.toJSON();
|
||||
const [promptOption, websearchOption] = commandData.options;
|
||||
|
||||
expect(promptOption.name).toBe('prompt');
|
||||
expect(promptOption.description).toBe('Your question or prompt');
|
||||
expect(promptOption.required).toBe(true);
|
||||
|
||||
expect(websearchOption.name).toBe('websearch');
|
||||
expect(websearchOption.description).toBe(
|
||||
'Enable web search for more up-to-date information'
|
||||
);
|
||||
expect(websearchOption.required).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
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.OPENROUTER_API_KEY = 'test-api-key';
|
||||
axios.post.mockReset();
|
||||
jest.clearAllMocks();
|
||||
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle successful API response with websearch disabled', async () => {
|
||||
axios.post.mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'https://openrouter.ai/api/v1/chat/completions',
|
||||
expect.objectContaining({
|
||||
model: 'google/gemini-2.0-flash-exp:free',
|
||||
plugins: undefined,
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
expect(interaction.followUp).toHaveBeenCalledWith({
|
||||
content: expect.not.stringContaining('Web search enabled'),
|
||||
split: false,
|
||||
allowedMentions: { parse: [] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle successful API response with websearch enabled', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
});
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'https://openrouter.ai/api/v1/chat/completions',
|
||||
expect.objectContaining({
|
||||
model: 'google/gemini-2.0-flash-exp:free:online',
|
||||
plugins: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'web',
|
||||
max_results: 3,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
expect(interaction.followUp).toHaveBeenCalledWith({
|
||||
content: expect.stringContaining('Web search enabled'),
|
||||
split: false,
|
||||
allowedMentions: { parse: [] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should use default websearch value when not provided', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
// Not providing websearch option
|
||||
});
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'https://openrouter.ai/api/v1/chat/completions',
|
||||
expect.objectContaining({
|
||||
model: 'google/gemini-2.0-flash-exp:free',
|
||||
plugins: undefined,
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle long responses with proper chunking and websearch enabled', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
});
|
||||
|
||||
const longResponse = 'A'.repeat(2500);
|
||||
axios.post.mockResolvedValueOnce({
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: longResponse,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(interaction.followUp).toHaveBeenCalledTimes(2);
|
||||
expect(interaction.followUp.mock.calls[0][0].content).toContain(
|
||||
'Web search enabled'
|
||||
);
|
||||
expect(interaction.followUp.mock.calls[1][0].content).toContain(
|
||||
'(continued)'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle code blocks in chunked responses with websearch', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
});
|
||||
|
||||
const responseWithCodeBlock =
|
||||
"Here's a code example:\n```python\nprint('hello')\n```".repeat(20);
|
||||
axios.post.mockResolvedValueOnce({
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: responseWithCodeBlock,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await askCommand.execute(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
|
||||
});
|
||||
|
||||
// First chunk should contain websearch indicator
|
||||
expect(interaction.followUp.mock.calls[0][0].content).toContain(
|
||||
'Web search enabled'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle API errors with websearch enabled', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
});
|
||||
|
||||
const error = new Error('API Error');
|
||||
error.response = { data: 'API Error Details' };
|
||||
axios.post.mockRejectedValueOnce(error);
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(interaction.followUp).toHaveBeenCalledWith({
|
||||
content: 'Sorry, there was an error processing your request.',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should send API request with correct headers regardless of websearch', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
});
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
await askCommand.execute(interaction);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(Object),
|
||||
{
|
||||
headers: {
|
||||
Authorization: 'Bearer test-api-key',
|
||||
'HTTP-Referer': 'https://github.com/hllywluis/kekbot.js',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle defer reply failure with websearch', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
deferFails: true,
|
||||
});
|
||||
|
||||
await expect(askCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to defer reply'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle follow up failure with websearch', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ask',
|
||||
stringOptions: {
|
||||
prompt: mockPrompt,
|
||||
},
|
||||
booleanOptions: {
|
||||
websearch: true,
|
||||
},
|
||||
followUpFails: true,
|
||||
});
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockApiResponse);
|
||||
|
||||
await expect(askCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to follow up'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
const { createMockInteraction } = require('../utils/testUtils');
|
||||
|
||||
// Mock the discord.js module
|
||||
jest.mock('discord.js', () => ({
|
||||
SlashCommandBuilder: jest.fn().mockReturnValue({
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
name: 'help',
|
||||
description: 'Lists all available commands',
|
||||
}),
|
||||
}),
|
||||
EmbedBuilder: jest.fn().mockReturnValue({
|
||||
setColor: jest.fn().mockReturnThis(),
|
||||
setTitle: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
setTimestamp: jest.fn().mockReturnThis(),
|
||||
addFields: jest.fn().mockReturnThis(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const helpCommand = require('../../commands/help');
|
||||
|
||||
describe('Help Command', () => {
|
||||
describe('Command Structure', () => {
|
||||
it('should have correct name and description', () => {
|
||||
const commandData = helpCommand.data.toJSON();
|
||||
expect(commandData.name).toBe('help');
|
||||
expect(commandData.description).toBe('Lists all available commands');
|
||||
});
|
||||
|
||||
it('should have required command properties', () => {
|
||||
expect(helpCommand).toHaveProperty('data');
|
||||
expect(helpCommand).toHaveProperty('execute');
|
||||
expect(typeof helpCommand.execute).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Execution', () => {
|
||||
let interaction;
|
||||
const mockCommands = new Map([
|
||||
['ping', { data: { name: 'ping', description: 'Replies with Pong!' } }],
|
||||
[
|
||||
'help',
|
||||
{ data: { name: 'help', description: 'Lists all available commands' } },
|
||||
],
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'help',
|
||||
});
|
||||
interaction.client.commands = mockCommands;
|
||||
});
|
||||
|
||||
it('should create an embed with all commands', async () => {
|
||||
await helpCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
embeds: expect.arrayContaining([expect.any(Object)]),
|
||||
ephemeral: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty commands collection', async () => {
|
||||
interaction.client.commands = new Map();
|
||||
await helpCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
embeds: expect.arrayContaining([expect.any(Object)]),
|
||||
ephemeral: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle interaction reply failure', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'help',
|
||||
replyFails: true,
|
||||
});
|
||||
interaction.client.commands = mockCommands;
|
||||
|
||||
await expect(helpCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to reply',
|
||||
);
|
||||
});
|
||||
|
||||
it('should create embed with correct structure', async () => {
|
||||
const { EmbedBuilder } = require('discord.js');
|
||||
await helpCommand.execute(interaction);
|
||||
|
||||
const embedInstance = EmbedBuilder.mock.results[0].value;
|
||||
|
||||
expect(embedInstance.setColor).toHaveBeenCalledWith('#5dc67b');
|
||||
expect(embedInstance.setTitle).toHaveBeenCalledWith('Available Commands');
|
||||
expect(embedInstance.setDescription).toHaveBeenCalledWith(
|
||||
'Here are all my commands:',
|
||||
);
|
||||
expect(embedInstance.setTimestamp).toHaveBeenCalled();
|
||||
expect(embedInstance.addFields).toHaveBeenCalledTimes(mockCommands.size);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,221 @@
|
||||
const { createMockInteraction } = require('../utils/testUtils');
|
||||
|
||||
// Mock the discord.js module
|
||||
jest.mock('discord.js', () => ({
|
||||
SlashCommandBuilder: jest.fn().mockReturnValue({
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
addUserOption: jest.fn().mockImplementation(callback => {
|
||||
const option = {
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
setRequired: jest.fn().mockReturnThis(),
|
||||
};
|
||||
callback(option);
|
||||
return {
|
||||
addStringOption: jest.fn().mockImplementation(callback => {
|
||||
const option = {
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
};
|
||||
callback(option);
|
||||
return {
|
||||
setDefaultMemberPermissions: jest.fn().mockReturnThis(),
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
name: 'kick',
|
||||
description: 'Kick a user from the server',
|
||||
options: [
|
||||
{
|
||||
name: 'target',
|
||||
description: 'The user to kick',
|
||||
type: 6,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'reason',
|
||||
description: 'Reason for kicking',
|
||||
type: 3,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
toJSON: jest.fn(),
|
||||
}),
|
||||
PermissionFlagsBits: {
|
||||
KickMembers: 0x2n,
|
||||
},
|
||||
}));
|
||||
|
||||
const kickCommand = require('../../commands/kick');
|
||||
|
||||
describe('Kick Command', () => {
|
||||
describe('Command Structure', () => {
|
||||
it('should have correct name and description', () => {
|
||||
const commandData = kickCommand.data.toJSON();
|
||||
expect(commandData.name).toBe('kick');
|
||||
expect(commandData.description).toBe('Kick a user from the server');
|
||||
});
|
||||
|
||||
it('should have required command properties', () => {
|
||||
expect(kickCommand).toHaveProperty('data');
|
||||
expect(kickCommand).toHaveProperty('execute');
|
||||
expect(typeof kickCommand.execute).toBe('function');
|
||||
});
|
||||
|
||||
it('should have correct option configuration', () => {
|
||||
const commandData = kickCommand.data.toJSON();
|
||||
const [targetOption, reasonOption] = commandData.options;
|
||||
|
||||
expect(targetOption.name).toBe('target');
|
||||
expect(targetOption.description).toBe('The user to kick');
|
||||
expect(targetOption.required).toBe(true);
|
||||
|
||||
expect(reasonOption.name).toBe('reason');
|
||||
expect(reasonOption.description).toBe('Reason for kicking');
|
||||
expect(reasonOption.required).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Execution', () => {
|
||||
let interaction;
|
||||
const mockKick = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockKick.mockReset();
|
||||
|
||||
const mockTarget = {
|
||||
kickable: true,
|
||||
kick: mockKick,
|
||||
user: {
|
||||
tag: 'TestUser#1234',
|
||||
},
|
||||
};
|
||||
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: mockTarget,
|
||||
},
|
||||
stringOptions: {
|
||||
reason: 'Test reason',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully kick a user with reason', async () => {
|
||||
await kickCommand.execute(interaction);
|
||||
|
||||
expect(mockKick).toHaveBeenCalledWith('Test reason');
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: expect.stringContaining('Successfully kicked TestUser#1234'),
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use default reason when none provided', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: {
|
||||
kickable: true,
|
||||
kick: mockKick,
|
||||
user: { tag: 'TestUser#1234' },
|
||||
},
|
||||
},
|
||||
stringOptions: {
|
||||
reason: null,
|
||||
},
|
||||
});
|
||||
|
||||
await kickCommand.execute(interaction);
|
||||
|
||||
expect(mockKick).toHaveBeenCalledWith('No reason provided');
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: expect.stringContaining('No reason provided'),
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-existent target', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: null,
|
||||
},
|
||||
});
|
||||
|
||||
await kickCommand.execute(interaction);
|
||||
|
||||
expect(mockKick).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: 'That user is not in this server!',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-kickable target', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: {
|
||||
kickable: false,
|
||||
user: { tag: 'Admin#1234' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await kickCommand.execute(interaction);
|
||||
|
||||
expect(mockKick).not.toHaveBeenCalled();
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content:
|
||||
'I cannot kick this user! They may have higher permissions than me.',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle kick failure', async () => {
|
||||
const mockTarget = {
|
||||
kickable: true,
|
||||
kick: jest.fn().mockRejectedValueOnce(new Error('Failed to kick user')),
|
||||
user: { tag: 'TestUser#1234' },
|
||||
};
|
||||
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: mockTarget,
|
||||
},
|
||||
});
|
||||
|
||||
await kickCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: 'There was an error trying to kick this user!',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle interaction reply failure', async () => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'kick',
|
||||
memberOptions: {
|
||||
target: {
|
||||
kickable: true,
|
||||
kick: mockKick,
|
||||
user: { tag: 'TestUser#1234' },
|
||||
},
|
||||
},
|
||||
replyFails: true,
|
||||
});
|
||||
|
||||
await expect(kickCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to reply',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
const { createMockInteraction } = require('../utils/testUtils');
|
||||
|
||||
// Mock the discord.js module
|
||||
jest.mock('discord.js', () => ({
|
||||
SlashCommandBuilder: jest.fn().mockReturnValue({
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
name: 'ping',
|
||||
description: 'Replies with Pong!',
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const pingCommand = require('../../commands/ping');
|
||||
|
||||
describe('Ping Command', () => {
|
||||
describe('Command Structure', () => {
|
||||
it('should have correct name and description', () => {
|
||||
const commandData = pingCommand.data.toJSON();
|
||||
expect(commandData.name).toBe('ping');
|
||||
expect(commandData.description).toBe('Replies with Pong!');
|
||||
});
|
||||
|
||||
it('should have required command properties', () => {
|
||||
expect(pingCommand).toHaveProperty('data');
|
||||
expect(pingCommand).toHaveProperty('execute');
|
||||
expect(typeof pingCommand.execute).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Execution', () => {
|
||||
let interaction;
|
||||
|
||||
beforeEach(() => {
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'ping',
|
||||
});
|
||||
});
|
||||
|
||||
it('should reply with "Pong! 🏓"', async () => {
|
||||
await pingCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledTimes(1);
|
||||
expect(interaction.reply).toHaveBeenCalledWith('Pong! 🏓');
|
||||
});
|
||||
|
||||
it('should handle interaction reply failure', async () => {
|
||||
// Mock a failed reply
|
||||
interaction.reply.mockRejectedValueOnce(new Error('Failed to reply'));
|
||||
|
||||
await expect(pingCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to reply',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
const { createMockInteraction } = require('../utils/testUtils');
|
||||
|
||||
// Mock the discord.js module
|
||||
jest.mock('discord.js', () => ({
|
||||
SlashCommandBuilder: jest.fn().mockReturnValue({
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
addIntegerOption: jest.fn().mockImplementation(callback => {
|
||||
const option = {
|
||||
setName: jest.fn().mockReturnThis(),
|
||||
setDescription: jest.fn().mockReturnThis(),
|
||||
setMinValue: jest.fn().mockReturnThis(),
|
||||
setMaxValue: jest.fn().mockReturnThis(),
|
||||
setRequired: jest.fn().mockReturnThis(),
|
||||
};
|
||||
callback(option);
|
||||
return {
|
||||
setDefaultMemberPermissions: jest.fn().mockReturnThis(),
|
||||
toJSON: jest.fn().mockReturnValue({
|
||||
name: 'prune',
|
||||
description: 'Prune up to 99 messages.',
|
||||
options: [
|
||||
{
|
||||
name: 'amount',
|
||||
description: 'Number of messages to prune',
|
||||
type: 4,
|
||||
required: true,
|
||||
min_value: 1,
|
||||
max_value: 99,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
}),
|
||||
toJSON: jest.fn(),
|
||||
}),
|
||||
PermissionFlagsBits: {
|
||||
ManageMessages: 0x2000n,
|
||||
},
|
||||
}));
|
||||
|
||||
const pruneCommand = require('../../commands/prune');
|
||||
|
||||
describe('Prune Command', () => {
|
||||
describe('Command Structure', () => {
|
||||
it('should have correct name and description', () => {
|
||||
const commandData = pruneCommand.data.toJSON();
|
||||
expect(commandData.name).toBe('prune');
|
||||
expect(commandData.description).toBe('Prune up to 99 messages.');
|
||||
});
|
||||
|
||||
it('should have required command properties', () => {
|
||||
expect(pruneCommand).toHaveProperty('data');
|
||||
expect(pruneCommand).toHaveProperty('execute');
|
||||
expect(typeof pruneCommand.execute).toBe('function');
|
||||
});
|
||||
|
||||
it('should have correct option configuration', () => {
|
||||
const commandData = pruneCommand.data.toJSON();
|
||||
const option = commandData.options[0];
|
||||
|
||||
expect(option.name).toBe('amount');
|
||||
expect(option.description).toBe('Number of messages to prune');
|
||||
expect(option.required).toBe(true);
|
||||
expect(option.min_value).toBe(1);
|
||||
expect(option.max_value).toBe(99);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Execution', () => {
|
||||
let interaction;
|
||||
const mockBulkDelete = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockBulkDelete.mockReset();
|
||||
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'prune',
|
||||
integerOptions: {
|
||||
amount: 5,
|
||||
},
|
||||
});
|
||||
interaction.channel.bulkDelete = mockBulkDelete;
|
||||
});
|
||||
|
||||
it('should successfully delete messages', async () => {
|
||||
mockBulkDelete.mockResolvedValueOnce({ size: 5 });
|
||||
|
||||
await pruneCommand.execute(interaction);
|
||||
|
||||
expect(mockBulkDelete).toHaveBeenCalledWith(5, true);
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: 'Successfully deleted 5 message(s).',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle bulk delete failure', async () => {
|
||||
mockBulkDelete.mockRejectedValueOnce(
|
||||
new Error('Failed to delete messages'),
|
||||
);
|
||||
|
||||
await pruneCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: 'There was an error trying to prune messages in this channel!',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle messages older than 14 days', async () => {
|
||||
mockBulkDelete.mockRejectedValueOnce(
|
||||
new Error('Messages older than 14 days cannot be deleted'),
|
||||
);
|
||||
|
||||
await pruneCommand.execute(interaction);
|
||||
|
||||
expect(interaction.reply).toHaveBeenCalledWith({
|
||||
content: 'There was an error trying to prune messages in this channel!',
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle interaction reply failure', async () => {
|
||||
mockBulkDelete.mockResolvedValueOnce({ size: 5 });
|
||||
interaction = createMockInteraction({
|
||||
commandName: 'prune',
|
||||
integerOptions: {
|
||||
amount: 5,
|
||||
},
|
||||
replyFails: true,
|
||||
});
|
||||
interaction.channel.bulkDelete = mockBulkDelete;
|
||||
|
||||
await expect(pruneCommand.execute(interaction)).rejects.toThrow(
|
||||
'Failed to reply',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user