Skip to content

Commit

Permalink
feat(one-way-request): allow user to make a one-way trip request
Browse files Browse the repository at this point in the history
    - write tests for the endpoint
    - create requests enpoints
    - create requests model and migration files
    - document the requests endpoint
    - test database connection before starting app
[Maintains #167727732]
  • Loading branch information
komsic committed Aug 22, 2019
1 parent ab28dd9 commit d3a0872
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ node_modules
# package-lock.json
package-lock.json

# yarn.lock
yarn.lock

# env variables
.env

Expand Down
43 changes: 43 additions & 0 deletions src/controllers/requestController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import models from '../database/models';
import response from '../utils/response';
import messages from '../utils/messages';
import create from '../services/dbServices';

const { Request } = models;
const { serverError } = messages;

/**
* request trip controller
* @param {Object} req - server request
* @param {Object} res - server response
* @returns {Object} - custom response
*/
const requestTrip = async (req, res) => {
try {
const {
type, originCity, destinationCity, departureDate, reason, accommodation, userId
} = req.body;

const tripToBeRequested = {
type,
originCity,
destinationCity,
departureDate,
reason,
accommodation,
userId,
};

const requestedTrip = await create(Request, tripToBeRequested);

return response(res, 201, 'success', requestedTrip);
} catch (error) {
return response(res, 500, 'error', {
message: serverError,
});
}
};

export default {
requestTrip,
};
57 changes: 57 additions & 0 deletions src/database/migrations/20190820173619-create-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Requests', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
userId: {
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id',
},
},
type: {
type: Sequelize.ENUM,
allowNull: false,
values: ['one-way', 'return'],
},
originCity: {
type: Sequelize.STRING,
allowNull: false,
},
destinationCity: {
type: Sequelize.STRING,
allowNull: false,
},
departureDate: {
type: Sequelize.DATE,
allowNull: false,
},
reason: {
type: Sequelize.STRING,
allowNull: false,
},
accommodation: {
type: Sequelize.STRING,
},
approvalStatus: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Requests');
}
};
48 changes: 48 additions & 0 deletions src/database/models/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module.exports = (sequelize, DataTypes) => {
const Request = sequelize.define('Request', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
type: {
type: DataTypes.ENUM,
allowNull: false,
values: ['one-way', 'return'],
},
originCity: {
type: DataTypes.STRING,
allowNull: false,
},
destinationCity: {
type: DataTypes.STRING,
allowNull: false,
},
departureDate: {
type: DataTypes.DATE,
allowNull: false,
},
reason: {
type: DataTypes.STRING,
allowNull: false,
},
accommodation: {
type: DataTypes.STRING,
},
approvalStatus: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
});

Request.associate = function(models) {
Request.belongsTo(models.User, {
foreignKey: 'userId',
as: 'User',
});
};

return Request;
};
18 changes: 16 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import response from './utils/response';
import './config/env';
import routes from './routes';
import swaggerDoc from './config/swaggerDoc';
import db from './database/models';

// Instance of express app
const app = express();
Expand Down Expand Up @@ -45,7 +46,20 @@ app.use('*', (req, res) => response(res, 404, 'error', {
message: messages.notFound,
}));

// Finally, start server...
const server = app.listen(process.env.PORT || 3000, () => infoLog(`Listening on port ${server.address().port}`));
// Finally, check db connection then start the server...
const { sequelize } = db;
sequelize
.authenticate()
.then(() => {
infoLog('connection to database successful');
const server = app.listen(
process.env.PORT || 3000,
() => infoLog(`Listening on port ${server.address().port}`)
);
})
.catch((e) => {
infoLog('Failed to connect to the database');
throw e.message;
});

export default app;
85 changes: 85 additions & 0 deletions src/routes/api/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import requestController from '../../controllers/requestController';
import validate from '../../middlewares/validator';
import requestSchema from '../../validation/requestSchema';

const { requestTrip } = requestController;
const { requestTripSchema } = requestSchema;

const requestRoute = (router) => {
router.route('/requests')
/**
* @swagger
* /api/v1/requests:
* post:
* tags:
* - Requests
* description: Create a request for a trip
* produces:
* - applications/json
* requestBody:
* description: Request object that needs to be sent for approval
* required: true
* content:
* applications/json:
* schema:
* $ref: '#/components/schemas/RequestTrip'
* responses:
* 201:
* description: Trip request successfully created
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* data:
* $ref: '#/components/schemas/RequestTrip'
*
* 500:
* description: Internal Server error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
* components:
* schemas:
* RequestTrip:
* properties:
* id:
* type: string
* readOnly: true
* originCity:
* type: string
* destinationCity:
* type: string
* type:
* type: string
* enum: [one-way, return]
* departureDate:
* type: string
* returnDate:
* type: string
* reason:
* type: string
* accommodation:
* type: string
* userId:
* type: string
* createdAt:
* type: string
* readOnly: true
* updateAt:
* type: string
* readOnly: true
* ErrorResponse:
* properties:
* status:
* type: string
* error:
* type: string
*/
.post(validate(requestTripSchema), requestTrip);
};

export default requestRoute;
3 changes: 3 additions & 0 deletions src/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import messages from '../utils/messages';
import response from '../utils/response';
import userRoute from './api/user';
import requestRoute from './api/request';

const routes = (router) => {
router
Expand Down Expand Up @@ -38,6 +39,8 @@ const routes = (router) => {
}));
// user routes
userRoute(router);
// request routes
requestRoute(router);
};

export default routes;
4 changes: 3 additions & 1 deletion src/test/mockData/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import userMock from './userMock';
import requestMock from './requestMock';

export default {
userMock
userMock,
requestMock
};
19 changes: 19 additions & 0 deletions src/test/mockData/requestMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default {
validTripRequest: {
userId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
type: 'one-way',
originCity: 'Abuja',
destinationCity: 'New Delhi',
departureDate: '2019-08-21 17:59:04.305+00',
reason: 'Holiday',
accommodation: 'Transcorp Hotel'
},
badInputTripRequest: {
userId: '122a0d86-8b78-4bb8-b28f-8e5f7811c456',
type: 'one-way',
destinationCity: 'New Delhi',
departureDate: '2019-08-21 17:59:04.305+00',
reason: 'Holiday',
accommodation: 'Transcorp Hotel'
}
};
4 changes: 1 addition & 3 deletions src/test/routes/auth.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
app, chai, expect, sinon
app, chai, expect, sinon, BASE_URL
} from '../testHelpers/config';
import models from '../../database/models';
import mockData from '../mockData';
Expand All @@ -8,8 +8,6 @@ const { User } = models;

const { userMock } = mockData;

const BASE_URL = '/api/v1';

describe('AUTH', () => {
describe('POST /user/signup', () => {
const signupEndpoint = `${BASE_URL}/user/signup`;
Expand Down
54 changes: 54 additions & 0 deletions src/test/routes/request.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { app, chai, expect, sinon, BASE_URL } from '../testHelpers/config';
import models from '../../database/models';
import mockData from '../mockData';

const { Request } = models;
const { validTripRequest, badInputTripRequest } = mockData.requestMock;

describe('REQUESTS', () => {
const requestTripEndpoint = `${BASE_URL}/requests`;

describe('POST /requests', () => {
it('should request a trip successfully', (done) => {
chai.request(app)
.post(requestTripEndpoint)
.send(validTripRequest)
.end((err, res) => {
const { data } = res.body;
expect(res.status).to.equal(201);
expect(data).to.have.property('type');
expect(data).to.have.property('originCity');
expect(data).to.have.property('destinationCity');
expect(data).to.have.property('departureDate');
expect(data).to.have.property('reason');
expect(data).to.have.property('accommodation');
done(err);
});
});

it('should request a trip successfully', (done) => {
chai.request(app)
.post(requestTripEndpoint)
.send(badInputTripRequest)
.end((err, res) => {
expect(res.status).to.equal(400);
expect(res.body).to.have.property('status').that.equal('error');
done(err);
});
});

it('should return an internal server error', (done) => {
const stub = sinon.stub(Request, 'create').callsFake(() => Promise.reject(new Error('Internal server error')));
chai
.request(app)
.post(requestTripEndpoint)
.send(validTripRequest)
.end((err, res) => {
expect(res.status).to.equal(500);
expect(res.body).to.have.property('status').that.equal('error');
done(err);
stub.restore();
});
});
});
});
Loading

0 comments on commit d3a0872

Please sign in to comment.