UNPKG

@zerospacegg/vynthra

Version:
317 lines (260 loc) 9.9 kB
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)); }); }); });