diff --git a/src/ActionParameterHandler.ts b/src/ActionParameterHandler.ts index c6c453d4..6022c195 100644 --- a/src/ActionParameterHandler.ts +++ b/src/ActionParameterHandler.ts @@ -96,11 +96,13 @@ export class ActionParameterHandler { protected async normalizeParamValue(value: any, param: ParamMetadata): Promise { if (value === null || value === undefined) return value; + const isNormalizationNeeded = + typeof value === 'object' && ['queries', 'headers', 'params', 'cookies'].includes(param.type); + const isTargetPrimitive = ['number', 'string', 'boolean'].includes(param.targetName); + const isTransformationNeeded = (param.parse || param.isTargetObject) && param.type !== 'param'; + // if param value is an object and param type match, normalize its string properties - if ( - typeof value === 'object' && - ['queries', 'headers', 'params', 'cookies'].some(paramType => paramType === param.type) - ) { + if (isNormalisationNeeded) { await Promise.all( Object.keys(value).map(async key => { const keyValue = value[key]; @@ -123,6 +125,7 @@ export class ActionParameterHandler { }) ); } + // if value is a string, normalize it to demanded type else if (typeof value === 'string') { switch (param.targetName) { @@ -135,7 +138,7 @@ export class ActionParameterHandler { } // if target type is not primitive, transform and validate it - if (['number', 'string', 'boolean'].indexOf(param.targetName) === -1 && (param.parse || param.isTargetObject)) { + if (!isTargetPrimitive && isTransformationNeeded) { value = this.parseValue(value, param); value = this.transformValue(value, param); value = await this.validateValue(value, param); diff --git a/test/ActionParameterHandler.spec.ts b/test/ActionParameterHandler.spec.ts new file mode 100644 index 00000000..9d322dc1 --- /dev/null +++ b/test/ActionParameterHandler.spec.ts @@ -0,0 +1,156 @@ +import { ActionParameterHandler } from '../src/ActionParameterHandler'; +import { ActionMetadata, ControllerMetadata, ExpressDriver, ParamMetadata } from '../src'; +import { ActionMetadataArgs } from '../src/metadata/args/ActionMetadataArgs'; +import { ControllerMetadataArgs } from '../src/metadata/args/ControllerMetadataArgs'; +import { ParamType } from '../src/metadata/types/ParamType'; + +const expect = require('chakram').expect; + +describe('ActionParameterHandler', () => { + const buildParamMetadata = ( + name: string = 'id', + type: ParamType = 'param', + isRequired: boolean = false + ): ParamMetadata => { + const controllerMetadataArgs: ControllerMetadataArgs = { + target: function () {}, + route: '', + type: 'json', + options: {}, + }; + const controllerMetadata = new ControllerMetadata(controllerMetadataArgs); + const args: ActionMetadataArgs = { + route: '', + method: 'getProduct', + options: {}, + target: function () {}, + type: 'get', + appendParams: undefined, + }; + const actionMetadata = new ActionMetadata(controllerMetadata, args, {}); + + return { + type, + name, + targetName: 'product', + isTargetObject: true, + actionMetadata, + target: () => {}, + method: 'getProduct', + object: 'getProduct', + extraOptions: undefined, + index: 0, + parse: undefined, + required: isRequired, + transform: function (action, value) { + return value; + }, + classTransform: undefined, + validate: undefined, + targetType: function () {}, + }; + }; + const driver = new ExpressDriver(); + const actionParameterHandler = new ActionParameterHandler(driver); + + describe('positive', () => { + it('handle - should process string parameters', async () => { + const param = buildParamMetadata('uuid'); + const action = { + request: { + params: { + uuid: '0b5ec98f-e26d-4414-b798-dcd35a5ef859', + }, + }, + response: {}, + }; + + const processedValue = await actionParameterHandler.handle(action, param); + + expect(processedValue).to.be.eq(action.request.params.uuid); + }); + + it('handle - should process string parameters, returns empty if a given string is empty', async () => { + const param = buildParamMetadata('uuid'); + const action = { + request: { + params: { + uuid: '', + }, + }, + response: {}, + }; + + const processedValue = await actionParameterHandler.handle(action, param); + + expect(processedValue).to.be.eq(action.request.params.uuid); + }); + + it('handle - should process number parameters', async () => { + const param = buildParamMetadata('id'); + const action = { + request: { + params: { + id: 10000, + }, + }, + response: {}, + }; + + const processedValue = await actionParameterHandler.handle(action, param); + + expect(processedValue).to.be.eq(action.request.params.id); + }); + + it('handle - undefined on empty object provided', async () => { + const param = buildParamMetadata(); + const action = { + request: { + params: {}, + }, + response: {}, + }; + + const processedValue = await actionParameterHandler.handle(action, param); + + expect(processedValue).to.be.eq(undefined); + }); + }); + describe('negative', () => { + it('handle - throws error if the parameter is required', async () => { + const param = buildParamMetadata('uuid', 'param', true); + const action = { + request: {}, + response: {}, + }; + let error; + + try { + await actionParameterHandler.handle(action, param); + } catch (e) { + error = e; + } + + expect(error.toString()).to.be.eq("TypeError: Cannot read property 'uuid' of undefined"); + }); + + it('handle - throws error if the parameter is required, type file provided', async () => { + const param = buildParamMetadata('uuid', 'file', true); + const action = { + request: {}, + response: {}, + }; + let error; + + try { + await actionParameterHandler.handle(action, param); + } catch (e) { + error = e; + } + + expect(error.httpCode).to.be.eq(400); + expect(error.name).to.be.eq('ParamRequiredError'); + expect(error.message).to.be.eq('Uploaded file "uuid" is required for request on undefined undefined'); + }); + }); +});