diff --git a/src/controllers/AccommodationController.js b/src/controllers/AccommodationController.js new file mode 100644 index 0000000..70789b7 --- /dev/null +++ b/src/controllers/AccommodationController.js @@ -0,0 +1,41 @@ +import models from '../models'; +import { HelperMethods } from '../utils'; + +const { Accommodation } = models; + +/** + * Class representing the Accomodation controller + * @class AccommodationController + * @description accommodation controller + */ +class AccommodationController { + /** + * Book An Accommodation. + * Route: POST: /accommodation + * @param {object} req - HTTP Request object + * @param {object} res - HTTP Response object + * @return {res} res - HTTP Response object + * @memberof AccommodationController + */ + static async bookAnAccommodation(req, res) { + try { + const { body } = req; + const { id } = req.decoded; + const { dataValues } = await Accommodation.create({ ...body, userId: id }); + if (dataValues.id) { + HelperMethods.requestSuccessful(res, { + success: true, + message: 'Accommodation booked successfully', + accommodationCreated: dataValues, + }, 201); + } + return HelperMethods.serverError(res, + 'Could not create an accommodation. Please try again'); + } catch (error) { + if (error.errors) return HelperMethods.sequelizeValidationError(res, error); + return HelperMethods.serverError(res); + } + } +} + +export default AccommodationController; diff --git a/src/controllers/RequestController.js b/src/controllers/RequestController.js index 63c18d2..49cb2ed 100644 --- a/src/controllers/RequestController.js +++ b/src/controllers/RequestController.js @@ -22,7 +22,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, diff --git a/src/controllers/index.js b/src/controllers/index.js index da0033f..ca0797c 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -1,4 +1,5 @@ import UserController from './UserController'; import RequestController from './RequestController'; +import AccommodationController from './AccommodationController'; -export { UserController, RequestController }; +export { UserController, RequestController, AccommodationController }; diff --git a/src/migrations/02-create-accommodations.js b/src/migrations/02-create-accommodations.js index 789ca84..1654366 100644 --- a/src/migrations/02-create-accommodations.js +++ b/src/migrations/02-create-accommodations.js @@ -5,6 +5,13 @@ module.exports = { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4 }, + userId: { + type: Sequelize.UUID, + references: { + model: 'Users', + key: 'id', + } + }, name: { type: Sequelize.STRING, allowNull: false, diff --git a/src/models/Accommodation.js b/src/models/Accommodation.js index a939b9d..697d362 100644 --- a/src/models/Accommodation.js +++ b/src/models/Accommodation.js @@ -34,12 +34,15 @@ export default (sequelize, DataTypes) => { } } }, - }); + }, { tableName: 'Accommodations' }); Accommodation.associate = models => { + Accommodation.belongsTo(models.User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + }); Accommodation.hasMany(models.Request, { foreignKey: 'accommodationId', - as: 'accommodation', }); }; diff --git a/src/models/User.js b/src/models/User.js index 3c838ed..e199eb5 100755 --- a/src/models/User.js +++ b/src/models/User.js @@ -190,6 +190,10 @@ export default (sequelize, DataTypes) => { foreignKey: 'userId', as: 'users_request', }); + User.hasMany(models.Accommodation, { + foreignKey: 'userId', + as: 'users_accommodation', + }); } } }); @@ -207,6 +211,10 @@ export default (sequelize, DataTypes) => { foreignKey: 'userId', onDelete: 'CASCADE' }); + User.hasMany(models.Accommodation, { + foreignKey: 'userId', + onDelete: 'CASCADE' + }); }; // eslint-disable-next-line func-names diff --git a/src/routes/accommodationRoute.js b/src/routes/accommodationRoute.js new file mode 100644 index 0000000..1cab3d4 --- /dev/null +++ b/src/routes/accommodationRoute.js @@ -0,0 +1,14 @@ +import { AccommodationController } from '../controllers'; +import { Authorization } from '../middlewares'; +import Validate from '../validation'; + +const accommodationRoute = app => { + app.post( + '/api/v1/accommodation', + Validate.validateUserInput, + Authorization.checkToken, + AccommodationController.bookAnAccommodation + ); +}; + +export default accommodationRoute; diff --git a/src/routes/index.js b/src/routes/index.js index debcbfc..884cc95 100755 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,5 +1,6 @@ import authRoute from './authRoute'; import requestRoute from './requestRoute'; +import accommodationRoute from './accommodationRoute'; import userRoute from './userRoute'; import socialAuthRoute from './socialAuthRoute'; /** @@ -19,6 +20,7 @@ const routes = app => { requestRoute(app); userRoute(app); socialAuthRoute(app); + accommodationRoute(app); }; export default routes; diff --git a/src/test/integrationTests/accommodationController.test.js b/src/test/integrationTests/accommodationController.test.js new file mode 100644 index 0000000..440a5a8 --- /dev/null +++ b/src/test/integrationTests/accommodationController.test.js @@ -0,0 +1,57 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../../index'; + +chai.use(chaiHttp); +const { expect } = chai; + +describe('Integration Tests For Accommodation Controller', () => { + describe('Test book accommodation controller', () => { + const accommodationDetails = { + name: 'Southern Sun Ikoyi Hotel', + address: '174, Owolabi street, Yaba', + roomName: 'C3', + roomType: '2 bedroom', + vacantNumber: '1', + }; + let token; + before('Get Token', async () => { + const loginResponse = await chai.request(app).post('/api/v1/auth/login') + .send({ + email: 'demo2@demo.com', + password: 'password', + }); + token = loginResponse.body.data.userDetails.token; + }); + it('should book an accommodation for a user', async () => { + const response = await chai.request(app).post('/api/v1/accommodation') + .send(accommodationDetails).set('x-access-token', token); + expect(response.status).to.deep.equal(201); + expect(response.body.data).to.have.property('success'); + expect(response.body.data.success).to.equal(true); + expect(response.body.data).to.have.property('message'); + expect(response.body.data.message) + .to.equal('Accommodation booked successfully'); + }); + it('should return client error when some details are missing', async () => { + const response = await chai.request(app).post('/api/v1/accommodation') + .send().set({ 'x-access-token': token }); + expect(response.status).to.deep.equal(400); + expect(response.body).to.have.property('success'); + expect(response.body.success).to.equal(false); + expect(response.body).to.have.property('message'); + expect(response.body.message) + .to.equal('The "name" field is required'); + }); + it('should return error for missing token', async () => { + const response = await chai.request(app).post('/api/v1/accommodation') + .send(accommodationDetails); + expect(response.status).to.deep.equal(401); + expect(response.body).to.have.property('success'); + expect(response.body.success).to.equal(false); + expect(response.body).to.have.property('message'); + expect(response.body.message) + .to.equal('User not authorized'); + }); + }); +}); diff --git a/src/test/integrationTests/index.js b/src/test/integrationTests/index.js index e64e102..89f7306 100644 --- a/src/test/integrationTests/index.js +++ b/src/test/integrationTests/index.js @@ -1,3 +1,4 @@ import './requestController.test'; import './userController.test'; import './socialController.test'; +import './accommodationController.test'; diff --git a/src/test/unitTests/accomodationController.test.js b/src/test/unitTests/accomodationController.test.js new file mode 100644 index 0000000..3000860 --- /dev/null +++ b/src/test/unitTests/accomodationController.test.js @@ -0,0 +1,35 @@ +import sinon from 'sinon'; +import chai from 'chai'; +import { AccommodationController } from '../../controllers'; +import models from '../../models'; + +const { expect } = chai; +const { Accommodation } = models; + +const req = { decoded: { id: 'some id' } }; + +const res = { + status() { + return this; + }, + json(obj) { + return obj; + } +}; + +describe('unit test for the Accomodation Controller', () => { + let stubbedMethod; + afterEach(() => { + if (stubbedMethod.restore) stubbedMethod.restore(); + }); + it('should return a server error when an unexpected error happens', async () => { + stubbedMethod = sinon.stub(Accommodation, 'create').throws({ + dataValues: 'some thing' + }); + const response = await AccommodationController.bookAnAccommodation(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); +}); diff --git a/src/test/unitTests/index.js b/src/test/unitTests/index.js index 08f07cd..f51ab09 100644 --- a/src/test/unitTests/index.js +++ b/src/test/unitTests/index.js @@ -2,5 +2,7 @@ import './authentication.test'; import './sendEmail.test'; import './helperMethods.test'; import './requestController.test'; +import './accomodationController.test'; +import './userController.test'; import './middleware.test'; import './searchRequestsMiddleware.test'; diff --git a/src/test/unitTests/userController.test.js b/src/test/unitTests/userController.test.js new file mode 100644 index 0000000..603809e --- /dev/null +++ b/src/test/unitTests/userController.test.js @@ -0,0 +1,73 @@ +import sinon from 'sinon'; +import chai from 'chai'; +import { UserController } from '../../controllers'; +import models from '../../models'; + +const { expect } = chai; +const { User } = models; + +const req = { decoded: { id: 'some id' } }; + +const res = { + status() { + return this; + }, + json(obj) { + return obj; + } +}; + +describe('unit test for the User Controller', () => { + let stubbedMethod; + afterEach(() => { + if (stubbedMethod.restore) stubbedMethod.restore(); + }); + it('should return a server error when an unexpected error happens', async () => { + stubbedMethod = sinon.stub(User, 'findByPk').throws({ + dataValues: 'some thing' + }); + const response = await UserController.updateProfile(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); + it('should return a server error when an unexpected error happens', async () => { + stubbedMethod = sinon.stub(User, 'findByPk').throws({ + dataValues: 'some thing' + }); + const response = await UserController.getProfile(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); + it('should return a server error when an unexpected error happens', async () => { + stubbedMethod = sinon.stub(User, 'findByPk').throws({ + dataValues: 'some thing' + }); + const response = await UserController.verifyEmail(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); + it('should return a server error when an unexpected error happens', async () => { + stubbedMethod = sinon.stub(User, 'findOne').throws({ + dataValues: 'some thing' + }); + const response = await UserController.resetPassword(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); + it('should only allow managers reject a User', async () => { + stubbedMethod = sinon.stub(User, 'update').throws({ dataValues: 'some thing' }); + const response = await UserController.rememberUserDetails(req, res); + expect(response).to.have.property('message'); + expect(response.message).to.equal('Internal server error'); + expect(response).to.have.property('success'); + expect(response.success).to.equal(false); + }); +});