diff --git a/package-lock.json b/package-lock.json index b45d481..a270f20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", + "dotenv": "^16.3.1", "eslint": "^8.53.0", "typescript": "^5.2.2", "vitest": "^0.34.6", @@ -3282,6 +3283,18 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index d18809d..fb1f8c8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", + "dotenv": "^16.3.1", "eslint": "^8.53.0", "typescript": "^5.2.2", "vitest": "^0.34.6", diff --git a/templates/python/main.py b/templates/python/main.py index a4135a9..b3d4826 100644 --- a/templates/python/main.py +++ b/templates/python/main.py @@ -1,7 +1,6 @@ import discord intents = discord.Intents.default() -intents.message_content = True client = discord.Client(intents=intents) @@ -17,4 +16,5 @@ async def on_message(message): if message.content.startswith('!ping'): await message.channel.send('Pong!') -client.run('<%= bot-token %>') +# 'log_handler=None' is used to disable the default logging handler. Remove it if you want to use the default logging handler. +client.run('<%= bot-token %>', log_handler=None) diff --git a/test/generator.test.ts b/test/generator.test.ts deleted file mode 100644 index 79610a2..0000000 --- a/test/generator.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { describe, test } from 'vitest'; -import { createHelpers } from 'yeoman-test'; -import path from 'path'; -import url from 'url'; - -const __dirname = url.fileURLToPath(new URL('../', import.meta.url)); - - -const defaultAnswers = { - botName: 'test-bot', - botType: 'typescript', - botToken: 'xxx', - openWithCode: false, -}; - -describe('Check yeoman generator works', () => { - const targetRoot = path.join(__dirname, 'test-temp'); - const moduleRoot = path.join(__dirname, 'app'); - const resultRoot = path.join(targetRoot, defaultAnswers.botName); - - test('Should produce TypeScript files', async () => { - const context = createHelpers({}).run(moduleRoot); - const configJson = path.join(resultRoot, 'config.json'); - - context.targetDirectory = targetRoot; - context.cleanTestDirectory(true); - await context - .onGenerator(generator => { - generator.destinationRoot(targetRoot); - }) - .withAnswers(defaultAnswers) - .withArguments(['skip-install', 'skip-build']) - .then((result) => { - const files = [ - configJson, - resultRoot + '/src/index.ts', - resultRoot + '/tsconfig.json', - resultRoot + '/package.json', - ]; - result.assertFile(files); - result.assertJsonFileContent(configJson, CONFIG_JSON_EXPECTED); - }); - // context.cleanup(); - }, 120_000); - - test('Should produce Python files', async () => { - const context = createHelpers({}).run(moduleRoot); - const mainPy = path.join(resultRoot, 'main.py'); - - context.targetDirectory = targetRoot; - context.cleanTestDirectory(true); - await context - .onGenerator(generator => { - generator.destinationRoot(targetRoot); - }) - .withAnswers({ ...defaultAnswers, botType: 'python' }) - .then((result) => { - const files = [ - mainPy, - resultRoot + '/requirements.txt', - ]; - result.assertFile(files); - // result.assertFileContent(mainPy, PYTHON_BOT_EXPECTED); - }); - context.cleanup(); - }, 120_000); - - test('Should produce Rust files', async () => { - const context = createHelpers({}).run(moduleRoot); - const cargoToml = path.join(resultRoot, 'Cargo.toml'); - - context.targetDirectory = targetRoot; - context.cleanTestDirectory(true); - await context - .onGenerator(generator => { - generator.destinationRoot(targetRoot); - }) - .withAnswers({ ...defaultAnswers, botType: 'rust' }) - .then((result) => { - const files = [ - resultRoot + '/src/main.rs', - cargoToml - ]; - result.assertFile(files); - result.assertFileContent(cargoToml, CARGO_TOML_EXPECTED); - }); - context.cleanup(); - }, 120_000); -}); - -const CONFIG_JSON_EXPECTED: Record = { - 'token': 'xxx' -}; - -const PYTHON_BOT_EXPECTED = `import discord - -intents = discord.Intents.default() -intents.message_content = True - -client = discord.Client(intents=intents) - -@client.event -async def on_ready(): - print(f'Bot is logged in as {client.user}') - -@client.event -async def on_message(message): - if message.author == client.user: - return - - if message.content.startswith('!ping'): - await message.channel.send('Pong!') - -client.run('${defaultAnswers.botToken}') -`; - -const CARGO_TOML_EXPECTED = `[package] -name = "${defaultAnswers.botName}" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -`; \ No newline at end of file diff --git a/test/templates.test.ts b/test/templates.test.ts new file mode 100644 index 0000000..1d12fdd --- /dev/null +++ b/test/templates.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, test } from 'vitest'; +import { createHelpers } from 'yeoman-test'; +import { spawn, exec } from 'child_process'; +import * as dotenv from 'dotenv'; +import path from 'path'; +import url from 'url'; + +const __dirname = url.fileURLToPath(new URL('../', import.meta.url)); +const TIMEOUT = 30_000; // 30 seconds + +dotenv.config({ path: path.join(__dirname, '.env') }); + +const { BOT_TOKEN } = process.env; +if (!BOT_TOKEN) { + throw new Error('You must specify a valid bot token in the BOT_TOKEN environment variable'); +} + +const defaultAnswers = { + botName: 'test-run-bot', + botType: 'typescript', + botToken: BOT_TOKEN, + openWithCode: false, +}; + +describe('Check if the templates work', () => { + const targetRoot = path.join(__dirname, 'test-temp'); + const moduleRoot = path.join(__dirname, 'app'); + const resultRoot = path.join(targetRoot, defaultAnswers.botName); + + test('Should generate and run TypeScript Discord Bot', async () => { + const context = createHelpers({}).run(moduleRoot); + + context.targetDirectory = targetRoot; + context.cleanTestDirectory(true); + await context + .onGenerator(generator => { + generator.destinationRoot(targetRoot); + }) + .withAnswers(defaultAnswers) + .then(async () => { + const result = await runBot('node', ['./dist/src/index.js'], resultRoot); + expect(result).toContain(BOT_OUTPUT_START); + }); + + context.cleanup(); + }, 120_000); + + + test.skip('Should generate and run Python Discord Bot', async () => { + const context = createHelpers({}).run(moduleRoot); + + context.targetDirectory = targetRoot; + context.cleanTestDirectory(true); + await context + .onGenerator(generator => { + generator.destinationRoot(targetRoot); + }) + .withAnswers({...defaultAnswers, botType: 'python' }) + .then(async () => { + // install dependencies + await exec('pip3 install -r requirements.txt', { cwd: resultRoot }); + + const result = await runBot('python', ['main.py'], resultRoot); + expect(result).toContain(BOT_OUTPUT_START); + }); + + context.cleanup(); + }, 120_000); +}); + + +async function runBot(command: string, args: string[], root: string): Promise { + const childProcess = spawn(command, args, { cwd: root, stdio: 'pipe', timeout: TIMEOUT }); + + const result = await new Promise((resolve) => { + // timeout + const timeout = setTimeout(() => { + resolve('TIMEOUT'); + }, TIMEOUT); + + // output + childProcess.stdout.on('data', (data) => { + clearTimeout(timeout); + + resolve(data.toString()); + }); + + childProcess.stderr.on('data', (data) => { + clearTimeout(timeout); + + resolve(data.toString()); + }); + + childProcess.on('close', () => { + clearTimeout(timeout); + }); + + }); + + childProcess.kill('SIGINT'); + return result; +} + + +const BOT_OUTPUT_START = 'Bot is logged in as'; \ No newline at end of file