Skip to content
Merged
13 changes: 8 additions & 5 deletions src/ActionParameterHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ export class ActionParameterHandler<T extends BaseDriver> {
protected async normalizeParamValue(value: any, param: ParamMetadata): Promise<any> {
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 (isNormalizationNeeded) {
await Promise.all(
Object.keys(value).map(async key => {
const keyValue = value[key];
Expand All @@ -123,6 +125,7 @@ export class ActionParameterHandler<T extends BaseDriver> {
})
);
}

// if value is a string, normalize it to demanded type
else if (typeof value === 'string') {
switch (param.targetName) {
Expand All @@ -135,7 +138,7 @@ export class ActionParameterHandler<T extends BaseDriver> {
}

// 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);
Expand Down
156 changes: 156 additions & 0 deletions test/ActionParameterHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
});