Skip to content

Commit

Permalink
feature(rejectRequest): user(manager) can reject a request
Browse files Browse the repository at this point in the history
  • Loading branch information
ChiamakaObitube authored and Daymorelah committed Sep 3, 2019
1 parent aa9ebf7 commit 2bd32b3
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 16 deletions.
39 changes: 38 additions & 1 deletion src/controllers/RequestController.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RequestController {
try {
const { id } = req.decoded;
const { body } = req;
const { dataValues } = await Request.create({ ...body, userId: id });
const { dataValues } = await Request.create({ ...body, UserId: id });
if (dataValues.id) {
HelperMethods.requestSuccessful(res, {
success: true,
Expand Down Expand Up @@ -66,6 +66,43 @@ class RequestController {
return HelperMethods.serverError(res);
}
}

/**
* Reject a Request
* Route: PATCH: /request
* @param {object} req - HTTP Request object
* @param {object} res - HTTP Response object
* @return {res} res - HTTP Response object
* @memberof RequestController
*/
static async rejectRequest(req, res) {
try {
const { role } = req.decoded;

if (role !== 'Manager') {
return HelperMethods.clientError(
res, 'Only managers can perform this action',
401
);
}
const requestExist = await Request.findByPk(req.body.id);
if (requestExist.dataValues.status === 'OPEN' && requestExist.dataValues.id) {
await requestExist.update({ status: 'REJECTED' });
return HelperMethods
.requestSuccessful(res, {
success: true,
message: 'You have successfully rejected this request',
}, 200);
}
return HelperMethods.clientError(
res, 'This request has already been rejected',
400
);
} catch (error) {
if (error.errors) return HelperMethods.sequelizeValidationError(res, error);
return HelperMethods.serverError(res);
}
}
}

export default RequestController;
5 changes: 5 additions & 0 deletions src/migrations/03-create-requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ module.exports = {
values: ['BUSINESS', 'VACATION', 'EXPEDITION'],
defaultValue: 'BUSINESS'
},
status: {
type: Sequelize.ENUM,
values: ['OPEN', 'REJECTED', 'APPROVED'],
defaultValue: 'OPEN'
},
accommodationId: {
type: Sequelize.UUID,
references: {
Expand Down
5 changes: 5 additions & 0 deletions src/models/Request.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export default (sequelize, DataTypes) => {
type: DataTypes.ENUM,
values: ['BUSINESS', 'VACATION', 'EXPEDITION'],
defaultValue: 'BUSINESS'
},
status: {
type: DataTypes.ENUM,
values: ['OPEN', 'REJECTED', 'APPROVED'],
defaultValue: 'OPEN'
}
});

Expand Down
14 changes: 0 additions & 14 deletions src/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,6 @@ export default (sequelize, DataTypes) => {
type: DataTypes.BOOLEAN,
defaultValue: false
}
},
{
classMethods: {
associate: models => {
User.hasMany(models.Requests, {
foreignKey: 'userId',
as: 'users_request',
});
}
}
});

User.beforeCreate(async user => {
Expand All @@ -201,10 +191,6 @@ export default (sequelize, DataTypes) => {
user.password = await CryptData.encryptData(user.password);
});

User.associate = models => {
User.hasMany(models.Request);
};

// eslint-disable-next-line func-names
User.prototype.verifyPassword = async function (clearPassword) {
const isPasswordCorrect = await CryptData
Expand Down
4 changes: 4 additions & 0 deletions src/routes/requestRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const requestRoutes = app => {
Authorization.checkToken,
Validate.validateUserInput,
RequestController.bookAReturnTrip);

app.patch('/api/v1/request/reject',
Authorization.checkToken,
RequestController.rejectRequest);
};

export default requestRoutes;
4 changes: 4 additions & 0 deletions src/seeders/03-demo-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
accommodationId: '2125be7b-f1f1-4f0a-af86-49c657870b5c',
userId: '3821b930-ce48-4ac8-9ddf-ee3bf7980d08',
reason: 'VACATION',
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -21,6 +22,7 @@ module.exports = {
accommodationId: '9c41e609-7a30-4211-9d10-146a9c54ee74',
userId: '96dc6b6d-7a77-4322-8756-e22f181d952c',
reason: 'BUSINESS',
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -33,6 +35,7 @@ module.exports = {
accommodationId: '2125be7b-f1f1-4f0a-af86-49c657870b5c',
userId: '79ddfd3b-5c83-4beb-815e-55b1c95230e1',
reason: 'EXPEDITION',
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date()
},
Expand All @@ -45,6 +48,7 @@ module.exports = {
accommodationId: '35106536-deb5-4111-bd90-9ddfac5d348b',
userId: '4712fc7e-ca41-457f-872e-4a64b79efbba',
reason: 'BUSINESS',
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date()
},
Expand Down
51 changes: 50 additions & 1 deletion src/test/integrationTests/requestController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ describe('Integration tests for the request controller', () => {
accommodationId: '2125be7b-f1f1-4f0a-af86-49c657870b5c',
userId: '79ddfd3b-5c83-4beb-815e-55b1c95230e1',
reason: 'EXPEDITION',
status: 'OPEN'
};
let token;
before('login with an existing user details from the seeded data', async () => {
const response = await chai.request(app).post('/api/v1/auth/login')
.send({
email: 'demo1@demo.com',
email: 'demo3@demo.com',
password: 'password',
});
token = response.body.data.userDetails.token;
Expand Down Expand Up @@ -94,4 +95,52 @@ describe('Integration tests for the request controller', () => {
expect(response.body.success).to.equal(false);
});
});
describe('Test for a manager to reject trip request', () => {
it('should reject a trip', async () => {
const rejectedTrip = {
id: '1b26c8d1-768d-4bcb-8407-f6d85b1f1dee',
origin: 'Mushin',
destination: 'Bariga',
flightDate: '2019-06-21',
returnDate: '2019-03-21',
accommodationId: '35106536-deb5-4111-bd90-9ddfac5d348b',
UserId: '4712fc7e-ca41-457f-872e-4a64b79efbba',
reason: 'BUSINESS',
status: 'REJECTED',
};
const response = await chai
.request(app)
.patch('/api/v1/request/reject')
.set('x-access-token', token)
.send(rejectedTrip);
expect(response.status).to.equal(200);
expect(response.body.data).to.have.property('message');
expect(response.body.data.message).to.equal('You have successfully rejected this request');
expect(response.body.data).to.have.property('success');
expect(response.body.data.success).to.equal(true);
});
it('should not reject an already rejected request', async () => {
const rejectedTrip = {
id: '1b26c8d1-768d-4bcb-8407-f6d85b1f1dee',
origin: 'Mushin',
destination: 'Bariga',
flightDate: '2019-06-21',
returnDate: '2019-03-21',
accommodationId: '35106536-deb5-4111-bd90-9ddfac5d348b',
UserId: '4712fc7e-ca41-457f-872e-4a64b79efbba',
reason: 'BUSINESS',
status: 'REJECTED',
};
const response = await chai
.request(app)
.patch('/api/v1/request/reject')
.set('x-access-token', token)
.send(rejectedTrip);
expect(response.status).to.equal(400);
expect(response.body).to.have.property('message');
expect(response.body.message).to.equal('This request has already been rejected');
expect(response.body).to.have.property('success');
expect(response.body.success).to.equal(false);
});
});
});
8 changes: 8 additions & 0 deletions src/test/unitTests/requestController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,12 @@ describe('unit test for the Request Controller', () => {
expect(response).to.have.property('success');
expect(response.success).to.equal(false);
});
it('should only allow managers reject a request', async () => {
stubbedMethod = sinon.stub(Request, 'update').throws({ dataValues: 'some thing' });
const response = await RequestController.rejectRequest(req, res);
expect(response).to.have.property('message');
expect(response.message).to.equal('Only managers can perform this action');
expect(response).to.have.property('success');
expect(response.success).to.equal(false);
});
});
45 changes: 45 additions & 0 deletions src/utils/ResponseHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint-disable no-unused-vars */

/**
*
*
* @export
* @class ResponseHandler
*/
export default class ResponseHandler {
/**
*
*
* @static
* @param {*} res HTTP response object
* @param {*} data An object or array to send as a response
* @param {number} [status=200] HTTP response status code. Default is 200
* @memberof ResponseHandler
* @returns {void}
*/
static success(res, data, status = 200) {
return res.status(status).json({
success: true,
status,
data
});
}

/**
*
*
* @static
* @param {*} res HTTP response object
* @param {string} [message='Bad request'] An error message
* @param {number} [status=400] HTTP response status code. Default is 400
* @memberof ResponseHandler
* @returns {void}
*/
static error(res, message = 'Bad request', status = 400) {
return res.status(status).json({
success: false,
status,
error: message
});
}
}
42 changes: 42 additions & 0 deletions src/utils/cryptData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import bcrypt from 'bcrypt';

/**
* @class CryptData
* @description class defining the methods used to crypt and decrypt
* sensitive information
*/
class CryptData {
/**
* @param {string} dataToEncrypt - string to encrypt
* @return {string} - hashed string
* @memberof CryptData
*/
static async encryptData(dataToEncrypt) {
const salt = 6;
try {
const hashedData = await bcrypt.hash(dataToEncrypt, salt);
return hashedData;
} catch (err) {
return Error('An error occurred while hashing');
}
}

/**
* @param {string} dataToDecrypt - string to decrypt
* @param {string} dataBaseHash - hash used to compare the string against
* @return {boolean} - result of the comparison. If the passed in string
* is the same as the hash used to compare, then a boolean 'true' is
* returned else 'false' is returned.
* @memberof CryptData
*/
static async decryptData(dataToDecrypt, dataBaseHash) {
try {
const isPasswordCorrect = await bcrypt
.compare(dataToDecrypt, dataBaseHash);
return isPasswordCorrect;
} catch (err) {
return Error('An error occurred while comparing the data sent');
}
}
}
export default CryptData;

0 comments on commit 2bd32b3

Please sign in to comment.