diff --git a/README.md b/README.md index 65bdb4c..0bc6319 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-# Custom Plugin Node Library +# Mia service Node.js Library [![Build Status][travis-svg]][travis-org] [![javascript style guide][standard-mia-svg]][standard-mia] @@ -10,30 +10,91 @@
-**The Mia-Platform Plugin Node Library** - -This library is intented to ease the creation of new services to deploy +This library is intended to ease the [creation of new services](https://docs.mia-platform.eu/development_suite/api-console/api-design/services/) to deploy on [Mia-Platform][mia-platform]. -Is highly copled with [`lc39`][lc39] and on [Fastify][fastify]. +Built on [`Fastify`][fastify], it takes advantage of Mia-Platform Node.js service launcher [`lc39`][lc39]. + +# Getting Started +You can use this module in your projects or, from the [DevOps Console](https://docs.mia-platform.eu/development_suite/overview-dev-suite/), get started quickly and easily with a [ready-to-use microservice template](https://docs.mia-platform.eu/development_suite/api-console/api-design/custom_microservice_get_started/). Even in the [Mia-Platform Marketplace](https://github.com/mia-platform-marketplace) repository, you can find some examples and templates that using this library. -## Getting Started +## Setup the local development environment -### Install +To develop the service locally you need: +- Node.js v12 or later. -To install the package you can run: +To setup node.js, we suggest using [nvm][nvm], so you can manage multiple versions easily. +Once you have installed nvm, you can go inside the directory of the project and simply run +`nvm install`, the `.nvmrc` file will install and select the correct version +if you don’t already have it. + +Once you have all the dependency in place, you can launch: +```shell +npm i +npm run coverage +``` + +These two commands, will install the dependencies and run the tests with the coverage report that you can view as an HTML +page in `coverage/lcov-report/index.html`. + +## Install module + +To install the package with npm: ```sh -npm install @mia-platform/custom-plugin-lib --save +npm i @mia-platform/custom-plugin-lib --save ``` +To install with Yarn: + +```sh +yarn add @mia-platform/custom-plugin-lib +``` +## Define a Custom Service + +You can define a new Custom Service that integrates with the platform simply writing this: +```js +const customService = require('@mia-platform/custom-plugin-lib')() + +module.exports = customService(async function helloWorldService(service) { + service.addRawCustomPlugin('GET', '/hello', function handler(request, reply) { + request.log.trace('requested myuser') + // if the user is not logged, this method returns a falsy value + const user = request.getUserId() || 'World' + reply.send({ + hello: `${user}!`, + }) + }) +}) +``` +- The library exports a function which creates the infrastructure ready to accept the definition of routes and decorators. Optionally can take a schema of the required environment variables, you can find the reference [here][fastify-env]. The function returned, `customService`, expects an async function to initialize and configure the `service`. +- `service` is a [Fastify instance](https://www.fastify.io/docs/latest/Server/), that is decorated by the library to help you interact with Mia-Platform resources. You can use *service* to register any Fastify routes, custom decorations and plugin, see [here][fastify-ecosystem] for a list of currently available plugins. + +- `addRawCustomPlugin` is a function that requires the HTTP method, the path of the route and a handler. The handler can also be an [async function](https://www.fastify.io/docs/latest/Routes/#async-await). +Optionally you can indicate the JSONSchemas to validate the querystring, the parameters, the payload and the response. -### Examples +To get more info about Custom Services can you look at the [related section](./docs/CustomService.md). -Defining a new service that integrate with the platform is as simple as in this -[example](examples/basic/index.js). -Please see also a more [advanced example](examples/advanced/index.js) to see how to require -more environment variables, and to specify schema definitions for validation and swagger documentation. +## Environment Variables configuration +To works correctly, this library needs some specific environment variables: -For using one of the two example provided you can move to one of the two directories and run: +* `USERID_HEADER_KEY` +* `USER_PROPERTIES_HEADER_KEY` +* `GROUPS_HEADER_KEY` +* `CLIENTTYPE_HEADER_KEY` +* `BACKOFFICE_HEADER_KEY` +* `MICROSERVICE_GATEWAY_SERVICE_NAME` + +When creating a new service from Mia-Platform DevOps Console, they come already defined but you can always change or add them anytime as described [in the DevOps console documentation](https://docs.mia-platform.eu/development_suite/api-console/api-design/services#environment-variable-configuration). +In local, the environment variables are defined +in this [file](examples/default.env). + +Other variables can be specified by setting your envSchema when calling the plugin. + +## Examples +You can see an [advanced example](examples/advanced/) to see different use cases of the library. + +To see other examples of library use you can visit [GitHub dependant repository graph](https://github.com/mia-platform/custom-plugin-lib/network/dependents?package_id=UGFja2FnZS00NTc2OTY4OTE%3D) and check all packages that depends on it. + +To run the [examples](examples) directly you can move to specific example folder and run: ```sh npm run start:local @@ -42,71 +103,31 @@ npm run start:local This command will launch the service on `localhost:3000` with the environment variables defined in this [file](examples/default.env). Now you can consult the swagger documentation of the service at -[this](http://localhost:3000/documentation/) address. +[http://localhost:3000/documentation/](http://localhost:3000/documentation/). -### Local Development +# How to -To develop the service locally you need: -- Node 8+ +* Create a Custom Service +* Declare routes +* Add decorators +* Call the other services on the Platform project +* API documentation +* Testing +* Logging -To setup node, please if possible try to use [nvm][nvm], so you can manage multiple versions easily. -Once you have installed nvm, you can go inside the directory of the project and simply run -`nvm install`, the `.nvmrc` file will install and select the correct version -if you don’t already have it. +[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -Once you have all the dependency in place, you can launch: -```shell -npm i -npm run coverage -``` +[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -This two commands, will install the dependencies and run the tests with the coverage report that you can view as an HTML -page in `coverage/lcov-report/index.html`. +[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -## `rawCustomPlugin` -Defining a custom plugin that uses the platform’s services is as simple as -in this [helloWorld](examples/basic/helloWorld.js) example. -This library exports a function that optionally takes a schema of the required environment variables -(you can find the reference [here][fastify-env]). -This function returns a `customService` function, that expects an async function to initialize and configure -the service (provided as the only argument). This service is a [fastify][fastify] instance, -that also contains the method `addRawCustomPlugin`, that allows you to add a route. -Multiple routes can be added in this way. -For each route you have to specify an async handler. You should always return something (strings, objects, streams), -unless you set the response status code to 204. Please read [here][fastify-async] for further information -about async handlers. -You must also specify the http method, and the path of the hander. Optionally you can indicate the JSONSchemas -to validate the querystring, the parameters, the payload, the response. -In addition to validation, you will also have a swagger documentation available at the `/documentation/` path. - -Thanks to TypeScript's type definitions, editors can actually provide autocompletion for the additional methods -of the request object such as `getUserId` or `getGroups` or `getUserProperties` - -In the async initialization function you can also access the `fastify` instance, so you can register any plugin, -see [here][fastify-ecosystem] for a list of currently available plugins. -In addition, you can register additional [`content-type` parsers][fastify-parsers]. - -NB: the fifth parameter of `rawCustomPlugin` should be used wisely. See tests for that. - -## Testing -CustomPlugin expose getDirectServiceProxy and getServiceProxy for testing purpose: -### getDirectServiceProxy -Import the function in you test: -``` javascript -const { getDirectServiceProxy } = require('@mia-platform/custom-plugin-lib') -const myServiceProxy = getDirectServiceProxy(MY_SERVICE_NAME) - ``` -all the options accepted by the getDirectServiceProxy can be passed (es: `{ port: CUSTOM_PORT }`). - -### getServiceProxy -It need the MICROSERVICE_GATEWAY_SERVICE_NAME so you need to pass it like this: -``` javascript -const { getServiceProxy } = require('@mia-platform/custom-plugin-lib') -const myServiceProxy = getServiceProxy(MICROSERVICE_GATEWAY_SERVICE_NAME) -``` -## Configuration -To use the library, you should specify the environment variables listed [here](index.js#L22), -other variables can be specified by setting your envSchema when calling the plugin. +[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean + +[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise + +[22]: https://developer.mozilla.org/docs/Web/API/Comment/Comment + +[23]: https://daringfireball.net/projects/markdown/ [travis-svg]: https://travis-ci.org/mia-platform/custom-plugin-lib.svg?branch=master [travis-org]: https://travis-ci.org/mia-platform/custom-plugin-lib diff --git a/docs/ApiDoc.md b/docs/ApiDoc.md new file mode 100644 index 0000000..120a8ac --- /dev/null +++ b/docs/ApiDoc.md @@ -0,0 +1,51 @@ +# API documentation +Services developed with this library automatically also exposes the documentation of the routes and decorators that +are implemented. The documentation is specified using the [OpenAPI 2.0 standard](https://swagger.io/specification/v2/) +and exhibited through [Swagger](https://swagger.io). + +Once the service is started, its documentation can be accessed at +route [`http://localhost:3000/documentation`](http://localhost:3000/documentation). + +The specification of the request scheme +and responses to a route must conform to the format accepted by +[Fastify](https://www.fastify.io/docs/latest/Validation-and-Serialization). + +## Example +```js +const customService = require('@mia-platform/custom-plugin-lib')() + +const schema = { + body: { + type: 'object', + properties: { + someKey: { type: 'string' }, + someOtherKey: { type: 'number' } + } + }, + + querystring: { + name: { type: 'string' }, + excitement: { type: 'integer' } + }, + + params: { + type: 'object', + properties: { + par1: { type: 'string' }, + par2: { type: 'number' } + } + }, + + headers: { + type: 'object', + properties: { + 'x-foo': { type: 'string' } + }, + required: ['x-foo'] + } +} + +module.exports = customService(async function exampleService(service) { + service.addRawCustomPlugin('GET', '/endpoint', function handler(request,reply) { ... }, schema) +}) +``` \ No newline at end of file diff --git a/docs/CustomService.md b/docs/CustomService.md new file mode 100644 index 0000000..010a494 --- /dev/null +++ b/docs/CustomService.md @@ -0,0 +1,30 @@ +# Custom Service +A Custom Microservice is a service that receives HTTP requests, whose cycle of use and deploy is managed by the platform. A Custom Microservice encapsulates ad-hoc business logics that can be developed by any user of the platform. To know how manage your services in the DevOps Console see the [documentation](https://docs.mia-platform.eu/development_suite/api-console/api-design/services/) + +The library exports a function which creates the infrastructure ready to accept the definition of routes and decorators. +The function optionally can take a schema of the required environment variables (you can find the reference [fastify-env](https://github.com/fastify/fastify-env). + +## Example + +```js +const customService = require('@mia-platform/custom-plugin-lib')({ + type: 'object', + required: ['ENV_VAR'], + properties: { + ENV_VAR: { type: 'string' }, + }, +}) +``` +> **_More examples?_** Go [here](../examples/advanced/index.js#L46) to see another use cases. + +You can configure the environment variables from DevOps console, in your service configuration. For further detail see [Mia-Platform documentation](https://docs.mia-platform.eu/development_suite/api-console/api-design/services/#environment-variable-configuration). + +The function expects an async function to initialize and configure the `service`, a [Fastify instance](https://www.fastify.io/docs/latest/Server/). +You can access the environment variables values from `service.config`: +```js +module.exports = customService(async function handler(service) { + const { ENV_VAR } = service.config + ... +}) +``` +Upon `service`, you can you can add [routes](Routes.md) and [decorators](Decorators.md). diff --git a/docs/Decorators.md b/docs/Decorators.md new file mode 100644 index 0000000..5dbb9f2 --- /dev/null +++ b/docs/Decorators.md @@ -0,0 +1,116 @@ +# Add decorators + +Decorators are particular endpoint that a microservice can expose. Using the DevOps console you can manage your decorators and link them to your endpoint routes. Check out [decorators documentation](https://docs.mia-platform.eu/development_suite/api-console/api-design/decorators/) for further detail on their usage and management. + +Decorators allow you to perform custom actions upon specific API handler invocations. There are three types of decorators: + + * **PRE**: invoked *before* the configured route handler. + * **POST**: invoked *after* the successful execution of configured route handler (a 2xx status code is returned). + * **CATCH**: invoked after the failure of the configured route handler (any other error status code, 4xx or 5xx). + +You can add a decorator with these methods: +* ```addPreDecorator(path, handler)``` +* ```addPostDecorator(path, handler)``` + +whose arguments are, in order: + +* `path` - the route path (e.g.,` /status /alive`). +* `handler`- function that contains the actual behavior. It must respect the same interface defined in the +documentation of the handlers of [fastify](https://www.fastify.io/docs/latest/Routes/#routes-config). + +## PRE decorators +```js +const customService = require('@mia-platform/custom-plugin-lib')() + +module.exports = customService(async function handler(service) { +service.addPreDecorator('/checkwho', function checkWhoHandler(request) { + const defaultWho = 'John Doe' + const body = request.getOriginalRequestBody() + const { who } = body + const newBody = { + ...body, + who: who || request.getUserId() || defaultWho, + } + // Set original request with retrieved data, the target microservice will receive your newly defined body. + return request.changeOriginalRequest().setBody(newBody) + }) +}) +``` +The first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is decorated as the `addRawCustomPlugin` method, in addition, with the following methods: +* `getOriginalRequest()` - returns the original request. +* `getOriginalRequestMethod()` - returns the original request HTTP method. +* `getOriginalRequestPath()` - returns the path of the original request. +* `getOriginalRequestHeaders()` - returns the headers of the original request. +* `getOriginalRequestQuery()` - returns the querystring of the original request. +* `getOriginalRequestBody()` - returns the body of the original request. +* `changeOriginalRequest()`- returns a builder object with following methods: + * `setBody(newBody)` - modify the body of the original request. + * `setHeaders(newHeaders)` - modify the headers of the original request. + * `setQuery(newPath)` - modify the querystring of the original request. +* `leaveOriginalRequestUnmodified` - leave the original request unmodified . + +## POST and CATCH decorators +```js +... +/* + POST decorator +*/ +service.addPostDecorator('/notify', function notifyHandler(request) { + // Get "notifications" setting from the request querystring + const { notifications } = request.getOriginalRequestQuery() + if(!notifications) { + // It's not necessary to send notification + // leave the original response unmodified + return req.leaveOriginalResponseUnmodified() + } + + const notifier = new Notifier() + // Try to send a notification + const response = await notifier.send({ text: `${who} says: ${mymsg}`}) + // Adds to original response body the time of notification send + return request.changeOriginalResponse().setBody( + { ...req.getOriginalRequestBody(),notifySendedAt:new Date() } + ) + +/* + CATCH decorator +*/ +service.addPostDecorator('/catch', function exceptionHandler(request) { + return request.changeOriginalResponse() + .setBody({msg:"Error"}) + .setStatusCode(500) +}) +``` + +Even in this case the first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is decorated as the `addRawCustomPlugin` method, in addition, with the following methods: +* `getOriginalRequest()` - returns the original request. +* `getOriginalRequestMethod()` - returns the original request method. +* `getOriginalRequestPath()` - returns the path of the original request. +* `getOriginalRequestHeaders()` - returns the headers of the original request. +* `getOriginalRequestQuery()` - returns the querystring of the original request. +* `getOriginalRequestBody()` - returns the body of the original request. + +Related to the original response: + +* `getOriginalResponseBody()` - returns the body of the original response. +* `getOriginalResponseHeaders()` - returns the headers of the original response. +* `getOriginalResponseStatusCode()` - returns the status code of the original response. +* `changeOriginalResponse()`- returns a builder object with following methods: + * `setBody(newBody)` - modify the body of the original response. + * `setHeaders(newHeaders)` - modifies the headers of the original response. + * `setStatusCode(newCode)` - changes the status code of the original response. +* `leaveOriginalResponseUnmodified` - leaves the original response unmodified. + +## Abort chain +To abort the decorator chain, you can call on the `request` the method: + + `abortChain(finalStatusCode, finalBody, finalHeaders)` + + whose arguments are, in order + +* `finalStatusCode` - the final returned status code. +* `finalBody` - the final returned body. +* `finalHeaders` - the final returned headers. + + +> **_More examples?_** Go [here](../examples/advanced/index.js) to see another decorators implementations. diff --git a/docs/HTTPClient.md b/docs/HTTPClient.md new file mode 100644 index 0000000..c737c14 --- /dev/null +++ b/docs/HTTPClient.md @@ -0,0 +1,69 @@ +# Call the other services on the Platform project +You can call any service or any endpoint defined on the Platform project, obtaining and using a proxy object. + +For example, if you need to connect to a CRUD, you have to use a Proxy towards the `crud-service`. + +You can get a proxy calling these methods both on `Request`(the first argument of handler) and `Service` (the Fastify instance): + +* `getServiceProxy(options)` - returns a proxy passing through the [Microservice Gateway](https://docs.mia-platform.eu/runtime_suite/microservice-gateway/). + * `options` - is an object with the following optional fields: + * `port` - an integer that identifies the port of the service to be queried. + * `protocol` - a string that identifies the protocol to use (only `http` and `https` are supported, the default value is `http`). + * `headers` - an object that represents the set of headers to send to the service; + * `prefix` - a string representing the prefix of the service call path. +* `getDirectServiceProxy(serviceName, options)` - returns a direct proxy to the service. + * `serviceName` - The name of the service to call. You can't specify the port here, you have to do in `options.port`. + * `options` - The same options described above. + +Potentially, the `getDirectServiceProxy` method allows you to also query services outside the Platform. In this case, however, it is necessary to bear in mind that the platform headers will be automatically forwarded. + +Both proxies, by default, forward the four Mia headers to the service called. In addition, other headers of the original request can also be forwarded to the named service. To do this it is necessary to define an additional environment variable, `ADDITIONAL_HEADERS_TO_PROXY`, whose value must be a string containing the keys of the headers to be forwarded separated by a comma. + +Both proxies expose the methods to perform a specific HTTP request to service. + + * `get(path, querystring, options)` + * `post(path, body, querystring, options)` + * `put(path, body, querystring, options)` + * `patch(path, body, querystring, options)` + * `delete(path, body, querystring, options)` + +The params to be passed to these functions are: + + * `path` - a string that identifies the route to which you want to send the request. + * `body` - optional, the body of the request which can be: + * a JSON object + * a [Buffer](https://nodejs.org/api/buffer.html#) + * one [Stream](https://nodejs.org/api/stream.html) + * `querystring` - optional, an object that represents the querystring. + * `options` - optional, an object that admits all the` options` listed above for the `getServiceProxy` and` getDirectServiceProxy` methods (which will eventually be overwritten), plus the following fields: + * `returnAs` - a string that identifies the format in which you want to receive the response. It can be `JSON`,` BUFFER` or `STREAM`. Default `JSON`. + * `allowedStatusCodes` - an array of integers that defines which status codes of the response are accepted. If the response status code is not contained in this array, the promise will be rejected. If this parameter is omitted, the promise is resolved in any case (even if the interrogated server answers 500). + * `isMiaHeaderInjected` - a boolean value that identifies whether Mia's headers should be forwarded in the request. Default `true`. + +All methods return a *Promise object*. + +## Examples +```js +// Example of a request towards `tokens-collection` endpoint passing through Microservice Gateway +async function tokenGeneration(request, response) { + const crudProxy = request.getServiceProxy() + const result = await crudProxy + .post('/tokens-collection/', { + id: request.body.quotationId, + valid: true + }) + // ... +} +``` +```js +// and bypassing Microservice Gateway +async function tokenGeneration(request, response) { + const crudProxy = request.getDirectServiceProxy('crud-service') + const result = await crudProxy + .post('/tokens-collection/', { + id: request.body.quotationId, + valid: true + }) + // ... +} +``` diff --git a/docs/Logging.md b/docs/Logging.md new file mode 100644 index 0000000..1f23d0b --- /dev/null +++ b/docs/Logging.md @@ -0,0 +1,41 @@ +# Logging +You can log a message to see in DevOps console. The library use the [Fastify logging system](https://www.fastify.io/docs/v2.0.x/Logging/), that is based on [pino](https://github.com/pinojs/pino). + +To log messages call these methods on the logger instance of request. Logging is enabled by default. Therefore you can call on `request.log` or `service.log`: +* `debug()` +* `info()` +* `warn()` +* `error()` +* `fatal()` + +Each method creates a log with the homonym level. + +By default the library will generate two logs for each request, one representing the incoming request and one for the request completion, logs are created with *trace* and *info* levels respectively and already provide useful information for later analysis or debugging. If you need more, you can add your logs. + +## Example +```js +service.addPostDecorator('/notify', function notifyHandler(request) { + // Get "notifications" setting from the request querystring + const { notifications } = request.getOriginalRequestQuery() + if(!notifications) { + return req.leaveOriginalResponseUnmodified() + } + + try { + const notifier = new Notifier() + const response = await notifier.send({ text: `${who} says: ${mymsg}`}) + const sendedAt = new Date(); + + // Log at "INFO" level + req.log.info({ statusCode: response.statusCode }, 'Notify sent') + + return request.changeOriginalResponse().setBody( + { ...req.getOriginalRequestBody(), notifySendedAt:sendedAt} + ) + } catch (error) { + // Log at "ERROR" level + req.log.error('Error sending notification', error) + } +) +``` +For further detail about logs can you see the [guidelines for logs](https://docs.mia-platform.eu/development_suite/monitoring-dashboard/dev_ops_guide/log/). diff --git a/docs/Routes.md b/docs/Routes.md new file mode 100644 index 0000000..6cdf3f2 --- /dev/null +++ b/docs/Routes.md @@ -0,0 +1,58 @@ +# Declare routes + +You can define the behaviour of the Custom Microservice in response to an HTTP request by declaring the routes. For this purpose, you can use the `addRawCustomPlugin` method: + +```js +service.addRawCustomPlugin(httpVerb, path, handler, schema) +``` +whose arguments are, in order + +* `httpVerb` - the HTTP verb of the request (e.g.,` GET`). +* `path` - the route path (e.g.,` /status /alive`). +* `handler` - function that contains the actual behavior. It must respect the same interface defined in the +documentation of the handlers of [fastify](https://www.fastify.io/docs/latest/Routes/#async-await). +* `schema` - definition of the request and response data schema. +The format is the one accepted by [fastify](https://www.fastify.io/docs/latest/Validation-and-Serialization). To further detail see [`related section`](ApiDoc.md). + + +To get more info about how to declare a route can you look at the related [Fastify documentation](https://github.com/fastify/fastify/blob/master/docs/Routes.md). + +## Example +```js +const customService = require('@mia-platform/custom-plugin-lib')() + +module.exports = customService(async function handler(service) { + service.addRawCustomPlugin('GET', '/hello', function helloHandler(request, reply) { + const user = request.getUserId() || 'World' + + reply.send({ + hello: `${user}!`, + }) + }) +}) +``` +> **_More examples?_** Go [here](../examples/advanced/index.js#L86) to see another `addRawCustomPlugin` uses case. + +- The first parameter of the handler function is [Request](https://www.fastify.io/docs/latest/Request/). The request is automatically decorated, indeed we can call `request.getUserId()`. + + The instance of `Request` is decorated with functions: + + * `getUserId()` - exposes the user's id, if logged in or `null`. + * `getGroups()` - exposes an array containing strings that identify the groups to which the logged-in user belongs. + * `getClientType()` - exposes the type of client that performed the HTTP request. + * `isFromBackOffice()` - exposes a boolean to discriminate whether the HTTP request from the CMS. + * `getMiaHeaders()` - exposes an object with all previous information. + The set of this data is called `Mia headers` and getting the values from the following environment variables: + * *USERID_HEADER_KEY* + * *GROUPS_HEADER_KEY* + * *CLIENTTYPE_HEADER_KEY* + * *BACKOFFICE_HEADER_KEY* + +- The second parameter is a [Reply instance](https://www.fastify.io/docs/latest/Reply/). Use this object to reply to the request. Its main methods are the following: + * `headers(object)` - sets the headers of the response. + * `code(statusCode)` - sets the HTTP status code of the response. + * `send(data)` - sends the payload `data` to the end user. + +- Inside the handler scope it's possible to access Fastify instance using `this`. + + diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..80ac8b4 --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,60 @@ +# Testing +`Mia service Node.js Library` is built on Fastify and therefore integrates with [testing tools](https://www.fastify.io/docs/latest/Testing/) +made available by the framework. A complete example of this type of test is available [here](../examples/advanced/tests/). + +CustomPlugin directly expose `getDirectServiceProxy` and `getServiceProxy` for testing purpose. +Ypu can import the function in you test in ths way: +``` javascript +const { getDirectServiceProxy } = require('@mia-platform/custom-plugin-lib') +const { getServiceProxy } = require('@mia-platform/custom-plugin-lib') + +const myServiceProxy = getDirectServiceProxy(serviceName,otions) +const myServiceProxy = getServiceProxy(options) + + ``` +## Integration and Unit test + +The testing of service can be performed at multiple levels of abstraction. One possibility is to use a technique called _fake http injection_ for which it is possible to simulate +receiving an HTTP request. In this way, all the service logic is exercised from the HTTP layer to the handlers. This pattern is an example of Integration Testing. + +### Example Integration Test + +In the example below the test framework [Mocha](https://mochajs.org/). + +```js +'use strict' + +const assert = require('assert') +const fastify = require('fastify') + +const customPlugin = require('@mia-platform/custom-plugin-lib')() +const index = customPlugin(async service => { + service.addRawCustomPlugin( + 'GET', + '/status/alive', + async (request, reply) => ({ + status: 'ok' + }) + ) +}) + +const createTestServer = () => { + // Silent => trace for enabliing logs + const createdServer = fastify({ logger: { level: 'silent' } }) + + createdServer.register(index) + return createdServer +} + +describe('/status/alive', () => { + it('should be available', async () => { + const server = createTestServer() + + const response = await server.inject({ + url: '/status/alive', + }) + + assert.equal(response.statusCode, 200) + }) +}) +```