├── test-coverage.png ├── .gitignore ├── gulpfile.js ├── src ├── models │ ├── User.js │ └── Event.js ├── config │ └── db.js ├── controllers │ ├── UsersEventsController.js │ ├── EventController.js │ └── UserController.js ├── routes │ └── EventRoutes.js ├── util │ └── ResponseDataUtil.js └── services │ ├── UsersEventService.js │ ├── EventService.js │ └── UserService.js ├── test ├── data │ ├── user-data.js │ └── event-data.js ├── karma.conf.js ├── config │ └── db.js ├── integration │ ├── user-events-int.js │ ├── events-int.js │ └── user-int.js └── unit │ ├── event-service-unit-test.js │ ├── user-service-unit-test.js │ └── users-events-service-unit-test.js ├── index.js ├── package.json ├── README.md └── SOLUTION.md /test-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajeshkumarbehura/crud-exmaple-js/master/test-coverage.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | .nyc_output/ 4 | .idea/* 5 | .idea 6 | coverage/ 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var Server = require('karma').Server; 3 | 4 | gulp.task('test', function (done) { 5 | new Server({ 6 | configFile: __dirname + '\\test\\karma.conf.js', 7 | singleRun: true, 8 | browserNoActivityTimeout: 60000 9 | }, function () { done(); }).start(); 10 | }); 11 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const {Schema} = mongoose; 4 | 5 | const UserSchema = new Schema( 6 | { 7 | name: {type: String, required: true}, 8 | personalId: {type: String, required: true}, 9 | contactNo: {type: Number, required: true} 10 | }, 11 | { 12 | timestamps: true, 13 | }, 14 | ); 15 | 16 | const User = mongoose.models.User || mongoose.model('User', UserSchema); 17 | 18 | module.exports = User; 19 | -------------------------------------------------------------------------------- /src/config/db.js: -------------------------------------------------------------------------------- 1 | /** 2 | Here do database connection handling. 3 | Change the database connection URL when required. 4 | */ 5 | const mongoose = require('mongoose'); 6 | const URL = "mongodb://localhost:27017/brick-test-db"; 7 | 8 | mongoose.connect(URL); 9 | 10 | const db = mongoose.connection; 11 | db.on('error', console.error.bind(console, 'MongoDB connection error:')); 12 | db.once('open', function () { 13 | // we're connected! 14 | console.log("Connected to MongoDB database") 15 | }); 16 | 17 | module.exports = db; -------------------------------------------------------------------------------- /test/data/user-data.js: -------------------------------------------------------------------------------- 1 | const User = require('../../src/models/User'); 2 | 3 | module.exports = { 4 | 5 | userJson: 6 | { 7 | "name": "rajesh", 8 | "contactNo": 9004453, 9 | "personalId": "1234", 10 | }, 11 | 12 | invalidUserJson: 13 | { 14 | "name": "rajesh", 15 | "contactNo": 9004453, 16 | }, 17 | 18 | 19 | userModel: () => { 20 | return new User({ 21 | "name": "rajesh", 22 | "contactNo": 9004453, 23 | "personalId": "1234", 24 | }); 25 | }, 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | //const mongoose = require('mongoose'); 3 | const cors = require('cors'); 4 | const eventRoutes = require('./src/routes/EventRoutes'); 5 | const db = require('./src/config/db'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const app = express(); 9 | const PORT = process.env.PORT || 5000; 10 | 11 | 12 | app.use(bodyParser.urlencoded({extended: true}),) 13 | .use(bodyParser.json()); 14 | 15 | 16 | app.use("/api/v1", eventRoutes) 17 | .use(function (req, res) { 18 | return res.status(404).send({message: 'Route' + req.url + ' Not found.'}); 19 | }) 20 | .use(cors); 21 | 22 | app.listen(PORT, () => console.log('Application listening in port ', PORT)); 23 | 24 | module.exports = app; 25 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var path = require('path'); 3 | config.set({ 4 | browsers: ['PhantomJS'], 5 | phantomjsLauncher: { 6 | cmd: { 7 | win32: path.join(__dirname, '/phantomjs/phantomjs.exe') 8 | } 9 | }, 10 | // this tells Karma to start Jasmine 11 | frameworks: ['jasmine'], 12 | files: [ 13 | '../src/**/*.js' 14 | ], 15 | 16 | // coverage reporter generates the coverage 17 | reporters: ['progress', 'coverage'], 18 | 19 | preprocessors: { 20 | '../src/**/*.js': ['coverage'] 21 | }, 22 | 23 | // optionally, configure the reporter 24 | coverageReporter: { 25 | type: 'html', 26 | dir: 'coverage/' 27 | } 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/controllers/UsersEventsController.js: -------------------------------------------------------------------------------- 1 | const usersEventsService = require('../services/UsersEventService'); 2 | const responseDataUtil = require('../util/ResponseDataUtil'); 3 | 4 | 5 | const usersEventsController = { 6 | 7 | /** 8 | * Get Users events either all or by parameters 9 | * @param request 10 | * @param response 11 | * @returns {Promise} 12 | */ 13 | getUserWithEvents: async (request, response) => { 14 | let responseData = null; 15 | if (request.query.userId) { 16 | responseData = await usersEventsService.findEventsByUserId(request.query.userId); 17 | } else { 18 | responseData = await usersEventsService.findEventsForAllUser(); 19 | } 20 | responseDataUtil.updateResponse(response, responseData); 21 | }, 22 | 23 | }; 24 | 25 | module.exports = usersEventsController; 26 | -------------------------------------------------------------------------------- /test/config/db.js: -------------------------------------------------------------------------------- 1 | const Mockgoose = require('mockgoose').Mockgoose; 2 | const mongoose = require('mongoose'); 3 | const DB_URI = 'mongodb://localhost:27017/myapp'; 4 | 5 | const mockgoose = new Mockgoose(mongoose); 6 | 7 | function connect() { 8 | return new Promise((resolve, reject) => { 9 | mockgoose.prepareStorage() 10 | .then(() => { 11 | mongoose.connect(DB_URI, {useNewUrlParser: true, useCreateIndex: true}) 12 | .then((res, err) => { 13 | console.log('Mock mongodb connected.') 14 | if (err) return reject(err); 15 | resolve(); 16 | }) 17 | }) 18 | 19 | }); 20 | } 21 | 22 | function close() { 23 | console.log('Mock mongodb closed.') 24 | return mongoose.disconnect(); 25 | } 26 | 27 | module.exports = {connect, close}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "body-parser": "^1.19.0", 8 | "chai": "^4.2.0", 9 | "chai-http": "^4.3.0", 10 | "cors": "^2.8.5", 11 | "dotenv": "^8.0.0", 12 | "express": "^4.16.4", 13 | "istanbul": "^0.4.5", 14 | "mocha": "^6.2.0", 15 | "mockgoose": "^8.0.2", 16 | "mongoose": "^5.5.8", 17 | "morgan": "^1.9.1", 18 | "supertest": "^4.0.2" 19 | }, 20 | "scripts": { 21 | "start": "nodemon index.js", 22 | "test": "npx nyc --reporter=text mocha --recursive --exit", 23 | "test-only": "mocha --recursive --exit" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "^1.19.1", 27 | "nyc": "^14.1.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/models/Event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const {Schema} = mongoose; 4 | 5 | const EventSchema = new Schema( 6 | { 7 | end: {type: Date, required: true}, 8 | start: {type: Date, required: true}, 9 | timeZoneOffset: {type: Number, required: true}, 10 | title: {type: String, required: true}, 11 | details: {type: String, required: true}, 12 | location: { 13 | address: {type: String, required: true}, 14 | latLng: { 15 | lat: {type: Number, required: true}, 16 | lng: {type: Number, required: true}, 17 | }, 18 | }, 19 | userId: {type: String}, 20 | }, 21 | { 22 | timestamps: true, 23 | }, 24 | ); 25 | 26 | 27 | const Event = mongoose.model('Event', EventSchema); 28 | 29 | module.exports = Event; 30 | -------------------------------------------------------------------------------- /src/routes/EventRoutes.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const eventController = require('../controllers/EventController'); 3 | const userController = require('../controllers/UserController'); 4 | const usersEventsController = require('../controllers/UsersEventsController'); 5 | 6 | /* Events api path */ 7 | router.route('/events').post(eventController.addEvent); 8 | router.route('/events').get(eventController.getAllEvents); 9 | router.route('/events/test').get(eventController.testEvent); 10 | 11 | /* users api path */ 12 | router.route('/users/test').get(userController.testUser); 13 | router.route('/users').get(userController.getAllUser); 14 | router.route('/users/:id').get(userController.getOneById); 15 | router.route('/users').post(userController.addUser); 16 | 17 | /* users and events api path */ 18 | router.route('/users-events').get(usersEventsController.getUserWithEvents); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Test Brickbase 2 | 3 | This repository contains code written in NodeJS with errors, bug and incorrect code practice. Your tasks are to: 4 | 5 | ## Task 1 6 | 1. Create Unit and Integration Tests up to for at least 70% test coverage 7 | 2. Find and remove all the bugs and error 8 | 3. Improve the code quality accordingly 9 | 4. Create a SOLUTIONS.md file listing all the correction you made and why you took the decisions your took. This is to help us ascertain your problem solving skills 10 | 11 | ## Task 2 12 | 1. Create a new API for User 13 | 2. Create a Models for a User 14 | 3. For each event created, connect that event with a user such that every event has a user 15 | 4. Create a way to retrieve all events for a user 16 | 5. Make it possible to return all a user and all the events created by that user 17 | 18 | 19 | ## Submission 20 | Create a private repo on github, add @michelmustapha and @bytenaija as collaborators and send an email to michel@brickbase.io indicating that you are done with the test. -------------------------------------------------------------------------------- /src/controllers/EventController.js: -------------------------------------------------------------------------------- 1 | const Event = require('../models/Event'); 2 | const eventService = require('../services/EventService'); 3 | const responseDataUtil = require('../util/ResponseDataUtil'); 4 | 5 | module.exports = { 6 | 7 | /** 8 | * Add a new Event in System 9 | * @param request 10 | * @param response 11 | * @returns {Promise} 12 | */ 13 | addEvent: async (request, response) => { 14 | let responseData = await eventService.createEvent(request.body); 15 | responseDataUtil.updateResponse(response, responseData); 16 | }, 17 | 18 | /** 19 | * Fetch all Events in system. 20 | * @param request 21 | * @param response 22 | * @returns {Promise} 23 | */ 24 | getAllEvents: async (request, response) => { 25 | let responseData = await eventService.findAllEvents(); 26 | responseDataUtil.updateResponse(response, responseData); 27 | }, 28 | 29 | /* Only for testing */ 30 | testEvent: (req, res) => { 31 | eventService.getTestEvent(); 32 | res.status(200).json({ 33 | success: true, 34 | message: 'This is test message.', 35 | }); 36 | }, 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /src/util/ResponseDataUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handle Response Object either Success or Failure and its convert into proper structure here. 3 | */ 4 | const ResponseDataUtil = { 5 | 6 | create: (statusCode, success, message) => { 7 | return { 8 | success: status, 9 | statusCode: statusCode, 10 | message: message 11 | }; 12 | }, 13 | 14 | create: (statusCode, success, message, data) => { 15 | return { 16 | success: success, 17 | statusCode: statusCode, 18 | message: message, 19 | data: data, 20 | }; 21 | }, 22 | 23 | createDefaultError: () => { 24 | return { 25 | success: false, 26 | statusCode: 500, 27 | message: 'An Error Occured.' 28 | }; 29 | }, 30 | 31 | updateResponse: (response, responseData) => { 32 | let jsonData = { 33 | success: responseData.success, 34 | message: responseData.message 35 | }; 36 | if (responseData.data) { 37 | jsonData.data = responseData.data; 38 | } 39 | response 40 | .status(responseData.statusCode) 41 | .json(jsonData); 42 | } 43 | 44 | }; 45 | 46 | module.exports = ResponseDataUtil; 47 | -------------------------------------------------------------------------------- /test/data/event-data.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../src/models/Event'); 2 | 3 | module.exports = { 4 | 5 | eventModel: { 6 | "end": "August 31, 2019 02:15", 7 | "start": "August 31, 2019 23:15", 8 | "title": "Test Event", 9 | "details": "Testing Event Bangok Stadium", 10 | "location": { 11 | "address": "1, bangok Street", 12 | "latLng": { 13 | "lat": 13.798159498972458, 14 | "lng": 100.53689315915108 15 | } 16 | }, 17 | "userId": "TEST_USER_1", 18 | }, 19 | 20 | invalidEventModel: { 21 | "end": "August 30, 2019 02:15", 22 | "start": "August 30, 2019 23:15", 23 | "location": { 24 | "address": "1, bangok Street", 25 | "latLng": { 26 | "lat": 13.798159498972458, 27 | "lng": 100.53689315915108 28 | } 29 | }, 30 | "userId": "TEST_USER_1", 31 | }, 32 | 33 | event: () => { 34 | var event = new Event({ 35 | "start": '2019-08-31T23:15:00.000Z', 36 | "end": '2019-08-31T02:15:00.000Z', 37 | "timeZoneOffset": -480, 38 | "title": 'Test Event', 39 | "details": 'Testing Event Bangok Stadium', 40 | "location": { 41 | "address": "1, bangok Street", 42 | "latLng": { 43 | "lat": 13.798159498972458, 44 | "lng": 100.53689315915108 45 | } 46 | }, 47 | "userId": "TEST_USER_2", 48 | }); 49 | return event; 50 | }, 51 | 52 | }; -------------------------------------------------------------------------------- /src/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | const userService = require('../services/UserService'); 2 | const responseDataUtil = require('../util/ResponseDataUtil'); 3 | 4 | 5 | const userController = { 6 | 7 | /* Only for testing */ 8 | testUser: (req, res) => { 9 | userService.test(); 10 | res.status(200).json({ 11 | success: true, 12 | message: 'This is test message.', 13 | }); 14 | }, 15 | 16 | /** 17 | * Add a user in system. 18 | * @param request 19 | * @param response 20 | * @returns {Promise} 21 | */ 22 | addUser: async (request, response) => { 23 | let responseData = await userService.createUser(request.body); 24 | responseDataUtil.updateResponse(response, responseData); 25 | }, 26 | 27 | /** 28 | * Fetch all users in system. 29 | * @param request 30 | * @param response 31 | * @returns {Promise} 32 | */ 33 | getAllUser: async (request, response) => { 34 | let responseData = null; 35 | if (request.query.personalId) { 36 | responseData = await userService.findByParams(request.query.personalId); 37 | } else { 38 | responseData = await userService.findAllUser(); 39 | } 40 | responseDataUtil.updateResponse(response, responseData); 41 | }, 42 | 43 | /** 44 | * Find a User by userId or _id. 45 | * @param request 46 | * @param response 47 | * @returns {Promise} 48 | */ 49 | getOneById: async (request, response) => { 50 | let responseData = await userService.findOneById(request.params.id); 51 | responseDataUtil.updateResponse(response, responseData); 52 | }, 53 | 54 | }; 55 | 56 | module.exports = userController; 57 | -------------------------------------------------------------------------------- /src/services/UsersEventService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Write the logic and database crud operation for User and Events goes here in Service Layer. 3 | */ 4 | let User = require('../models/User'); 5 | let Event = require('../models/Event'); 6 | const responseDataUtil = require('../util/ResponseDataUtil'); 7 | 8 | 9 | const UsersEventsService = { 10 | 11 | /** 12 | * find all events for a user by user id 13 | * @param userId 14 | */ 15 | findEventsByUserId: async (userId) => { 16 | 17 | let userModel = await User.findById(userId); 18 | 19 | if (!userModel) { 20 | return responseDataUtil.create(400, false, "User not found.", userModel); 21 | } 22 | 23 | let eventModels = await Event.find({userId: userId}); 24 | userModel['events'] = eventModels; 25 | return responseDataUtil.create(200, true, "Events found.", 26 | UsersEventsService.toResultModel(userModel, eventModels)); 27 | 28 | }, 29 | 30 | /** 31 | * Find all users and their events 32 | * @returns {Promise<*|{success, statusCode, message}>} 33 | */ 34 | findEventsForAllUser: async () => { 35 | 36 | // find all users 37 | let userModels = await User.find({}); 38 | 39 | let results = []; 40 | await Promise.all(userModels.map(async (userModel) => { 41 | let _eventModels = await Event.find({userId: userModel.id}); 42 | results.push(UsersEventsService.toResultModel(userModel, _eventModels)); 43 | })); 44 | 45 | return responseDataUtil.create(200, true, "Events found.", results); 46 | }, 47 | 48 | /** 49 | * Custom result object model to send as response data 50 | * @param userModel 51 | * @param eventModels 52 | * @returns {{_id: *, name: *, personalId: (string|*|personalId|{type, required}), contactNo: (number|contactNo|{type, required}), events: *}} 53 | */ 54 | toResultModel: (userModel, eventModels) => { 55 | return { 56 | _id: userModel._id, 57 | name: userModel.name, 58 | personalId: userModel.personalId, 59 | contactNo: userModel.contactNo, 60 | events: eventModels 61 | }; 62 | } 63 | 64 | }; 65 | 66 | module.exports = UsersEventsService; 67 | -------------------------------------------------------------------------------- /src/services/EventService.js: -------------------------------------------------------------------------------- 1 | const Event = require('../models/Event'); 2 | const responseDataUtil = require('../util/ResponseDataUtil'); 3 | 4 | const EventService = { 5 | 6 | getTestEvent: () => { 7 | console.log(" Event Service method is called."); 8 | }, 9 | 10 | 11 | /** 12 | * Create a new Event 13 | * @param eventData 14 | * @returns {Promise<*>} 15 | */ 16 | createEvent: async (eventData) => { 17 | 18 | let eventModel = EventService.convertToModel(eventData); 19 | 20 | let existingEvent = await Event.findOne({ 21 | $and: [ 22 | { 23 | 'location.latLng.lng': eventModel.location.latLng.lng, 24 | 'location.latLng.lat': eventModel.location.latLng.lat, 25 | }, 26 | {start: {$gte: eventModel.start}}, 27 | {end: {$lte: eventModel.end}}, 28 | ], 29 | }); 30 | 31 | let responseData = null; 32 | 33 | if (existingEvent) { 34 | responseData = responseDataUtil 35 | .create(400, false, 'An Event already exist at this venue on this day', existingEvent); 36 | 37 | } else { 38 | 39 | await eventModel.save() 40 | .then((event) => { 41 | responseData = responseDataUtil.create(200, true, "Event Created.", event); 42 | }) 43 | .catch((err) => { 44 | console.log(err); 45 | responseData = responseDataUtil.create(500, false, 'An Error Occured'); 46 | }); 47 | } 48 | 49 | return responseData; 50 | }, 51 | 52 | /** 53 | * Fetch all Events in System 54 | * @returns {Promise<*>} 55 | */ 56 | findAllEvents: async () => { 57 | let responseData = null; 58 | await Event.find({}) 59 | .then((events) => { 60 | responseData = responseDataUtil.create(200, true, "Found Events.", events); 61 | }) 62 | .catch((err) => { 63 | console.log("Error in findAllEvents", err); 64 | responseData = responseDataUtil.createDefaultError(); 65 | }); 66 | return responseData; 67 | }, 68 | 69 | 70 | /** 71 | * Extract request body data and convert into Event model. 72 | * @param request 73 | */ 74 | convertToModel: (request) => { 75 | let eventModel = new Event(); 76 | 77 | const dateStart = new Date(request.start); 78 | dateStart.setTime( 79 | dateStart.getTime() - new Date().getTimezoneOffset() * 60 * 1000, 80 | ); 81 | eventModel.start = dateStart; 82 | 83 | const dateEnd = new Date(request.end); 84 | dateEnd.setTime( 85 | dateEnd.getTime() - new Date().getTimezoneOffset() * 60 * 1000, 86 | ); 87 | eventModel.end = dateEnd; 88 | 89 | eventModel.timeZoneOffset = new Date().getTimezoneOffset(); 90 | eventModel.title = request.title; 91 | eventModel.details = request.details; 92 | eventModel.location = request.location; 93 | eventModel.userId = request.userId; 94 | return eventModel; 95 | } 96 | 97 | }; 98 | 99 | module.exports = EventService; 100 | -------------------------------------------------------------------------------- /src/services/UserService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Write the logic and database crud operation for User goes here in Service Layer. 3 | */ 4 | const User = require('../models/User'); 5 | const responseDataUtil = require('../util/ResponseDataUtil'); 6 | 7 | const UserService = { 8 | 9 | /* only for testing */ 10 | test: () => { 11 | console.log('only test user service method'); 12 | }, 13 | 14 | /** 15 | * Create a new User 16 | * @param userData 17 | * @returns {Promise<*>} 18 | */ 19 | createUser: async (userData) => { 20 | let responseData = null; 21 | let userModel = UserService.convertToModel(userData); 22 | 23 | let existingUserModel = await User.findOne({personalId: userModel.personalId}); 24 | // Check user exist or not 25 | if (existingUserModel) { 26 | responseData = responseDataUtil 27 | .create(400, false, 'User already exist and check personal id.', 28 | existingUserModel); 29 | return responseData; 30 | } 31 | 32 | //save when user does not exist. 33 | await userModel.save() 34 | .then(function (savedUserModel) { 35 | responseData = responseDataUtil.create(201, true, "User Created.", savedUserModel); 36 | }) 37 | .catch((err) => { 38 | console.log(err); 39 | responseData = responseDataUtil.create(500, false, 'An Error Occured'); 40 | }); 41 | return responseData; 42 | }, 43 | 44 | /** 45 | * Create Get all users in system 46 | * @returns {Promise<*>} 47 | */ 48 | findAllUser: async () => { 49 | let responseData = null; 50 | await User.find({}) 51 | .then(function (users) { 52 | responseData = responseDataUtil.create(200, true, "Found Users.", users); 53 | }) 54 | .catch((err) => { 55 | console.log("Error in findAllEvents", err); 56 | responseData = responseDataUtil.createDefaultError(); 57 | }); 58 | return responseData; 59 | }, 60 | 61 | /** 62 | * Find one by Id 63 | * @param id 64 | * @returns {Promise<*>} 65 | */ 66 | findOneById: async (id) => { 67 | let responseData = null; 68 | await User.findById(id) 69 | .then(function (user) { 70 | responseData = responseDataUtil.create(200, true, "Found Users.", user); 71 | }) 72 | .catch((err) => { 73 | console.log("Error in findAllEvents", err); 74 | responseData = responseDataUtil.createDefaultError(); 75 | }); 76 | return responseData; 77 | }, 78 | 79 | /** 80 | * Find one by parameters 81 | * @param id 82 | * @returns {Promise<*>} 83 | */ 84 | findByParams: async (personalId) => { 85 | let users = await User.find({personalId: personalId}); 86 | return responseDataUtil.create(200, true, "Found Users.", users); 87 | }, 88 | 89 | 90 | /* User data validation and convert into Model */ 91 | convertToModel(userData) { 92 | return new User({ 93 | name: userData.name, 94 | contactNo: userData.contactNo, 95 | personalId: userData.personalId 96 | }); 97 | } 98 | 99 | }; 100 | 101 | 102 | module.exports = UserService; 103 | -------------------------------------------------------------------------------- /test/integration/user-events-int.js: -------------------------------------------------------------------------------- 1 | //Require the dev-dependencies 2 | let chai = require('chai'); 3 | let chaiHttp = require('chai-http'); 4 | let app = require('../../index'); 5 | let should = chai.should(); 6 | 7 | let User = require('../../src/models/User'); 8 | let userSampleData = require('../data/user-data'); 9 | let eventSampleData = require('../data/event-data'); 10 | 11 | chai.use(chaiHttp); 12 | 13 | describe('Event Controller Integration Testing.', function () { 14 | 15 | beforeEach((done) => { 16 | //Before each test we empty the database 17 | User.deleteMany({}, () => { 18 | done(); 19 | }); 20 | }); 21 | 22 | it('GET - /api/v1/users-events?userId={} find events by User id.', (done) => { 23 | let userModel = userSampleData.userModel(); 24 | userModel.personalId = new Date().getTime(); 25 | userModel.save() 26 | .then((data) => { 27 | let event = eventSampleData.event(); 28 | event.userId = data._id.toString(); 29 | return event; 30 | }) 31 | .then((event) => { 32 | return event.save(); 33 | }) 34 | .then((eventModel) => { 35 | chai.request(app) 36 | .get('/api/v1/users-events?userId=' + eventModel.userId) 37 | .end((err, res) => { 38 | res.should.have.status(200); 39 | res.body.data._id.should.be.eql(eventModel.userId); 40 | res.body.data.events[0]._id.should.be.eql(eventModel._id.toString()); 41 | res.body.data.events[0].timeZoneOffset.should.be.eql(eventModel.timeZoneOffset); 42 | res.body.data.events[0].title.should.be.eql(eventModel.title); 43 | res.body.data.events[0].details.should.be.eql(eventModel.details); 44 | res.body.data.events[0].userId.should.be.eql(eventModel.userId); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | it('GET - /api/v1/users-events find all users with events .', (done) => { 51 | let userModel = userSampleData.userModel(); 52 | userModel.personalId = new Date().getTime(); 53 | 54 | User.deleteMany({}) 55 | .then(() => { 56 | return userModel.save() 57 | }) 58 | .then((_userModel) => { 59 | let event = eventSampleData.event(); 60 | event.userId = _userModel._id.toString(); 61 | return event; 62 | }) 63 | .then((event) => { 64 | return event.save(); 65 | }) 66 | .then((eventModel) => { 67 | chai.request(app) 68 | .get('/api/v1/users-events') 69 | .end((err, res) => { 70 | res.should.have.status(200); 71 | res.body.should.have.property('success').eq(true); 72 | res.body.should.have.property('data').a('array'); 73 | res.body.data[0]._id.should.be.eql(eventModel.userId); 74 | res.body.data[0].name.should.be.eql(userModel.name); 75 | res.body.data[0].personalId.should.be.eql(userModel.personalId); 76 | res.body.data[0].events[0]._id.should.be.eql(eventModel._id.toString()); 77 | res.body.data[0].events[0].timeZoneOffset.should.be.eql(eventModel.timeZoneOffset); 78 | res.body.data[0].events[0].title.should.be.eql(eventModel.title); 79 | res.body.data[0].events[0].details.should.be.eql(eventModel.details); 80 | res.body.data[0].events[0].userId.should.be.eql(eventModel.userId); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /test/unit/event-service-unit-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let should = chai.should(); 3 | let Event = require('../../src/models/Event'); 4 | let eventTestData = require('../data/event-data'); 5 | 6 | const eventService = require('../../src/services/EventService'); 7 | const db = require('../config/db'); 8 | const {assert} = require('chai'); 9 | 10 | 11 | describe('EventService Unit Test.', function () { 12 | 13 | before((done) => { 14 | db.connect() 15 | .then(() => done()) 16 | .catch((err) => done(err)); 17 | }); 18 | 19 | after((done) => { 20 | db.close() 21 | .then(() => done()) 22 | .catch((err) => done(err)); 23 | }); 24 | 25 | it("findAllEvents() - Find All Events Method", (done) => { 26 | let testEventModel = eventTestData.event(); 27 | // create the data 28 | testEventModel.save() 29 | .then(() => { 30 | //call test method 31 | return eventService.findAllEvents(); 32 | }) 33 | .then((result) => { 34 | let data = result.data; 35 | assert.isArray(data); 36 | assert.isNotNull(data[0]._id); 37 | assert.equal(testEventModel.title, data[0].title); 38 | assert.equal(testEventModel.details, data[0].details); 39 | assert.equal(testEventModel.userId, data[0].userId); 40 | assert.equal(testEventModel.location.address, data[0].location.address); 41 | assert.equal(testEventModel.location.address, data[0].location.address); 42 | assert.equal(testEventModel.location.latLng.lat, data[0].location.latLng.lat); 43 | assert.equal(testEventModel.location.latLng.lng, data[0].location.latLng.lng); 44 | done(); 45 | }); 46 | }); 47 | 48 | 49 | it("createEvent() - Create a Event Method", (done) => { 50 | let testEventModel = eventTestData.eventModel; 51 | // create the data 52 | 53 | Event.deleteMany({}) 54 | .then(() => { 55 | return eventService.createEvent(testEventModel) 56 | }) 57 | .then((result) => { 58 | let data = result.data; 59 | assert.isNotNull(data._id); 60 | assert.equal(testEventModel.title, data.title); 61 | assert.equal(testEventModel.details, data.details); 62 | assert.equal(testEventModel.userId, data.userId); 63 | assert.equal(testEventModel.location.address, data.location.address); 64 | assert.equal(testEventModel.location.address, data.location.address); 65 | assert.equal(testEventModel.location.latLng.lat, data.location.latLng.lat); 66 | assert.equal(testEventModel.location.latLng.lng, data.location.latLng.lng); 67 | done(); 68 | }); 69 | }); 70 | 71 | 72 | it("createEvent() - Create a duplicate Event Method", (done) => { 73 | let testEventModel = eventTestData.eventModel; 74 | Event.deleteMany({}) 75 | .then(() => { 76 | return eventService.createEvent(testEventModel) 77 | }) 78 | .then(() => { 79 | // create a duplicate record 80 | return eventService.createEvent(testEventModel) 81 | }) 82 | .then((result) => { 83 | //console.log(result); 84 | let data = result.data; 85 | assert.isFalse(result.success); 86 | assert.equal(400, result.statusCode); 87 | assert.isNotNull(data._id); 88 | done(); 89 | }); 90 | }); 91 | 92 | it("createEvent() - Create a Event with Invalid Internal Error", (done) => { 93 | let testEventModel = eventTestData.invalidEventModel; 94 | // create the data 95 | 96 | Event.deleteMany({}) 97 | .then(() => { 98 | return eventService.createEvent(testEventModel) 99 | }) 100 | .then((result) => { 101 | //console.log(result); 102 | assert.isFalse(result.success); 103 | assert.equal(500, result.statusCode); 104 | done(); 105 | }); 106 | }); 107 | 108 | }); -------------------------------------------------------------------------------- /test/integration/events-int.js: -------------------------------------------------------------------------------- 1 | //Require the dev-dependencies 2 | let chai = require('chai'); 3 | let chaiHttp = require('chai-http'); 4 | let app = require('../../index'); 5 | let should = chai.should(); 6 | let event = require('../../src/models/Event'); 7 | let sampleData = require('../data/event-data'); 8 | 9 | chai.use(chaiHttp); 10 | 11 | describe('Event Controller Integration Testing.', function () { 12 | 13 | beforeEach((done) => { 14 | //Before each test we empty the database 15 | event.deleteMany({}, () => { 16 | done(); 17 | }); 18 | }); 19 | 20 | /*Case - Valid test api */ 21 | it('GET /api/v1/events/test', (done) => { 22 | chai.request(app) 23 | .get('/api/v1/events/test') 24 | .end((err, res) => { 25 | //console.log('Response - ', res.body); 26 | res.should.have.status(200); 27 | res.body.should.have.property('success').eq(true); 28 | res.body.should.have.property('message').eq('This is test message.'); 29 | done(); 30 | }); 31 | }); 32 | 33 | 34 | it('POST /api/v1/events - Create A New Event from json data', (done) => { 35 | chai.request(app) 36 | .post('/api/v1/events') 37 | .send(sampleData.eventModel) 38 | .end((err, res) => { 39 | //console.log('Response - ', res.body); 40 | res.should.have.status(200); 41 | res.body.should.have.property('success').eq(true); 42 | res.body.data.should.be.a('object'); 43 | res.body.data.should.have.property('_id').a('string'); 44 | res.body.data.should.have.property('start'); 45 | res.body.data.should.have.property('end'); 46 | res.body.data.should.have.property('timeZoneOffset'); 47 | res.body.data.should.have.property('title'); 48 | res.body.data.should.have.property('details'); 49 | res.body.data.should.have.property('createdAt'); 50 | res.body.data.should.have.property('updatedAt'); 51 | res.body.data.should.have.property('userId'); 52 | 53 | res.body.data.timeZoneOffset.should.be.eql(-480); 54 | res.body.data.title.should.be.eql(sampleData.eventModel.title); 55 | res.body.data.details.should.be.eql(sampleData.eventModel.details); 56 | res.body.data.userId.should.be.eql(sampleData.eventModel.userId); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('POST /api/v1/events - Create A Duplicate Event from json data', (done) => { 62 | sampleData.event().save() 63 | .then(function (existingEvent) { 64 | let existingEventId = existingEvent._id.toString(); 65 | chai.request(app) 66 | .post('/api/v1/events') 67 | .send(sampleData.eventModel) 68 | .end((err, res) => { 69 | //console.log('Response -> ', res.body); 70 | res.should.have.status(400); 71 | res.body.data._id.should.be.eql(existingEventId); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | 77 | 78 | it('GET /api/v1/events - Get all Events when no data exists.', (done) => { 79 | // remove all data at first 80 | event.deleteMany({}, () => { 81 | //console.log("all data cleared"); 82 | }).then(function () { 83 | chai.request(app) 84 | .get('/api/v1/events') 85 | .end((err, res) => { 86 | res.should.have.status(200); 87 | res.body.should.have.property('success').eq(true); 88 | res.body.should.have.property('data').a('array'); 89 | done(); 90 | }); 91 | }); 92 | }); 93 | 94 | 95 | it('GET /api/v1/events - Get all Events when data exists.', (done) => { 96 | // remove all data at first 97 | event.deleteMany({}, () => { 98 | //console.log("all data cleared"); 99 | }).then((data) => { 100 | return sampleData.event().save(); 101 | }).then(function (savedData) { 102 | chai.request(app) 103 | .get('/api/v1/events') 104 | .end((err, res) => { 105 | res.should.have.status(200); 106 | res.body.should.have.property('success').eq(true); 107 | res.body.should.have.property('data').a('array'); 108 | res.body.data[0]._id.should.be.eql(savedData._id.toString()); 109 | res.body.data[0].timeZoneOffset.should.be.eql(savedData.timeZoneOffset); 110 | res.body.data[0].userId.should.be.eql(savedData.userId); 111 | res.body.data[0].title.should.be.eql(savedData.title); 112 | done(); 113 | }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/unit/user-service-unit-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let should = chai.should(); 3 | let User = require('../../src/models/User'); 4 | let userTestData = require('../data/user-data'); 5 | 6 | const userService = require('../../src/services/UserService'); 7 | const db = require('../config/db'); 8 | const {assert} = require('chai') 9 | 10 | 11 | describe('UserService Unit Test.', function () { 12 | 13 | before((done) => { 14 | db.connect() 15 | .then(() => done()) 16 | .catch((err) => done(err)); 17 | }); 18 | 19 | after((done) => { 20 | db.close() 21 | .then(() => done()) 22 | .catch((err) => done(err)); 23 | }); 24 | 25 | it("findAllUser() - Find All User Method", (done) => { 26 | 27 | let testUserModel = userTestData.userModel(); 28 | // create the data 29 | testUserModel.save().then(() => { 30 | //call test method 31 | userService.findAllUser().then((result) => { 32 | let data = result.data; 33 | assert.isArray(data); 34 | assert.isNotNull(data[0]._id); 35 | assert.equal(testUserModel.name, data[0].name); 36 | assert.equal(testUserModel.contactNo, data[0].contactNo); 37 | assert.equal(testUserModel.personalId, data[0].personalId); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | 44 | it("findOneById() - Find by User id", (done) => { 45 | let testUserModel = userTestData.userModel(); 46 | // create the data 47 | testUserModel.save().then((savedUser) => { 48 | //call test method 49 | let userId = savedUser._id.toString(); 50 | userService.findOneById(userId) 51 | .then((result) => { 52 | let data = result.data; 53 | assert.isNotNull(data._id); 54 | assert.equal(testUserModel.name, data.name); 55 | assert.equal(testUserModel.contactNo, data.contactNo); 56 | assert.equal(testUserModel.personalId, data.personalId); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | 62 | 63 | it("findOneById() - Generated DB error with Status 500 ", (done) => { 64 | //call test method 65 | userService.findOneById('3076424e-e438-43b2-bef3-edbb2bad0371') 66 | .then((result) => { 67 | //console.log('error- ', result); 68 | assert.isFalse(result.success); 69 | assert.equal(500, result.statusCode); 70 | done(); 71 | }); 72 | }); 73 | 74 | 75 | it("findByParams() - Find by Parameters - personal id", (done) => { 76 | let testUserModel = userTestData.userModel(); 77 | testUserModel.personalId = new Date().getTime(); 78 | // create the data 79 | testUserModel.save().then((savedUser) => { 80 | //call test method 81 | userService.findByParams(savedUser.personalId) 82 | .then((result) => { 83 | let data = result.data; 84 | assert.isArray(data); 85 | assert.isNotNull(data[0]._id); 86 | assert.equal(testUserModel.name, data[0].name); 87 | assert.equal(testUserModel.contactNo, data[0].contactNo); 88 | assert.equal(testUserModel.personalId, data[0].personalId); 89 | done(); 90 | }); 91 | }); 92 | }); 93 | 94 | 95 | it("createUser() - Create User Method", (done) => { 96 | let testUserJson = userTestData.userJson; 97 | // create the data 98 | userService.createUser(testUserJson) 99 | .then((result) => { 100 | //console.log('saved user' ,result); 101 | let savedUser = result.data; 102 | assert.isNotNull(savedUser._id); 103 | assert.equal(testUserJson.name, savedUser.name); 104 | assert.equal(testUserJson.contactNo, savedUser.contactNo); 105 | assert.equal(testUserJson.personalId, savedUser.personalId); 106 | done(); 107 | }); 108 | }); 109 | 110 | 111 | it("createUser() - Create User Method with Error", (done) => { 112 | let testUserJson = userTestData.invalidUserJson; 113 | // create the data 114 | User.deleteMany({}) 115 | .then(() => { 116 | userService.createUser(testUserJson) 117 | .then((result) => { 118 | //console.log('saved user', result); 119 | assert.isFalse(result.success); 120 | assert.equal(500, result.statusCode); 121 | done(); 122 | }); 123 | }); 124 | }); 125 | 126 | 127 | it("createUser() - Create a Duplicate User by Personal Id", (done) => { 128 | let testUserJson = userTestData.userJson; 129 | User.deleteMany({}) 130 | .then(() => { 131 | return userService.createUser(testUserJson); 132 | }) 133 | .then(() => { 134 | return userService.createUser(testUserJson); 135 | }) 136 | .then((result) => { 137 | //console.log('result', result); 138 | assert.isFalse(result.success); 139 | assert.equal(400, result.statusCode); 140 | done(); 141 | }); 142 | }); 143 | 144 | }); -------------------------------------------------------------------------------- /test/integration/user-int.js: -------------------------------------------------------------------------------- 1 | //Require the dev-dependencies 2 | let chai = require('chai'); 3 | let chaiHttp = require('chai-http'); 4 | let app = require('../../index'); 5 | let should = chai.should(); 6 | let User = require('../../src/models/User'); 7 | let userTestData = require('../data/user-data'); 8 | 9 | 10 | chai.use(chaiHttp); 11 | 12 | describe('Users API Integration Testing.', function () { 13 | 14 | 15 | beforeEach((done) => { 16 | //Before each test we empty the database 17 | User.deleteMany({}, () => { 18 | done(); 19 | }); 20 | }); 21 | 22 | /*Case - Valid test api */ 23 | it('GET /api/v1/users/test', (done) => { 24 | chai.request(app) 25 | .get('/api/v1/users/test') 26 | .end((err, res) => { 27 | //console.log('Response - ', res.body); 28 | res.should.have.status(200); 29 | res.body.should.have.property('success').eq(true); 30 | res.body.should.have.property('message').eq('This is test message.'); 31 | done(); 32 | }); 33 | }); 34 | 35 | 36 | it('GET /api/v1/testing 404 Path not found.', (done) => { 37 | chai.request(app) 38 | .get('/api/v1/user-testing') 39 | .end((err, res) => { 40 | res.should.have.status(404); 41 | //console.log('Response - ', res.body); 42 | done(); 43 | }); 44 | }); 45 | 46 | 47 | it('POST /api/v1/users - Create new User from json data', (done) => { 48 | chai.request(app) 49 | .post('/api/v1/users') 50 | .send(userTestData.userJson) 51 | .end((err, res) => { 52 | //console.log('Response - ', res.body); 53 | res.should.have.status(201); 54 | res.body.should.have.property('success').eq(true); 55 | res.body.data.should.be.a('object'); 56 | res.body.data.should.have.property('_id').a('string'); 57 | res.body.data.should.have.property('name'); 58 | res.body.data.should.have.property('contactNo'); 59 | res.body.data.should.have.property('personalId'); 60 | res.body.data.should.have.property('createdAt'); 61 | res.body.data.should.have.property('updatedAt'); 62 | 63 | res.body.data.name.should.be.eql(userTestData.userJson.name); 64 | res.body.data.contactNo.should.be.eql(userTestData.userJson.contactNo); 65 | res.body.data.personalId.should.be.eql(userTestData.userJson.personalId); 66 | done(); 67 | }); 68 | }); 69 | 70 | 71 | it('POST /api/v1/users - Create duplicate user from json data', (done) => { 72 | userTestData.userModel() 73 | .save() 74 | .then(function (savedUser) { 75 | let savedUserId = savedUser._id.toString(); 76 | chai.request(app) 77 | .post('/api/v1/users') 78 | .send(userTestData.userJson) 79 | .end((err, res) => { 80 | //console.log('Response - ', res.body); 81 | res.should.have.status(400); 82 | res.body.data._id.should.be.eql(savedUserId); 83 | done(); 84 | }); 85 | }); 86 | }); 87 | 88 | it('GET /api/v1/users - Get all Users .', (done) => { 89 | // remove all data at first 90 | User.deleteMany({}) 91 | .then(userTestData.userModel().save()) 92 | .then(function (savedUser) { 93 | chai.request(app) 94 | .get('/api/v1/users') 95 | .end((err, res) => { 96 | //console.log('Response - ', res.body); 97 | res.should.have.status(200); 98 | res.body.should.have.property('success').eq(true); 99 | res.body.should.have.property('data').a('array'); 100 | done(); 101 | }); 102 | }); 103 | }); 104 | 105 | 106 | it('GET /api/v1/users/:id - Find one by id .', (done) => { 107 | // first save one and then search for that 108 | userTestData.userModel() 109 | .save() 110 | .then(function (savedUser) { 111 | let savedUserId = savedUser._id.toString(); 112 | chai.request(app) 113 | .get('/api/v1/users/' + savedUserId) 114 | .end((err, res) => { 115 | res.should.have.status(200); 116 | res.body.data.should.have.property('_id').a('string'); 117 | res.body.data._id.should.be.eql(savedUserId); 118 | res.body.data.name.should.be.eql(savedUser.name); 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | it('GET /api/v1/users?personalId={} - Find one by personal Id.', (done) => { 125 | // first save one and then search for that 126 | let userModel = userTestData.userModel(); 127 | userModel.personalId = new Date().getTime(); 128 | userModel.save() 129 | .then(function (savedUser) { 130 | //console.log('response - ', savedUser); 131 | let savedUserId = savedUser._id.toString(); 132 | chai.request(app) 133 | .get('/api/v1/users?personalId=' + savedUser.personalId) 134 | .end((err, res) => { 135 | //console.log('response - ', res.body); 136 | res.should.have.status(200); 137 | res.body.data[0].should.have.property('_id').a('string'); 138 | res.body.data[0]._id.should.be.eql(savedUserId); 139 | res.body.data[0].name.should.be.eql(savedUser.name); 140 | done(); 141 | }); 142 | }); 143 | }); 144 | 145 | }); 146 | -------------------------------------------------------------------------------- /SOLUTION.md: -------------------------------------------------------------------------------- 1 | 2 | # A. Bugs Fixed 3 | Code had errors like missing syntax , some lines of code were removed. These all bugs were fixed and application can run successfully. 4 | 5 | For example - 6 | 7 | 1. index.js - app variable was not defined before using. 8 | const app = express(); - this line was missing 9 | 10 | 2. mongodb connection was not defined. To fix this, db.js file created for handling db connection and imported in index.js file to create database 11 | connection when application starts. 12 | 13 | 3. EventController file line no 25 had a syntax bug which is fixed to access mongodb with different parameters. 14 | and fix is like in below - 15 | $and: [ 16 | { 17 | 'location.latLng.lng': eventModel.location.latLng.lng, 18 | 'location.latLng.lat': eventModel.location.latLng.lat, 19 | }, 20 | {start: {$gte: eventModel.start}}, 21 | {end: {$lte: eventModel.end}}, 22 | ], 23 | 4. To save data into Mongodb, we need to use save method, instead of make. 24 | fix is like 25 | Event.make(data) was fixed to event.save() 26 | Event.see({}) was fixed to event.find({}) 27 | 28 | 5. Router file method name was mismatch with Controller file. 29 | router.route('/').post(EventController.addJobsEvent); 30 | router.route('/').get(EventController.getEvents); 31 | 32 | 6. module.export was missing in router.js 33 | module.export = router 34 | 35 | 36 | # B. Solutions 37 | 38 | In the application, I prefer to use to popular opensource framework instead of new fancy technologies like Mocha, Chai, Mockgoose, Istanbul etc. These kind of framework has lot of opensource community support and help. During my expreices, I faced some sitution that, where my team used new technoglogy and later some tech problems in this framework or team members skill on technology becomes barrier for speed up development. 39 | 40 | Using popular framework, helps to get tech solution , and human resources easier. In this case, our team member focus on building business solution instead of solving some deep technology issues, which may cause lot of organisation resource like cash or time . 41 | 42 | Every application has various level of architecture as 43 | - Code architecture 44 | - System Integration Architecture 45 | - Cloud Level Architecture 46 | 47 | But here, I refactored only Code level of architecture. 48 | 49 | ## Code Architecture 50 | In this application, I have designed code into 3 major layers as Controller, Service, Model 51 | 52 | #### Controller - 53 | It handles the request, invoke services to perform that action, and process response to sending back to the requester. 54 | 55 | #### Service - 56 | It is any function which can perform any task, like calculating some formula, accessing database to read or write. Here we will use a service function to access the database for retrieving and storing the information. 57 | Also, service will not have access to the request and response object. So anything needs to be done on the request and response object will have to be done in the controller only. 58 | 59 | 60 | And overall flow of application API will be like in below- 61 | /api -> Controller -> Service -> Model -> Database 62 | 63 | 64 | #### Model- 65 | It is the data access layer to fetch and save the documents. The service layer will be invoking the models to perform any actions on the document in the database via the model. 66 | A model represents the document which can be created, updated, removed and fetched from the database 67 | 68 | ## Folder Taxonomy 69 | Designing folder taxonomy is important section in project. As project grows bigger, the folder structure gets complex and over the period of time, we need to keep 70 | refactoring to make it simple and easy to understand by folder name and file name which can express its behaviour. 71 | 72 | - src 73 | 1. config - Application Configuration files like db.js(database connection) 74 | 2. controllers - Application Controllers 75 | 3. models - Models and database schemas 76 | 4. routers - Define API end points and its routing path 77 | 5. services - Application level all kind of Business logic 78 | 6. util - Application common utility behaviours 79 | - test 80 | 1. config - for test config files like db.js ( mockdb connection) 81 | 2. integration - Integration testing files based on controllers and API 82 | 3. unit - Unit testing files 83 | 4. data - Mock json data required during testing 84 | - index.js 85 | - package.json 86 | 87 | 88 | ### Config File & Utility 89 | Application level of configuration files need to be under config folder. 90 | For eg- db.js which defines database connection path details and handles connection creation under this folder. 91 | 92 | ## Unit Testing Mocha & Mockgoose 93 | For unit testing, using mock database is important, it makes development and testing functionality faster, so that application does not require to relay on realtime db connetion like integration , cleaning up database which slows down development process. 94 | For mock database and testing framework, Mockgoose and Mocha with Chai are used in applicastion. Both are popular stable framework and lot of community support available. It makes easier trouble shooting testing issues. Chai library is used for validation of result or response data during testing. 95 | 96 | ## Integration Testing with MongoDB 97 | Integration testing need to be done integratte with database or any if anyintegration required. This application need to connect with MongoDB to run 98 | integration test cases. 99 | Integration Test files are written for each controller to make sure all api end points working fine. 100 | 101 | ## APIs 102 | 103 | Api are designed with some best practices like mentioning version , using nouns etc. 104 | 105 | - /api/v1/events - CRUD operation on event 106 | - /api/v1/users - CRUD operation on user 107 | - /api/v1/users-events - Read operation for users with events 108 | 109 | # Installation and Run 110 | 111 | To run application and test cases, npm or yarn package must be installed, if not installed, please install before running application. 112 | 113 | - To run test case with coverage - 114 | npm test 115 | or 116 | yarn test 117 | 118 | - To run application - 119 | npm start or yarn start 120 | -------------------------------------------------------------------------------- /test/unit/users-events-service-unit-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let should = chai.should(); 3 | let Event = require('../../src/models/Event'); 4 | let User = require('../../src/models/User'); 5 | let eventTestData = require('../data/event-data'); 6 | let userTestData = require('../data/user-data'); 7 | 8 | const usersEventsService = require('../../src/services/UsersEventService'); 9 | const db = require('../config/db'); 10 | const {assert} = require('chai'); 11 | 12 | 13 | describe('EventService Unit Test.', function () { 14 | 15 | before((done) => { 16 | db.connect() 17 | .then(() => done()) 18 | .catch((err) => done(err)); 19 | }); 20 | 21 | after((done) => { 22 | db.close() 23 | .then(() => done()) 24 | .catch((err) => done(err)); 25 | }); 26 | 27 | it("findEventsByUserId() - Find All Events by User Id", (done) => { 28 | 29 | let userModel = userTestData.userModel(); 30 | let eventModel = eventTestData.event(); 31 | 32 | let savedUserModel = null; 33 | let savedEventModel = null; 34 | 35 | Event.deleteMany({}) 36 | .then(() => { 37 | return User.deleteMany({}); 38 | }) 39 | .then(() => { 40 | return userModel.save(); 41 | }) 42 | .then((_savedUser) => { 43 | eventModel.userId = _savedUser._id.toString(); 44 | savedUserModel = _savedUser; 45 | return eventModel.save(); 46 | }) 47 | .then((_savedEvent) => { 48 | savedEventModel = _savedEvent; 49 | return usersEventsService.findEventsByUserId(_savedEvent.userId); 50 | }) 51 | .then((result) => { 52 | //console.log('result', JSON.stringify(result)); 53 | let data = result.data; 54 | assert.isNotNull(data._id); 55 | assert.equal(savedUserModel._id.toString(), data._id.toString()); 56 | assert.equal(savedUserModel.name, data.name); 57 | assert.equal(savedUserModel.personalId, data.personalId); 58 | assert.isArray(data.events); 59 | assert.equal(savedEventModel._id.toString(), data.events[0]._id.toString()); 60 | assert.equal(savedEventModel.title, data.events[0].title); 61 | assert.equal(savedEventModel.detail, data.events[0].detail); 62 | done(); 63 | }) 64 | 65 | }); 66 | 67 | 68 | it("findEventsForAllUser() - Find All Events Method", (done) => { 69 | 70 | let userModel = userTestData.userModel(); 71 | let eventModel = eventTestData.event(); 72 | 73 | let savedUserModel = null; 74 | let savedEventModel = null; 75 | 76 | Event.deleteMany({}) 77 | .then(() => { 78 | return User.deleteMany({}); 79 | }) 80 | .then(() => { 81 | return userModel.save(); 82 | }) 83 | .then((_savedUser) => { 84 | eventModel.userId = _savedUser._id.toString(); 85 | savedUserModel = _savedUser; 86 | return eventModel.save(); 87 | }) 88 | .then((_savedEvent) => { 89 | savedEventModel = _savedEvent; 90 | return usersEventsService.findEventsForAllUser(); 91 | }) 92 | .then((result) => { 93 | //console.log('result', JSON.stringify(result)); 94 | let data = result.data; 95 | assert.isArray(data) 96 | assert.isNotNull(data[0]._id); 97 | assert.equal(savedUserModel._id.toString(), data[0]._id.toString()); 98 | assert.equal(savedUserModel.name, data[0].name); 99 | assert.equal(savedUserModel.personalId, data[0].personalId); 100 | assert.isArray(data[0].events); 101 | assert.equal(savedEventModel._id.toString(), data[0].events[0]._id.toString()); 102 | assert.equal(savedEventModel.title, data[0].events[0].title); 103 | assert.equal(savedEventModel.detail, data[0].events[0].detail); 104 | done(); 105 | }) 106 | }); 107 | 108 | 109 | it("findEventsForAllUser() - When No record exists", (done) => { 110 | Event.deleteMany({}) 111 | .then(() => { 112 | return User.deleteMany({}); 113 | }) 114 | .then(() => { 115 | return usersEventsService.findEventsForAllUser(); 116 | }) 117 | .then((result) => { 118 | //console.log('result', (result)); 119 | let data = result.data; 120 | assert.isTrue(result.success); 121 | assert.isNotNull(data); 122 | assert.equal(0, data.length); 123 | done(); 124 | }) 125 | }); 126 | 127 | it("findEventsByUserId() - When user does not events ", (done) => { 128 | 129 | let userModel = userTestData.userModel(); 130 | let savedUserModel = null; 131 | 132 | Event.deleteMany({}) 133 | .then(() => { 134 | return User.deleteMany({}); 135 | }) 136 | .then(() => { 137 | return userModel.save(); 138 | }) 139 | .then((_savedUser) => { 140 | savedUserModel = _savedUser; 141 | return usersEventsService.findEventsByUserId(_savedUser._id.toString()); 142 | }) 143 | .then((result) => { 144 | console.log('result', (result)); 145 | let data = result.data; 146 | assert.isNotNull(data._id); 147 | assert.equal(savedUserModel._id.toString(), data._id.toString()); 148 | assert.equal(savedUserModel.name, data.name); 149 | assert.equal(savedUserModel.personalId, data.personalId); 150 | assert.isArray(data.events); 151 | assert.equal(0, data.events.length); 152 | done(); 153 | }) 154 | }); 155 | 156 | it("findEventsByUserId() - When user does not exist ", (done) => { 157 | User.deleteMany({}) 158 | .then(() => { 159 | return usersEventsService.findEventsByUserId('5d6a62c30e42f714202515fa'); 160 | }) 161 | .then((result) => { 162 | //console.log('result', (result)); 163 | assert.isFalse(result.success); 164 | assert.equal(400, result.statusCode); 165 | done(); 166 | }) 167 | }); 168 | 169 | }); --------------------------------------------------------------------------------