@zerospacegg/vynthra
Version:
Discord bot for ZeroSpace.gg data
317 lines (260 loc) • 9.9 kB
text/typescript
import { test } from 'node:test';
import assert from 'node:assert';
import { createBot, type BotConfig } from '../../src/bot/index.js';
import { validateSubcommand, validateSubcommands, createSubcommand } from '../../src/bot/utils.js';
import { createMockBotConfig } from '../setup-node.js';
import { SlashCommandSubcommandBuilder } from 'discord.js';
test('Bot Configuration', async (t) => {
await t.test('createBot', async (t) => {
await t.test('should create a bot with minimal config', () => {
const config = createMockBotConfig();
const bot = createBot(config);
assert.ok(bot);
assert.ok(bot.getClient);
assert.strictEqual(typeof bot.start, 'function');
assert.strictEqual(typeof bot.shutdown, 'function');
});
await t.test('should create a bot with custom root command name', () => {
const config = createMockBotConfig({
rootCommandName: 'mygame',
rootCommandDescription: 'My custom game bot',
});
const bot = createBot(config);
assert.ok(bot);
});
await t.test('should create a bot with custom subcommands', () => {
const customSubcommand = createSubcommand(
'test',
'Test subcommand',
(subcommand) => subcommand,
async (interaction) => {
await interaction.reply('Test response');
}
);
const config = createMockBotConfig({
subcommands: [customSubcommand],
});
const bot = createBot(config);
assert.ok(bot);
});
await t.test('should validate custom subcommands on creation', () => {
const invalidSubcommand = {
name: '', // Invalid empty name
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
const config = createMockBotConfig({
subcommands: [invalidSubcommand as any],
});
assert.throws(() => createBot(config));
});
});
await t.test('Configuration Validation', async (t) => {
await t.test('should require token and clientId', () => {
assert.throws(() => createBot({} as BotConfig));
assert.throws(() => createBot({ token: 'test' } as BotConfig));
assert.throws(() => createBot({ clientId: 'test' } as BotConfig));
});
await t.test('should handle optional guildId', () => {
const config = createMockBotConfig();
const { guildId, ...configWithoutGuildId } = config;
const bot = createBot(configWithoutGuildId);
assert.ok(bot);
});
await t.test('should use default values for optional fields', () => {
const config = createMockBotConfig();
const bot = createBot(config);
// The bot should be created successfully with defaults
assert.ok(bot);
});
});
await t.test('Custom Configuration Options', async (t) => {
await t.test('should accept custom root command name', () => {
const config = createMockBotConfig({
rootCommandName: 'customgame',
});
const bot = createBot(config);
assert.ok(bot);
});
await t.test('should accept custom root command description', () => {
const config = createMockBotConfig({
rootCommandDescription: 'Custom game description',
});
const bot = createBot(config);
assert.ok(bot);
});
await t.test('should accept empty subcommands array', () => {
const config = createMockBotConfig({
subcommands: [],
});
const bot = createBot(config);
assert.ok(bot);
});
await t.test('should accept multiple custom subcommands', () => {
const subcommand1 = createSubcommand(
'help',
'Help command',
(subcommand) => subcommand,
async (interaction) => {
await interaction.reply('Help!');
}
);
const subcommand2 = createSubcommand(
'info',
'Info command',
(subcommand) => subcommand,
async (interaction) => {
await interaction.reply('Info!');
}
);
const config = createMockBotConfig({
subcommands: [subcommand1, subcommand2],
});
const bot = createBot(config);
assert.ok(bot);
});
});
await t.test('Environment Integration', async (t) => {
await t.test('should work with environment variables', () => {
process.env.DISCORD_TOKEN = 'env-token';
process.env.DISCORD_CLIENT_ID = 'env-client-id';
process.env.DISCORD_GUILD_ID = 'env-guild-id';
const config: BotConfig = {
token: process.env.DISCORD_TOKEN!,
clientId: process.env.DISCORD_CLIENT_ID!,
guildId: process.env.DISCORD_GUILD_ID,
};
const bot = createBot(config);
assert.ok(bot);
// Clean up
delete process.env.DISCORD_TOKEN;
delete process.env.DISCORD_CLIENT_ID;
delete process.env.DISCORD_GUILD_ID;
});
});
});
test('Subcommand Validation', async (t) => {
await t.test('validateSubcommand', async (t) => {
await t.test('should validate valid subcommand', () => {
const validSubcommand = createSubcommand(
'test',
'Test command',
(subcommand) => subcommand,
async (interaction) => {
await interaction.reply('Test');
}
);
assert.doesNotThrow(() => validateSubcommand(validSubcommand));
});
await t.test('should reject subcommand with empty name', () => {
const invalidSubcommand = {
name: '',
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.throws(() => validateSubcommand(invalidSubcommand), /valid name/);
});
await t.test('should reject subcommand with empty description', () => {
const invalidSubcommand = {
name: 'test',
description: '',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.throws(() => validateSubcommand(invalidSubcommand), /valid description/);
});
await t.test('should reject subcommand with invalid name characters', () => {
const invalidSubcommand = {
name: 'Test Command!', // Contains uppercase and special characters
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.throws(() => validateSubcommand(invalidSubcommand), /lowercase letters/);
});
await t.test('should reject subcommand with too long description', () => {
const invalidSubcommand = {
name: 'test',
description: 'a'.repeat(101), // 101 characters
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.throws(() => validateSubcommand(invalidSubcommand), /100 characters/);
});
await t.test('should reject subcommand without builder function', () => {
const invalidSubcommand = {
name: 'test',
description: 'Test',
builder: null,
execute: async () => {},
};
assert.throws(() => validateSubcommand(invalidSubcommand as any), /builder function/);
});
await t.test('should reject subcommand without execute function', () => {
const invalidSubcommand = {
name: 'test',
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: null,
};
assert.throws(() => validateSubcommand(invalidSubcommand as any), /execute function/);
});
await t.test('should accept valid command names with allowed characters', () => {
const validNames = ['test', 'test-command', 'test_command', 'test123', 'a'];
for (const name of validNames) {
const subcommand = {
name,
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.doesNotThrow(() => validateSubcommand(subcommand), `Name "${name}" should be valid`);
}
});
await t.test('should reject invalid command names', () => {
const invalidNames = ['', 'Test', 'test command', 'test!', 'test@', 'a'.repeat(33)];
for (const name of invalidNames) {
const subcommand = {
name,
description: 'Test',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
};
assert.throws(() => validateSubcommand(subcommand), `Name "${name}" should be invalid`);
}
});
});
await t.test('validateSubcommands', async (t) => {
await t.test('should validate array of valid subcommands', () => {
const subcommands = [
createSubcommand('test1', 'Test 1', (sc) => sc, async () => {}),
createSubcommand('test2', 'Test 2', (sc) => sc, async () => {}),
];
assert.doesNotThrow(() => validateSubcommands(subcommands));
});
await t.test('should reject duplicate subcommand names', () => {
const subcommands = [
createSubcommand('test', 'Test 1', (sc) => sc, async () => {}),
createSubcommand('test', 'Test 2', (sc) => sc, async () => {}),
];
assert.throws(() => validateSubcommands(subcommands), /Duplicate subcommand name: test/);
});
await t.test('should validate empty array', () => {
assert.doesNotThrow(() => validateSubcommands([]));
});
await t.test('should reject array with invalid subcommand', () => {
const subcommands = [
createSubcommand('valid', 'Valid command', (sc) => sc, async () => {}),
{
name: '',
description: 'Invalid',
builder: () => new SlashCommandSubcommandBuilder(),
execute: async () => {},
} as any,
];
assert.throws(() => validateSubcommands(subcommands));
});
});
});