diff --git a/.gitignore b/.gitignore index fe2253c..db71f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist coverage .env build + +.tool-versions diff --git a/__tests__/date/date.test.ts b/__tests__/date/date.test.ts index 3074f6b..e40c075 100644 --- a/__tests__/date/date.test.ts +++ b/__tests__/date/date.test.ts @@ -76,4 +76,3 @@ describe('getDiffDays', () => { expect(getDiffDays(param.date)).toBe(param.output) }) }) - diff --git a/__tests__/entitySchema/index.test.ts b/__tests__/entitySchema/index.test.ts index 7024801..fee6549 100644 --- a/__tests__/entitySchema/index.test.ts +++ b/__tests__/entitySchema/index.test.ts @@ -1,4 +1,4 @@ -import { EntitySchema } from '../../src/entitySchema'; +import { EntitySchema } from '../../src/entitySchema' describe('assembleEntitySchema', () => { const validDefaultsObjects = [ @@ -6,15 +6,15 @@ describe('assembleEntitySchema', () => { solicitation: { appId: true, jsonData: { - address: true, - }, + address: true + } }, entitySchema: { appId: { type: 'string', occult: false, required: true, - label: 'Portal', + label: 'Portal' }, jsonData: { type: 'object', @@ -26,7 +26,7 @@ describe('assembleEntitySchema', () => { type: 'string', occult: false, required: true, - label: 'Ocorrência', + label: 'Ocorrência' }, address: { type: 'object', @@ -38,17 +38,17 @@ describe('assembleEntitySchema', () => { type: 'string', occult: false, required: true, - label: 'Estado', + label: 'Estado' }, city: { type: 'string', occult: false, required: true, - label: 'Cidade', - }, - }, - }, - }, + label: 'Cidade' + } + } + } + } }, test: { type: 'array', @@ -59,16 +59,16 @@ describe('assembleEntitySchema', () => { type: 'string', occult: false, required: true, - label: 'Ocorrência', - }, - }, + label: 'Ocorrência' + } + } }, returnExpected: { appId: { type: 'string', occult: false, required: true, - label: 'Portal', + label: 'Portal' }, jsonData: { type: 'object', @@ -86,46 +86,46 @@ describe('assembleEntitySchema', () => { type: 'string', occult: false, required: true, - label: 'Estado', + label: 'Estado' }, city: { type: 'string', occult: false, required: true, - label: 'Cidade', - }, - }, - }, - }, - }, - }, + label: 'Cidade' + } + } + } + } + } + } }, { solicitation: { - jsonData: true, + jsonData: true }, entitySchema: { appId: { type: 'string', occult: false, required: true, - label: 'Portal', - }, + label: 'Portal' + } }, - returnExpected: {}, + returnExpected: {} }, { solicitation: { - appId: true, + appId: true }, entitySchema: undefined, - returnExpected: {}, + returnExpected: {} }, { solicitation: { test: { - children1: true, - }, + children1: true + } }, entitySchema: { test: { @@ -138,51 +138,51 @@ describe('assembleEntitySchema', () => { type: 'string', occult: false, required: true, - label: 'teste', + label: 'teste' }, children2: { type: 'string', occult: false, required: true, - label: 'teste', - }, - }, - }, + label: 'teste' + } + } + } }, returnExpected: { test: { contentArray: { children1: { - label: "teste", + label: 'teste', occult: false, required: true, - type: "string", + type: 'string' }, children2: { - label: "teste", + label: 'teste', occult: false, required: true, - type: "string", - }, + type: 'string' + } }, contentObject: { children1: { - label: "teste", + label: 'teste', occult: false, required: true, - type: "string", - }, + type: 'string' + } }, - label: "teste", + label: 'teste', occult: false, required: true, - type: "array", - }, - }, - }, - ]; + type: 'array' + } + } + } + ] test.each(validDefaultsObjects)('should returns validation of the provided specific entity schema', ({ solicitation, entitySchema, returnExpected }) => { - expect(EntitySchema.assembleEntitySchema(solicitation, entitySchema)).toEqual(returnExpected); - }); -}); + expect(EntitySchema.assembleEntitySchema(solicitation, entitySchema)).toEqual(returnExpected) + }) +}) diff --git a/__tests__/error/index.test.ts b/__tests__/error/index.test.ts index 410f952..65e5c36 100644 --- a/__tests__/error/index.test.ts +++ b/__tests__/error/index.test.ts @@ -1,11 +1,11 @@ -import { error } from '../../src/error'; +import { error } from '../../src/error' describe('error', () => { it('Should format error with a error object', () => { - expect(error(400, { id: undefined })).toEqual({ statusCode: 400, error: { id: undefined } }); - }); + expect(error(400, { id: undefined })).toEqual({ statusCode: 400, error: { id: undefined } }) + }) it('Should format error with a error string', () => { - expect(error(400, 'Incorrect id value')).toEqual({ statusCode: 400, message: 'Incorrect id value' }); - }); -}); + expect(error(400, 'Incorrect id value')).toEqual({ statusCode: 400, message: 'Incorrect id value' }) + }) +}) diff --git a/__tests__/lambda/lambdaService.test.ts b/__tests__/lambda/lambdaService.test.ts index 4e7704c..028c7a1 100644 --- a/__tests__/lambda/lambdaService.test.ts +++ b/__tests__/lambda/lambdaService.test.ts @@ -18,7 +18,7 @@ describe('invoke', () => { })).resolves.toEqual({status: 400, body: { code: 'undefined' }}); */ - }); + }) it('Should returns the function invoked with error', async () => { /** @@ -35,7 +35,7 @@ describe('invoke', () => { isOffline: false, })).rejects.toBeTruthy(); **/ - }); + }) it('Should returns error to invoke without optional properties', async () => { /** @@ -44,5 +44,5 @@ describe('invoke', () => { functionName: 'authenticate-api-generalValidateSession', })).rejects.toBeTruthy(); **/ - }); -}); + }) +}) diff --git a/__tests__/s3/formatters.test.ts b/__tests__/s3/formatters.test.ts index 50cb6e1..cabbda1 100644 --- a/__tests__/s3/formatters.test.ts +++ b/__tests__/s3/formatters.test.ts @@ -25,6 +25,4 @@ describe('streamToString', () => { const result = await streamToString(readable) expect(result).toEqual(jsonString) }) - - }) diff --git a/package-lock.json b/package-lock.json index 7e0420b..83efec4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "adapcon-utils-js", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "adapcon-utils-js", - "version": "1.4.0", + "version": "1.4.1", "dependencies": { "@aws-sdk/client-dynamodb": "^3.496.0", "@aws-sdk/client-lambda": "^3.496.0", diff --git a/package.json b/package.json index ca5d293..7244e2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adapcon-utils-js", - "version": "1.4.0", + "version": "1.4.1", "description": "Utils library for Javascript", "keywords": [], "author": { diff --git a/src/entitySchema/index.ts b/src/entitySchema/index.ts index 9a9140e..e8b01fc 100644 --- a/src/entitySchema/index.ts +++ b/src/entitySchema/index.ts @@ -1,24 +1,25 @@ export const EntitySchema = { - assembleEntitySchema(solicitation: any, entitySchema: any): object { - const defaultObject = {}; - const properties = Object.keys(solicitation); + assembleEntitySchema (solicitation: any, entitySchema: any): object { + const defaultObject = {} + const properties = Object.keys(solicitation) - properties.forEach(key => { - if (entitySchema === undefined) return; - const haveChildren = typeof solicitation[key] === 'object'; - const property = entitySchema[key]; + properties.forEach(key => { + if (entitySchema === undefined) return + const haveChildren = typeof solicitation[key] === 'object' + const property = entitySchema[key] - if (property === undefined) return; + if (property === undefined) return - if (haveChildren) { - defaultObject[key] = { - ...property, - contentObject: { - ...EntitySchema.assembleEntitySchema(solicitation[key], property.contentObject || property.contentArray), - }, - }; - } else defaultObject[key] = property; - }); + if (haveChildren) { + defaultObject[key] = { + ...property, + contentObject: { + ...EntitySchema.assembleEntitySchema(solicitation[key], property.contentObject || property.contentArray) + } + } + } else defaultObject[key] = property + }) - return defaultObject; -}} + return defaultObject + } +} diff --git a/src/error/customErrors.test.ts b/src/error/customErrors.test.ts new file mode 100644 index 0000000..4fe923f --- /dev/null +++ b/src/error/customErrors.test.ts @@ -0,0 +1,175 @@ +import { HttpStatuses } from '..' +import { InternalError, BadRequestError, NotFoundError, IntegrationError, UnauthorizedError } from './customErrors' + +describe('CustomError', () => { + describe('InternalError', () => { + it('should return a ConnectionError with correct message and statusCode', () => { + const error = new InternalError(new Error('throwing error')) + + expect(error).toMatchObject({ + message: 'throwing error', + statusCode: HttpStatuses.internalError + }) + }) + + it('should return string containing correct items on toString() call', () => { + const error = new InternalError(new Error('throwing error')) + + expect(error.toString()).toContain('Internal Error: throwing error') + }) + + it('should return correct lambdaResponse on toLambdaResponse() call', () => { + const error = new InternalError(new Error('throwing error')) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.internalError, + body: JSON.stringify({ message: 'throwing error' }) + }) + }) + }) + + describe('IntegrationError', () => { + it('should return a IntegrationError with correct message and statusCode', () => { + const integrationName = 'microservice1' + const error = new IntegrationError(integrationName) + + expect(error).toMatchObject({ + message: `Could not integration with ${integrationName}`, + statusCode: HttpStatuses.integrationError + }) + }) + + it('IntegrationError toString()', () => { + const integrationName = 'microservice1' + const error = new IntegrationError(integrationName) + + expect(error.toString()).toContain(`Integration Error: Could not integration with ${integrationName}`) + }) + + it('should return correct lambdaResponse on toLambdaResponse() call', () => { + const integrationName = 'microservice1' + const error = new IntegrationError(integrationName) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.integrationError, + body: JSON.stringify({ message: `Could not integration with ${integrationName}` }) + }) + }) + + it('should return a IntegrationError with correct message, statusCode, and customError message', () => { + const integrationName = 'microservice1' + const customMessage = 'microservice1 is offline' + const error = new IntegrationError(integrationName, customMessage) + + expect(error).toMatchObject({ + message: customMessage, + statusCode: HttpStatuses.integrationError, + customMessage + }) + }) + + it('IntegrationError toString() with customError message', () => { + const integrationName = 'microservice1' + const error = new IntegrationError(integrationName, 'microservice1 is offline') + + expect(error.toString()).toContain('Integration Error: microservice1 is offline') + }) + + it('should return correct lambdaResponse on toLambdaResponse() call with customError message', () => { + const integrationName = 'microservice1' + const customMessage = 'microservice1 is offline' + const error = new IntegrationError(integrationName, customMessage) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.integrationError, + body: JSON.stringify({ message: customMessage }) + }) + }) + }) + + describe('BadRequestError', () => { + it('should return a BadRequestError with correct message and statusCode', () => { + const errorMessage = 'invalid appId' + const error = new BadRequestError(errorMessage) + + expect(error).toMatchObject({ + message: errorMessage, + statusCode: HttpStatuses.badRequest + }) + }) + + it('should return string containing correct items on toString() call', () => { + const errorMessage = 'invalid appId' + const error = new BadRequestError(errorMessage) + + expect(error.toString()).toContain(`Bad Request Error: ${errorMessage}`) + }) + + it('should return correct lambdaResponse on toLambdaResponse() call', () => { + const errorMessage = 'invalid appId' + const error = new BadRequestError(errorMessage) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.badRequest, + body: JSON.stringify({ message: errorMessage }) + }) + }) + }) + + describe('NotFoundError', () => { + it('should return a NotFoundError with correct message and statusCode', () => { + const errorMessage = 'thing not found' + const error = new NotFoundError(errorMessage) + + expect(error).toMatchObject({ + message: errorMessage, + statusCode: HttpStatuses.notFound + }) + }) + + it('should return string containing correct items on toString() call', () => { + const errorMessage = 'thing not found' + const error = new NotFoundError(errorMessage) + + expect(error.toString()).toContain(`Not Found Error: ${errorMessage}`) + }) + + it('should return correct lambdaResponse on toLambdaResponse() call', () => { + const errorMessage = 'thing not found' + const error = new NotFoundError(errorMessage) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.notFound, + body: JSON.stringify({ message: errorMessage }) + }) + }) + }) + describe('UnauthorizedError', () => { + it('should return a UnauthorizedError with correct message and statusCode', () => { + const errorMessage = 'not enough permissions' + const error = new UnauthorizedError(errorMessage) + + expect(error).toMatchObject({ + message: errorMessage, + statusCode: HttpStatuses.unauthorized + }) + }) + + it('should return string containing correct items on toString() call', () => { + const errorMessage = 'not enough permissions' + const error = new UnauthorizedError(errorMessage) + + expect(error.toString()).toContain(`Unauthorized Error: ${errorMessage}`) + }) + + it('should return correct lambdaResponse on toLambdaResponse() call', () => { + const errorMessage = 'not enough permissions' + const error = new UnauthorizedError(errorMessage) + + expect(error.toLambdaResponse()).toMatchObject({ + statusCode: HttpStatuses.unauthorized, + body: JSON.stringify({ message: errorMessage }) + }) + }) + }) +}) diff --git a/src/error/customErrors.ts b/src/error/customErrors.ts new file mode 100644 index 0000000..fa59521 --- /dev/null +++ b/src/error/customErrors.ts @@ -0,0 +1,82 @@ +import { HttpStatuses } from '..' + +abstract class CustomError extends Error { + abstract statusCode: number + abstract kind: string + + /** + * Returns a string representation of the error + * should be used for logging + * @returns {string} + */ + public toString (): string { + return `${this.kind}: ${this.message}${this.stack ? `\n${this.stack}` : ''}` + } + + /** + * Returns a lambda response object + * should be used for returning errors in lambda functions + * after a 'instanceof' check -> if (err instanceof CustomError) ... + * @returns {{statusCode: number, body: string}} + */ + public toLambdaResponse (): { statusCode: number, body: string } { + return { + statusCode: this.statusCode, + body: JSON.stringify({ message: this.message }) + } + } +} + +class BadRequestError extends CustomError { + statusCode = HttpStatuses.badRequest + kind = 'Bad Request Error' +} + +class IntegrationError extends CustomError { + statusCode = HttpStatuses.integrationError + kind = 'Integration Error' + protected customMessage: string + + constructor (protected integration: string, protected errorMessage?: string) { + const message = errorMessage ?? `Could not integration with ${integration}` + super(message) + this.customMessage = message + } +} + +class InternalError extends CustomError { + statusCode = HttpStatuses.internalError + kind = 'Internal Error' + + constructor (error: unknown) { + super('An internal error occurred') + + if (typeof error === 'string') { + this.message = error + } else if (typeof error === 'object' && 'message' in error! && 'statusCode' in error) { + this.message = error.message as string + this.statusCode = error.statusCode as HttpStatuses + } else if (typeof error === 'object' && 'message' in error!) { + this.message = error.message as string + } + } +} + +class NotFoundError extends CustomError { + statusCode = HttpStatuses.notFound + kind = 'Not Found Error' +} + +class UnauthorizedError extends CustomError { + statusCode = HttpStatuses.unauthorized + kind = 'Unauthorized Error' +} + +export { + BadRequestError, + CustomError, + IntegrationError, + InternalError, + NotFoundError, + UnauthorizedError +} diff --git a/src/error/index.ts b/src/error/index.ts index 04fbe72..45992ab 100644 --- a/src/error/index.ts +++ b/src/error/index.ts @@ -1,5 +1,7 @@ import { isString } from '../string' +export * from './customErrors' + export const error = (statusCode: number, err: any): object => ({ statusCode, ...(!isString(err) ? { error: err } : { message: err }) diff --git a/src/s3/formatters.ts b/src/s3/formatters.ts index 4f3e9b6..993deaa 100644 --- a/src/s3/formatters.ts +++ b/src/s3/formatters.ts @@ -1,4 +1,4 @@ -import { Readable } from 'stream'; +import { Readable } from 'stream' export const streamToString = async (stream: Readable) => new Promise((resolve, reject) => { const chunks: any[] = []