diff --git a/docs/architecture-concepts/service-container.mdx b/docs/architecture-concepts/service-container.mdx index 306e73b0..f1560efc 100644 --- a/docs/architecture-concepts/service-container.mdx +++ b/docs/architecture-concepts/service-container.mdx @@ -50,22 +50,20 @@ itself. ## Simple resolution -If you use the `@Service` annotation, you can instruct the container how he -should resolve that class, or use the default resolution configurations of the -annotation. For example, you may place the following code in your -`Path.routes('http.ts')` file: +You may place the following code in your `Path.routes('http.ts')` +file: ```typescript import { Route } from '@athenna/http' -import { Service } from '@athenna/ioc' -@Service() export class AppService { public ok() { return { message: 'ok' } } } +ioc.bind('appService', AppService) + Route.get('/', ({ response }) => { const appService = ioc.use('appService') const body = appService.ok() @@ -91,11 +89,11 @@ develop without it. The example above is just to show that we can place our services anywhere in our application, without depending on configuration files or any other kind of -setup. We recommend you placing your services in a specifics directory to save -only your service classes and not inside your route file. A good place to put -your services is `app/services` directory, since `make:service` command will -save your services there. But of course you can do whatever you want with -Athenna 😎. +setup. We recommend you placing your services in specifics directory and not +inside your route file. A good place to put your services is `app/services` +directory, since `make:service` command will save your services there. +But remember that this is only a tip, at the end of the day you can do +whatever you want with Athenna 😎. ::: @@ -193,6 +191,49 @@ import { StringNormalizer } from '#app/helpers/StringNormalizer' ioc.instance('App/Helpers/StringNormalizer', new StringNormalizer()) ``` +### The `@Service()` annotation + +The `@Service()` annotation is just a helper that will map all the +configurations that a service needs to be registered in the container, +this means that this annotation does not have the responsibility to +bind your service in the container. + +Let's create a simple service to understand how this annotation works: + +```shell +./node artisan make:service StringNormalizer +``` + +The command above will create the `Path.services('StringNormalizer.ts')` +file and will automatically register the service in the `services` property +of the `.athennarc.json` file. + +Let's add some configuration to the `@Service()` annotation of +`StringNormalizer` class: + +```typescript title="Path.services('StringNormalizer.ts')" +import { Service } from '@athenna/ioc' + +@Service({ + type: 'singleton', + camelAlias: 'stringNormalizer', + alias: 'App/Services/StringNormalizer', +}) +export class StringNormalizer { + public run(value: string) { + return value.trim().toLowerCase() + } +} +``` + +When bootstrapping the Athenna application the `StringNormalizer` class +will be registered in the service container as a +[singleton](/docs/architecture-concepts/service-container#binding-a-singleton) +with the alias `App/Services/StringNormalizer` and the camel alias +`stringNormalizer`, that is typically used in +[automatic constructor injections](/docs/architecture-concepts/service-container#automatic-constructor-injection) +and also by [the `@Inject()` annotation](/docs/architecture-concepts/service-container#using-inject-annotation) when no alias is provided to it. + ## Resolving You may use the `use` or `safeUse` methods from the ioc global property to @@ -236,9 +277,9 @@ export class AppController { } ``` -### Using `@Inject` annotation +### Using `@Inject()` annotation -You can also use the `@Inject` annotation instead of the constructor. The +You can also use the `@Inject()` annotation instead of the constructor. The annotation follows the same logic of the constructor, you need to use the camelCase name of your dependency as the property name to be resolved properly: @@ -260,7 +301,7 @@ export class AppController { } ``` -When using the `@Inject` annotation you could also passes as argument a +When using the `@Inject()` annotation you could also pass as argument a specific alias to be resolved in the container: ```typescript diff --git a/docs/cli-application/commands.mdx b/docs/cli-application/commands.mdx index 5d610719..ac65f67a 100644 --- a/docs/cli-application/commands.mdx +++ b/docs/cli-application/commands.mdx @@ -245,7 +245,6 @@ your dependencies: #### ❌ Does not work ```typescript -import { Inject } from '@athenna/ioc' import { BaseCommand } from '@athenna/artisan' import { MailgunService } from '#app/services/MailgunService' @@ -273,9 +272,8 @@ export class SendEmails extends BaseCommand { #### ✅ Works ```typescript -import { Inject } from '@athenna/ioc' import { BaseCommand } from '@athenna/artisan' -import { MailgunService } from '#app/services/MailgunService' +import type { MailgunService } from '#app/services/MailgunService' export class SendEmails extends BaseCommand { public static signature(): string { @@ -286,13 +284,12 @@ export class SendEmails extends BaseCommand { return 'Send an email.' } - @Inject() - private mailgunService: MailgunService 👈 - public async handle(): Promise { + const mailgunService = ioc.safeUse('App/Services/MailgunService') + const msg = 'People reading this will have a wonderful day! 🥳' - await this.mailgunService.send(msg) + await mailgunService.send(msg) } } ``` @@ -454,7 +451,7 @@ export class Greet extends BaseCommand { Will output: -```typescript +```console hey lenon ``` diff --git a/docs/getting-started/athennarc-file.mdx b/docs/getting-started/athennarc-file.mdx index d341207b..caf7ba1e 100644 --- a/docs/getting-started/athennarc-file.mdx +++ b/docs/getting-started/athennarc-file.mdx @@ -276,7 +276,7 @@ Route facade: An array with the middlewares of your application. The middlewares registered in this array will be registered in the service container to be accessed easily by your -Route facade: +`Route` facade: ```json { @@ -287,9 +287,21 @@ Route facade: } ``` +Athenna expects that the middlewares set in this property +to be annotated with `@Middleware()`, `@Interceptor()` or `@Terminator()` +annotations, this is not mandatory, but you will only +be able to set the name of the middleware or if it is global or not using +the annotations. + +If you are not using TypeScript in your application, you can use the +[`namedMiddlewares`](/docs/getting-started/athennarc-file#the-namedmiddlewares-property) +property to register named middlewares and the +[`globalMiddlewares`](/docs/getting-started/athennarc-file#the-globalmiddlewares-property) +property to register global middlewares. + :::tip -More information about commands could be found at +More information about middlewares could be found at [http middlewares documentation section](https://athenna.io/docs/rest-api-application/middlewares). ::: @@ -329,3 +341,30 @@ in any request of your application: ] } ``` + +## The `artisan` property + +An object with a variety of Artisan configurations. +At this point the only configurations accepted are `artisan.child.executor` +and `artisan.child.path`. Both configurations are used to define +how the `Artisan.callInChild()` method will behave when no options +are set to it as second argument: + +```json +{ + "artisan": { + "child": { + "executor": "node --inspect", + "path": "./bin/artisan.ts" + } + } +} +``` + +:::tip + +Athenna will automatically parse the `artisan.child.path` using +the `Path.ext()` method, so you don't need to worry about if the +extension is `.js` or `.ts`. + +::: diff --git a/docs/rest-api-application/controllers.mdx b/docs/rest-api-application/controllers.mdx index 65404440..35ee7e65 100644 --- a/docs/rest-api-application/controllers.mdx +++ b/docs/rest-api-application/controllers.mdx @@ -125,7 +125,8 @@ is used to resolve all Athenna controllers. As a result, you are able to use any dependencies your controller may need using `@Inject()` annotation or in its constructor. The declared dependencies will automatically be resolved -and injected into the controller instance: +and injected into the controller instance when receiving +a request from the server: ```typescript import { Inject } from '@athenna/ioc' diff --git a/docs/rest-api-application/middlewares.mdx b/docs/rest-api-application/middlewares.mdx index de103230..a14a8da6 100644 --- a/docs/rest-api-application/middlewares.mdx +++ b/docs/rest-api-application/middlewares.mdx @@ -116,7 +116,7 @@ export class EnsureApiKeyIsValid implements MiddlewareContract { } ``` -If you are not using TypeScript you can use the +If you are not using TypeScript, you can use the `globalMiddlewares` array in your `.athennarc.json` file: ```json @@ -149,7 +149,7 @@ export class EnsureApiKeyIsValid implements MiddlewareContract { } ``` -If you are not using TypeScript you can use the +If you are not using TypeScript, you can use the `namedMiddlewares` object in your `.athennarc.json` file: ```json diff --git a/docs/rest-api-application/rate-limiting.mdx b/docs/rest-api-application/rate-limiting.mdx index 63167df5..057669a6 100644 --- a/docs/rest-api-application/rate-limiting.mdx +++ b/docs/rest-api-application/rate-limiting.mdx @@ -6,7 +6,7 @@ description: See how to create rate limiting rules for Athenna REST API applicat # Rate Limiting -See how to create rate limiting rules for Athenna REST API application. +See how to create rate-limiting rules for Athenna REST API application. ## Introduction @@ -123,14 +123,21 @@ consult the [Meta article about rate limiters](https://developers.facebook.com/d ## Disabling rate limiting -The `HttpKernel` class will automatically disable the -plugin registration if the package does not exist, so -to disable rate limiting in Athenna you need to -remove the `@fastify/rate-limit` package from your -application: +The `HttpKernel` class will automatically disable the +plugin registration if the package does not exist, so +to disable rate-limiting in Athenna you need to remove the +`@fastify/rate-limit` package from your application: ```shell npm remove @fastify/rate-limit ``` +You can also disable by setting `http.rateLimit.enabled` to `false`: +```typescript title="Path.config('http.ts')" +export default { + rateLimit: { + enabled: false + } +} +``` diff --git a/docs/rest-api-application/security-with-helmet.mdx b/docs/rest-api-application/security-with-helmet.mdx index 390b3695..17e011ee 100644 --- a/docs/rest-api-application/security-with-helmet.mdx +++ b/docs/rest-api-application/security-with-helmet.mdx @@ -99,14 +99,24 @@ Route.resource('/tests', 'WelcomeController').helmet('index', {...}) Route.resource('/tests', 'WelcomeController').helmet('store', {...}) ``` -## Disabling helmet +## Disabling Helmet -The `HttpKernel` class will automatically disable the -plugin registration if the package does not exist, so -to disable helmet in Athenna you need to -remove the `@fastify/helmet` package from your +The `HttpKernel` class will automatically disable the +plugin registration if the package does not exist, so +to disable helmet in Athenna you need to +remove the `@fastify/helmet` package from your application: ```shell npm remove @fastify/helmet ``` + +You can also disable by setting `http.helmet.enabled` to `false`: + +```typescript title="Path.config('http.ts')" +export default { + helmet: { + enabled: false + } +} +``` diff --git a/docs/rest-api-application/swagger-documentation.mdx b/docs/rest-api-application/swagger-documentation.mdx index 8757ed6c..86a361f9 100644 --- a/docs/rest-api-application/swagger-documentation.mdx +++ b/docs/rest-api-application/swagger-documentation.mdx @@ -137,12 +137,22 @@ Route.resource('/tests', 'WelcomeController').swagger('store', {...}) ## Disabling Swagger -The `HttpKernel` class will automatically disable the -plugin registration if the package does not exist, so -to disable Swagger in Athenna you need to remove the -`@fastify/swagger` and `@fastify/swagger-ui` packages from your +The `HttpKernel` class will automatically disable the +plugin registration if the package does not exist, so +to disable Swagger in Athenna you need to remove the +`@fastify/swagger` and `@fastify/swagger-ui` packages from your application: ```shell npm remove @fastify/swagger @fastify/swagger-ui ``` + +You can also disable by setting `http.swagger.enabled` to `false`: + +```typescript title="Path.config('http.ts')" +export default { + swagger: { + enabled: false + } +} +``` diff --git a/docs/rest-api-application/tracing-requests.mdx b/docs/rest-api-application/tracing-requests.mdx index 2eb2beb1..7abe1727 100644 --- a/docs/rest-api-application/tracing-requests.mdx +++ b/docs/rest-api-application/tracing-requests.mdx @@ -70,15 +70,17 @@ to disable tracer in Athenna you need to remove the npm remove cls-rtracer ``` -You can also disable the tracer by setting `http.trace` to `false`: +You can also disable by setting `http.rTracer.enabled` to `false`: ```typescript title="Path.config('http.ts')" export default { - trace: false + rTracer: { + enabled: false + } } ``` -Or by setting the `tracer` option as `false` when booting the +Or by setting the `trace` option as `false` when booting the server in `Ignite.httpServer()` method: ```typescript title="Path.bootstrap('main.ts')" diff --git a/docs/testing/mocking.mdx b/docs/testing/mocking.mdx index a3e757e6..3a164ef2 100644 --- a/docs/testing/mocking.mdx +++ b/docs/testing/mocking.mdx @@ -10,4 +10,90 @@ Understand how to mock dependencies and functions in Athenna. ## Introduction -Coming soon +When testing Athenna applications, you may wish to "mock" +certain aspects of your application so they are not actually +executed during a given test. For example, when testing a +controller that calls a service, you may wish to mock the +service, so they are not actually executed during the test. +This allows you to only test the controller's HTTP response +without worrying about the execution of the service since +the service can be tested in their own test case. + +## Mocking objects + +Coming soon... + +## Mocking services + +Mocking a single service method could be a difficult thing to do +if your service is not going to be registered as a singleton in your +application. The best way to mock a service is by instantiating +a new instance of the service, registering it in the container +and then mocking the method you want in each test case: + +```typescript +import { AppService } from '#app/services/AppService' +import { BaseRestTest } from '@athenna/core/testing/BaseRestTest' +import { Test, type Context, BeforeAll, Mock, AfterEach } from '@athenna/test' + +export default class AppControllerTest extends BaseRestTest { + public appService = new AppService() + + @BeforeAll() + public async beforeAll() { + ioc.instance('App/Services/AppService', this.appService) + } + + @AfterEach() + public async afterEach() { + Mock.restoreAll() + } + + @Test() + public async shouldBeAbleToMock({ request }: Context) { + const mock = Mock.when(this.appService, 'findOne').return({ fake: true }) + + const response = await request.get('/api/v1') + + response.assertStatusCode(200) + response.assertBodyContains({ fake: true }) + assert.called(mock) + } +} +``` + +### Replacing the entire service + +If you prefer you can also replace the entire service with +a mocked version of your service using the `ioc.fake()` method + +```typescript +import { Test, BeforeAll, type Context } from '@athenna/test' +import { AppServiceMock } from '#tests/fixtures/AppServiceMock' +import { BaseRestTest } from '@athenna/core/testing/BaseRestTest' + +export default class AppControllerTest extends BaseRestTest { + @BeforeAll() + public beforeAll() { + ioc + .fake('App/Services/AppService', AppServiceMock) + .alias('appService', 'App/Services/AppService') + } + + @Test() + public async shouldBeAbleToMock({ request }: Context) { + const response = await request.get('/api/v1') + + response.assertStatusCode(200) + response.assertBodyContains({ fake: true }) + } +} +``` + +### Using the `@InjectMock()` annotation + +Coming soon... + +## Mocking facades + +Coming soon...