Initial commit

This commit is contained in:
2025-02-02 14:10:51 -05:00
commit 9c74e724a8
28 changed files with 10554 additions and 0 deletions
+360
View File
@@ -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'
);
});
});
});
+106
View File
@@ -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);
});
});
});
+221
View File
@@ -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',
);
});
});
});
+57
View File
@@ -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',
);
});
});
});
+140
View File
@@ -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',
);
});
});
});