diff --git a/bin/artisan.ts b/bin/artisan.ts index b653889..4e9fbfa 100644 --- a/bin/artisan.ts +++ b/bin/artisan.ts @@ -43,6 +43,7 @@ Config.set('rc.commands', { entrypoint: '#bin/repl', path: '#src/commands/ReplCommand', }, + greet: '#tests/stubs/commands/GreetCommand', }) /* @@ -52,7 +53,7 @@ Config.set('rc.commands', { | | Here is where your application will bootstrap. Ignite class will be res | ponsible to bootstrap your application partial or complete. Is not reco -| mmended to bootstrap the Athenna application completelly by calling the +| mmended to bootstrap the Athenna application completely by calling the | "fire" method, you should always let the type of application determine if | the application should be fully bootstrapped or not. | diff --git a/bin/test.ts b/bin/test.ts index 65faa7f..30041ef 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -7,12 +7,16 @@ * file that was distributed with this source code. */ +import { request } from '@athenna/http/testing/plugins' +import { command } from '@athenna/artisan/testing/plugins' import { Runner, assert, specReporter } from '@athenna/test' process.env.CORE_TESTING = 'true' await Runner.setTsEnv() .addPlugin(assert()) + .addPlugin(request()) + .addPlugin(command()) .addReporter(specReporter()) .addPath('tests/unit/**/*.ts') .setCliArgs(process.argv.slice(2)) diff --git a/package-lock.json b/package-lock.json index c112b09..3d73ac9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@athenna/core", - "version": "4.5.0", + "version": "4.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@athenna/core", - "version": "4.5.0", + "version": "4.6.0", "license": "MIT", "dependencies": { "pretty-repl": "^3.1.2", "semver": "^7.5.4" }, "devDependencies": { - "@athenna/artisan": "^4.2.0", - "@athenna/common": "^4.4.0", + "@athenna/artisan": "^4.8.0", + "@athenna/common": "^4.9.2", "@athenna/config": "^4.3.0", "@athenna/http": "^4.4.0", "@athenna/ioc": "^4.1.0", @@ -72,9 +72,9 @@ "dev": true }, "node_modules/@athenna/artisan": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@athenna/artisan/-/artisan-4.2.0.tgz", - "integrity": "sha512-shko3RVj71VtW/TUo4GxeSjS3aRn0D8NE/aJu52wyhsvKSMzelmbvozpZbDfgk2FuRE7uK60JqxH8LePoZPcOg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@athenna/artisan/-/artisan-4.8.0.tgz", + "integrity": "sha512-46HgRutIy0/tpeAqNnYRFPkdaHuVV6lJXsGrZcWc+KbLDK3KF53nuFQ2mpyNZPhAq2s0yY2l9EazDnORY4iEVg==", "dev": true, "dependencies": { "chalk-rainbow": "^1.0.0", @@ -83,7 +83,7 @@ "columnify": "^1.6.0", "commander": "^9.5.0", "figlet": "^1.6.0", - "inquirer": "^9.2.8", + "inquirer": "^9.2.10", "log-update": "^5.0.1", "ora": "^6.3.1" } @@ -556,9 +556,9 @@ } }, "node_modules/@athenna/common": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@athenna/common/-/common-4.5.0.tgz", - "integrity": "sha512-/Va3PtDRAR/FdfRuGYmuUXPGuOikqmXtGOENWVVbcHVu0PaXkZ8P9bqWngbyfBV+R2O0g2UVyzEXS7M6NP0JMg==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@athenna/common/-/common-4.9.2.tgz", + "integrity": "sha512-0gnRXRPAO3oWwIlyyugDn+mV0cWXSY2MYTVsr1t2VviSEfSXeAW8mX1QU+JxSEnbNccOZ6+1UfeRvaKsVra/rw==", "dev": true, "dependencies": { "@fastify/formbody": "^7.4.0", @@ -582,7 +582,7 @@ "uuid": "^8.3.2", "validator-brazil": "^1.2.2", "youch": "^3.2.3", - "youch-terminal": "^2.2.1" + "youch-terminal": "^2.2.2" } }, "node_modules/@athenna/config": { diff --git a/package.json b/package.json index 9742133..8f4a2e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/core", - "version": "4.5.0", + "version": "4.6.0", "description": "The plug and play Node.js framework.", "license": "MIT", "author": "João Lenon ", @@ -70,8 +70,8 @@ "semver": "^7.5.4" }, "devDependencies": { - "@athenna/artisan": "^4.2.0", - "@athenna/common": "^4.4.0", + "@athenna/artisan": "^4.8.0", + "@athenna/common": "^4.9.2", "@athenna/config": "^4.3.0", "@athenna/http": "^4.4.0", "@athenna/ioc": "^4.1.0", @@ -204,7 +204,9 @@ "provider": "./templates/provider.edge", "service": "./templates/service.edge", "test": "./templates/test.edge", - "testFn": "./templates/testFn.edge", + "test-cli": "./templates/test-cli.edge", + "test-rest": "./templates/test-rest.edge", + "test-fn": "./templates/test-fn.edge", "command": "node_modules/@athenna/artisan/templates/command.edge", "controller": "node_modules/@athenna/http/templates/controller.edge", "middleware": "node_modules/@athenna/http/templates/middleware.edge", diff --git a/src/commands/MakeTestCommand.ts b/src/commands/MakeTestCommand.ts index 6df2a01..735e6d7 100644 --- a/src/commands/MakeTestCommand.ts +++ b/src/commands/MakeTestCommand.ts @@ -24,6 +24,20 @@ export class MakeTestCommand extends BaseCommand { }) public isUnit: boolean + @Option({ + description: 'Create a REST API test.', + signature: '-r, --rest', + default: false, + }) + public isRest: boolean + + @Option({ + description: 'Create a CLI test.', + signature: '-c, --cli', + default: false, + }) + public isCli: boolean + @Option({ description: 'Create the test as function instead of class.', signature: '--function', @@ -44,8 +58,12 @@ export class MakeTestCommand extends BaseCommand { let template = 'test' + if (this.isCli) template = 'test-cli' + else if (this.isRest) template = 'test-rest' + else if (this.isUnit) template = 'test' // This is necessary to avoid multiple options case. + if (this.isFunction) { - template = 'testFn' + template = 'test-fn' } const file = await this.generator diff --git a/src/commands/TestCommand.ts b/src/commands/TestCommand.ts index abd78f4..1903a60 100644 --- a/src/commands/TestCommand.ts +++ b/src/commands/TestCommand.ts @@ -8,7 +8,7 @@ */ import { Module } from '@athenna/common' -import { BaseCommand, Option } from '@athenna/artisan' +import { Option, BaseCommand, Commander } from '@athenna/artisan' export class TestCommand extends BaseCommand { @Option({ @@ -26,6 +26,10 @@ export class TestCommand extends BaseCommand { return 'Run the tests of your application.' } + public static commander(commander: Commander) { + return commander.allowUnknownOption() + } + // TODO Verify if this command still makes sense to exist. public async handle(): Promise { if (this.env !== '') { diff --git a/src/testing/BaseCliTest.ts b/src/testing/BaseCliTest.ts index 04b3cba..c6c6300 100644 --- a/src/testing/BaseCliTest.ts +++ b/src/testing/BaseCliTest.ts @@ -7,29 +7,27 @@ * file that was distributed with this source code. */ +import { pathToFileURL } from 'node:url' import { Options } from '@athenna/common' import { BeforeAll } from '@athenna/test' -import { Artisan } from '@athenna/artisan' import { Ignite, type IgniteOptions } from '@athenna/core' +import { TestCommand } from '@athenna/artisan/testing/plugins' export class BaseCliTest { public ignite: Ignite public igniteOptions: IgniteOptions = {} + public artisanPath: string = Path.bootstrap(`artisan.${Path.ext()}`) @BeforeAll() public async baseBeforeAll() { + TestCommand.setArtisanPath(this.artisanPath) + this.ignite = await new Ignite().load( - import.meta.url, + pathToFileURL(Path.bootstrap(`test.${Path.ext()}`)).href, this.getIgniteOptions(), ) } - public async execute( - command: string, - ): Promise<{ stdout: string; stderr: string }> { - return Artisan.callInChild(command, Path.bootstrap('artisan.ts')) - } - private getIgniteOptions() { return Options.create(this.igniteOptions, { bootLogs: false, diff --git a/src/testing/BaseRestTest.ts b/src/testing/BaseRestTest.ts index 8966d9d..2c5f9d3 100644 --- a/src/testing/BaseRestTest.ts +++ b/src/testing/BaseRestTest.ts @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ +import { pathToFileURL } from 'node:url' import { Options } from '@athenna/common' import { ServerImpl } from '@athenna/http' import { AfterAll, BeforeAll } from '@athenna/test' @@ -21,7 +22,7 @@ export class BaseRestTest { @BeforeAll() public async baseBeforeAll() { this.ignite = await new Ignite().load( - import.meta.url, + pathToFileURL(Path.bootstrap(`test.${Path.ext()}`)).href, this.getIgniteOptions(), ) diff --git a/templates/test-cli.edge b/templates/test-cli.edge new file mode 100644 index 0000000..914463b --- /dev/null +++ b/templates/test-cli.edge @@ -0,0 +1,12 @@ +import { Test, type Context } from '@athenna/test' +import { BaseCliTest } from '@athenna/core/testing/BaseCliTest' + +export default class {{ namePascal }} extends BaseCliTest { + @Test() + public async shouldBeAbleToExecuteCliCommands({ command }: Context) { + const output = await command.run('greet') + + output.assertSucceeded() + output.assertLogged('Hello World!') + } +} diff --git a/templates/testFn.edge b/templates/test-fn.edge similarity index 100% rename from templates/testFn.edge rename to templates/test-fn.edge diff --git a/templates/test-rest.edge b/templates/test-rest.edge new file mode 100644 index 0000000..a7720aa --- /dev/null +++ b/templates/test-rest.edge @@ -0,0 +1,11 @@ +import { Test, type Context } from '@athenna/test' +import { BaseRestTest } from '@athenna/core/testing/BaseRestTest' + +export default class {{ namePascal }} extends BaseRestTest { + @Test() + public async shouldBeAbleToExecuteRestApiRequests({ request }: Context) { + const response = await request.get('/') + + response.assertStatusCode(200) + } +} diff --git a/templates/test.edge b/templates/test.edge index e1ef4a6..8db8c0d 100644 --- a/templates/test.edge +++ b/templates/test.edge @@ -1,5 +1,4 @@ -import { Test } from '@athenna/test' -import type { Context } from '@athenna/test/types' +import { Test, type Context } from '@athenna/test' export default class {{ namePascal }} { @Test() diff --git a/tests/stubs/commands/GreetCommand.ts b/tests/stubs/commands/GreetCommand.ts new file mode 100644 index 0000000..95c35c3 --- /dev/null +++ b/tests/stubs/commands/GreetCommand.ts @@ -0,0 +1,23 @@ +/** + * @athenna/core + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { BaseCommand, Argument } from '@athenna/artisan' + +export class GreetCommand extends BaseCommand { + @Argument() + public name: string + + public static signature(): string { + return 'greet' + } + + public async handle(): Promise { + this.logger.simple(`({bold,green} [ HELLO ${this.name.toUpperCase()}! ])\n`) + } +} diff --git a/tests/unit/testing/BaseCliTest.ts b/tests/unit/testing/BaseCliTest.ts new file mode 100644 index 0000000..89fc1ba --- /dev/null +++ b/tests/unit/testing/BaseCliTest.ts @@ -0,0 +1,16 @@ +import { ArtisanProvider } from '@athenna/artisan' +import { Test, type Context } from '@athenna/test' +import { BaseCliTest as InternalBaseCliTest } from '@athenna/core/testing/BaseCliTest' + +export default class BaseCliTest extends InternalBaseCliTest { + public artisanPath = Path.bin('artisan.ts') + + @Test() + public async shouldBeAbleToRunTestsExtendingBaseCliTestClass({ command }: Context) { + new ArtisanProvider().register() + const output = await command.run('greet lenon') + + output.assertSucceeded() + output.assertLogged('HELLO LENON!') + } +} diff --git a/tests/unit/testing/BaseRestTest.ts b/tests/unit/testing/BaseRestTest.ts new file mode 100644 index 0000000..16ebf23 --- /dev/null +++ b/tests/unit/testing/BaseRestTest.ts @@ -0,0 +1,17 @@ +import { Test, type Context } from '@athenna/test' +import { BaseRestTest as InternalBaseRestTest } from '@athenna/core/testing/BaseRestTest' +import type { HttpOptions } from '#src/index' + +export default class BaseRestTest extends InternalBaseRestTest { + public restOptions: HttpOptions = { + routePath: Path.stubs('routes/http.ts'), + } + + @Test() + public async shouldBeAbleToRunTestsExtendingBaseRestTestClass({ request }: Context) { + const response = await request.get('/hello') + + response.assertStatusCode(200) + response.assertBodyContains({ ok: true }) + } +}