diff --git a/.eslintrc b/.eslintrc index 6b4e8bd2..1042ccec 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,8 @@ "mocha": true }, "rules": { - "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js"]}], + "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "**/serviceMocks.js"]}], + max-len: ["error", { "ignoreComments": true, code: 120 }], "valid-jsdoc": ["error", { "requireReturn": true, "requireReturnType": true, diff --git a/migrations/sync.js b/migrations/sync.js index bf4f2277..023b2d06 100644 --- a/migrations/sync.js +++ b/migrations/sync.js @@ -1,5 +1,6 @@ +/* eslint-disable no-console */ /** * Sync the database models to db tables. */ @@ -10,7 +11,7 @@ */ // process.env.NODE_ENV = 'development' -require('./dist/models').default.sequelize.sync({ force: true }) +require('../dist/models').default.sequelize.sync({ force: true }) .then(() => { console.log('Database synced successfully'); process.exit(); diff --git a/src/events/projects/index.js b/src/events/projects/index.js index c2499b18..1cf66edf 100644 --- a/src/events/projects/index.js +++ b/src/events/projects/index.js @@ -1,6 +1,4 @@ -import config from 'config'; -import querystring from 'querystring'; -import util from '../../util'; + /** * Creates a lead in salesforce for the connect project. diff --git a/src/index.js b/src/index.js index 4a37f70b..4e73d87c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ + const app = require('./app'); const coreLib = require('tc-core-library-js'); const expressListRoutes = require('express-list-routes'); diff --git a/src/middlewares/checkRole.js b/src/middlewares/checkRole.js index 1144e647..156d378d 100644 --- a/src/middlewares/checkRole.js +++ b/src/middlewares/checkRole.js @@ -7,14 +7,16 @@ * @version 1.0 */ import config from 'config'; + const util = require('tc-core-library-js').util(config); -module.exports = function (roleName) { - return function (req, res, next) { - if (!req.authUser || !Array.isArray(req.authUser.roles) || req.authUser.roles.indexOf(roleName) == -1) { +module.exports = function defineCheckRole(roleName) { + return function checkRoleMiddleware(req, res, next) { + if (!req.authUser || !Array.isArray(req.authUser.roles) || + req.authUser.roles.indexOf(roleName) === -1) { return res.status(403) - .json(util.wrapErrorResponse(req.id, 403, 'You are not allowed to perform this action.')); + .json(util.wrapErrorResponse(req.id, 403, 'You are not allowed to perform this action.')); } - next(); + return next(); }; }; diff --git a/src/mocks/addBillingAccount.js b/src/mocks/addBillingAccount.js index 3af425e9..d53b0ed0 100644 --- a/src/mocks/addBillingAccount.js +++ b/src/mocks/addBillingAccount.js @@ -1,4 +1,7 @@ +/* eslint-disable max-len */ + const http = require('https'); +const _ = require('lodash'); const options = { method: 'POST', @@ -21,10 +24,7 @@ const req = http.request(options, (res) => { chunks.push(chunk); }); - res.on('end', () => { - const body = Buffer.concat(chunks); - console.log(body.toString()); - }); + res.on('end', _.noop); }); req.write('{\n "billingAccountId": 123456789\n}'); req.end(); diff --git a/src/mocks/addCopilot.js b/src/mocks/addCopilot.js index c6b17ec7..6310453d 100644 --- a/src/mocks/addCopilot.js +++ b/src/mocks/addCopilot.js @@ -1,4 +1,7 @@ +/* eslint-disable max-len */ + const http = require('https'); +const _ = require('lodash'); const options = { method: 'POST', @@ -21,10 +24,7 @@ const req = http.request(options, (res) => { chunks.push(chunk); }); - res.on('end', () => { - const body = Buffer.concat(chunks); - console.log(body.toString()); - }); + res.on('end', _.noop); }); req.write('{\n "copilotUserId": 123456789\n}'); req.end(); diff --git a/src/mocks/createProject.js b/src/mocks/createProject.js index 4aca9b40..1f04482b 100644 --- a/src/mocks/createProject.js +++ b/src/mocks/createProject.js @@ -1,4 +1,7 @@ +/* eslint-disable max-len */ + const http = require('https'); +const _ = require('lodash'); const options = { method: 'POST', @@ -21,10 +24,7 @@ const req = http.request(options, (res) => { chunks.push(chunk); }); - res.on('end', () => { - const body = Buffer.concat(chunks); - console.log(body.toString()); - }); + res.on('end', _.noop); }); req.write('{\n "projectName": "Tony Test 1",\n "projectDescription": "test 1 description"\n}'); diff --git a/src/mocks/direct.js b/src/mocks/direct.js index 4dabcfdc..2b7df972 100644 --- a/src/mocks/direct.js +++ b/src/mocks/direct.js @@ -1,17 +1,20 @@ -import express from 'express'; +import express, { Router } from 'express'; import _ from 'lodash'; import bodyParser from 'body-parser'; import config from 'config'; import coreLib from 'tc-core-library-js'; import expressRequestId from 'express-request-id'; -import Router from 'express'; import https from 'https'; import path from 'path'; import fs from 'fs'; -config.version = 'v3'; + const util = require('tc-core-library-js').util(config); +const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; + +config.version = 'v3'; + const app = express(); app.use(bodyParser.urlencoded({ extended: false, @@ -33,10 +36,9 @@ app.use(coreLib.middleware.logger(null, logger)); app.logger = logger; const router = Router(); -const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; router.all('/v3/direct/projects*', jwtAuth()); -let projectId = 2; +const projectId = 2; const projects = { 1: { projectName: 'test direct project1', @@ -53,9 +55,10 @@ router.route('/v3/direct/projects') app.logger.info('get direct projects'); res.json(util.wrapResponse(req.id, { projects })); }) - .post((req, res) => { + .post((freq, res) => { + const req = freq; app.logger.info({ body: req.body }, 'create direct project'); - const newId = projectId++; + const newId = projectId + 1; req.body.id = newId; projects[newId] = req.body; res.json(util.wrapResponse(req.id, { projectId: newId })); @@ -63,47 +66,48 @@ router.route('/v3/direct/projects') router.route('/v3/direct/projects/:projectId(\\d+)/billingaccount') .post((req, res) => { - const projectId = req.params.projectId; - app.logger.info({ body: req.body, projectId }, 'add billingaccount to Project'); - if (projects[projectId]) { - projects[projectId] = _.merge(projects[projectId], req.body); - res.json(util.wrapResponse(req.id, { billingAccountName: `mock account name for ${req.body.billingAccountId}` })); + const pId = req.params.projectId; + app.logger.info({ body: req.body, pId }, 'add billingaccount to Project'); + if (projects[pId]) { + projects[pId] = _.merge(projects[pId], req.body); + res.json(util.wrapResponse(req.id, { billingAccountName: 'mock account name for ' + + `${req.body.billingAccountId}` })); } else { - res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${projectId}`)); + res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${pId}`)); } }); router.route('/v3/direct/projects/:projectId(\\d+)/copilot') .post((req, res) => { - const projectId = req.params.projectId; - app.logger.info({ body: req.body, projectId }, 'add copilot to Project'); - if (projects[projectId]) { - projects[projectId] = _.merge(projects[projectId], req.body); - res.json(util.wrapResponse(req.id, { copilotProjectId: projectId })); + const pId = req.params.projectId; + app.logger.info({ body: req.body, pId }, 'add copilot to Project'); + if (projects[pId]) { + projects[pId] = _.merge(projects[pId], req.body); + res.json(util.wrapResponse(req.id, { copilotProjectId: pId })); } else { - res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${projectId}`)); + res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${pId}`)); } }) .delete((req, res) => { - const projectId = req.params.projectId; - app.logger.info({ body: req.body, projectId }, 'remove copilot from Project'); - if (projects[projectId]) { - projects[projectId] = _.omit(projects[projectId], 'copilotUserId'); + const pId = req.params.projectId; + app.logger.info({ body: req.body, pId }, 'remove copilot from Project'); + if (projects[pId]) { + projects[pId] = _.omit(projects[pId], 'copilotUserId'); res.json(util.wrapResponse(req.id, true)); } else { - res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${projectId}`)); + res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${pId}`)); } }); router.route('/v3/direct/projects/:projectId(\\d+)/permissions') .post((req, res) => { - const projectId = req.params.projectId; - app.logger.info({ body: req.body, projectId }, 'add permissions to Project'); - if (projects[projectId]) { + const pId = req.params.projectId; + app.logger.info({ body: req.body, pId }, 'add permissions to Project'); + if (projects[pId]) { res.json(); } else { - res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${projectId}`)); + res.json(util.wrapErrorResponse(req.id, 404, `Cannot find direct project ${pId}`)); } }); diff --git a/src/models/project.js b/src/models/project.js index d565e72c..edaa7999 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -1,9 +1,10 @@ +/* eslint-disable valid-jsdoc */ -import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants'; import _ from 'lodash'; +import { PROJECT_TYPE, PROJECT_STATUS, PROJECT_MEMBER_ROLE } from '../constants'; -module.exports = function (sequelize, DataTypes) { - var Project = sequelize.define('Project', { +module.exports = function defineProject(sequelize, DataTypes) { + const Project = sequelize.define('Project', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, directProjectId: DataTypes.BIGINT, billingAccountId: DataTypes.BIGINT, @@ -65,15 +66,17 @@ module.exports = function (sequelize, DataTypes) { return this.findAll({ where: { $or: [ - ['EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "userId" = ? )', userId], - ['"Project".status=? AND NOT EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" IS NULL AND "projectId" = "Project".id AND "role" = ? )', + ['EXISTS(SELECT * FROM "project_members" WHERE "deletedAt" ' + + 'IS NULL AND "projectId" = "Project".id AND "userId" = ? )', userId], + ['"Project".status=? AND NOT EXISTS(SELECT * FROM "project_members" WHERE ' + + ' "deletedAt" IS NULL AND "projectId" = "Project".id AND "role" = ? )', PROJECT_STATUS.REVIEWED, PROJECT_MEMBER_ROLE.COPILOT], ], }, attributes: ['id'], raw: true, }) - .then(res => _.map(res, 'id')); + .then(res => _.map(res, 'id')); }, /** * Get direct project id @@ -139,10 +142,11 @@ module.exports = function (sequelize, DataTypes) { logging: (str) => { log.debug(str); }, raw: true, }) - .then((count) => { - count = count[0].count; + .then((fcount) => { + const count = fcount[0].count; // select project attributes - return sequelize.query(`SELECT ${attributesStr} FROM projects WHERE ${query} ORDER BY ${orderStr} LIMIT ${parameters.limit} OFFSET ${parameters.offset}`, + return sequelize.query(`SELECT ${attributesStr} FROM projects WHERE ${query} ORDER BY ` + + ` ${orderStr} LIMIT ${parameters.limit} OFFSET ${parameters.offset}`, { type: sequelize.QueryTypes.SELECT, logging: (str) => { log.debug(str); }, raw: true, diff --git a/src/models/projectAttachment.js b/src/models/projectAttachment.js index c7825fc2..8fab5508 100644 --- a/src/models/projectAttachment.js +++ b/src/models/projectAttachment.js @@ -1,6 +1,6 @@ -module.exports = function (sequelize, DataTypes) { +module.exports = function defineProjectAttachment(sequelize, DataTypes) { const ProjectAttachment = sequelize.define('ProjectAttachment', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, title: { type: DataTypes.STRING, allowNull: true }, diff --git a/src/models/projectHistory.js b/src/models/projectHistory.js index 9b243810..23bdbe93 100644 --- a/src/models/projectHistory.js +++ b/src/models/projectHistory.js @@ -1,6 +1,6 @@ -module.exports = function (sequelize, DataTypes) { +module.exports = function defineProjectHistory(sequelize, DataTypes) { const ProjectHistory = sequelize.define('ProjectHistory', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, projectId: { type: DataTypes.BIGINT, allowNull: false }, diff --git a/src/models/projectMember.js b/src/models/projectMember.js index a8b858b7..2ab4c94b 100644 --- a/src/models/projectMember.js +++ b/src/models/projectMember.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import { PROJECT_MEMBER_ROLE } from '../constants'; -module.exports = function (sequelize, DataTypes) { +module.exports = function defineProjectMember(sequelize, DataTypes) { const ProjectMember = sequelize.define('ProjectMember', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, userId: DataTypes.BIGINT, diff --git a/src/permissions/index.js b/src/permissions/index.js index 5465845a..f8563d6a 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -1,19 +1,23 @@ const Authorizer = require('tc-core-library-js').Authorizer; +const projectView = require('./project.view'); +const projectEdit = require('./project.edit'); +const projectDelete = require('./project.delete'); +const projectMemberDelete = require('./projectMember.delete'); module.exports = () => { Authorizer.setDeniedStatusCode(403); // anyone can create a project Authorizer.setPolicy('project.create', true); - Authorizer.setPolicy('project.view', require('./project.view')); - Authorizer.setPolicy('project.edit', require('./project.edit')); - Authorizer.setPolicy('project.delete', require('./project.delete')); - Authorizer.setPolicy('project.addMember', require('./project.view')); - Authorizer.setPolicy('project.removeMember', require('./projectMember.delete')); - Authorizer.setPolicy('project.addAttachment', require('./project.edit')); - Authorizer.setPolicy('project.updateAttachment', require('./project.edit')); - Authorizer.setPolicy('project.removeAttachment', require('./project.edit')); - Authorizer.setPolicy('project.updateMember', require('./project.edit')); + Authorizer.setPolicy('project.view', projectView); + Authorizer.setPolicy('project.edit', projectEdit); + Authorizer.setPolicy('project.delete', projectDelete); + Authorizer.setPolicy('project.addMember', projectView); + Authorizer.setPolicy('project.removeMember', projectMemberDelete); + Authorizer.setPolicy('project.addAttachment', projectEdit); + Authorizer.setPolicy('project.updateAttachment', projectEdit); + Authorizer.setPolicy('project.removeAttachment', projectEdit); + Authorizer.setPolicy('project.updateMember', projectEdit); }; diff --git a/src/permissions/project.delete.js b/src/permissions/project.delete.js index b532798c..c07479d3 100644 --- a/src/permissions/project.delete.js +++ b/src/permissions/project.delete.js @@ -1,27 +1,29 @@ /* globals Promise */ +import _ from 'lodash'; import util from '../util'; import models from '../models'; import { USER_ROLE, PROJECT_MEMBER_ROLE } from '../constants'; -import _ from 'lodash'; /** * Super admin, Topcoder Managers are allowed to edit any project * Rest can add members only if they are currently part of the project team. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise */ -module.exports = req => new Promise((resolve, reject) => { - const projectId = _.parseInt(req.params.projectId); +module.exports = freq => new Promise((resolve, reject) => { + const projectId = _.parseInt(freq.params.projectId); return models.ProjectMember.getActiveProjectMembers(projectId) .then((members) => { + const req = freq; req.context = req.context || {}; req.context.currentProjectMembers = members; // check if auth user has acecss to this project - const hasAccess = util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) - || !_.isUndefined(_.find(members, m => m.userId === req.authUser.userId - && ( - m.role === PROJECT_MEMBER_ROLE.CUSTOMER && m.isPrimary - || m.role === PROJECT_MEMBER_ROLE.MANAGER))); + const hasAccess = util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) || + !_.isUndefined(_.find(members, m => m.userId === req.authUser.userId && + ((m.role === PROJECT_MEMBER_ROLE.CUSTOMER && m.isPrimary) || + m.role === PROJECT_MEMBER_ROLE.MANAGER))); if (!hasAccess) { // user is not an admin nor is a registered project member diff --git a/src/permissions/project.edit.js b/src/permissions/project.edit.js index 62854066..57d56847 100644 --- a/src/permissions/project.edit.js +++ b/src/permissions/project.edit.js @@ -1,19 +1,21 @@ -/* globals Promise */ +import _ from 'lodash'; import util from '../util'; import models from '../models'; import { USER_ROLE } from '../constants'; -import _ from 'lodash'; /** * Super admin, Topcoder Managers are allowed to edit any project * Rest can add members only if they are currently part of the project team. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise */ -module.exports = req => new Promise((resolve, reject) => { - const projectId = _.parseInt(req.params.projectId); +module.exports = freq => new Promise((resolve, reject) => { + const projectId = _.parseInt(freq.params.projectId); return models.ProjectMember.getActiveProjectMembers(projectId) .then((members) => { + const req = freq; req.context = req.context || {}; req.context.currentProjectMembers = members; // check if auth user has acecss to this project diff --git a/src/permissions/project.view.js b/src/permissions/project.view.js index 26e64740..91776d1a 100644 --- a/src/permissions/project.view.js +++ b/src/permissions/project.view.js @@ -1,21 +1,22 @@ -/* globals Promise */ - +import _ from 'lodash'; import util from '../util'; import models from '../models'; import { USER_ROLE } from '../constants'; -import _ from 'lodash'; /** * Super admin, Topcoder Managers are allowed to view any projects * Co-pilots can view projects they are part of or if no other co-pilot has been * assigned. Others can only view projcets that they are part of. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise */ -module.exports = req => new Promise((resolve, reject) => { - const projectId = _.parseInt(req.params.projectId); - const currentUserId = req.authUser.userId; +module.exports = freq => new Promise((resolve, reject) => { + const projectId = _.parseInt(freq.params.projectId); + const currentUserId = freq.authUser.userId; return models.ProjectMember.getActiveProjectMembers(projectId) .then((members) => { + const req = freq; req.context = req.context || {}; req.context.currentProjectMembers = members; // check if auth user has acecss to this project @@ -23,7 +24,8 @@ module.exports = req => new Promise((resolve, reject) => { || util.hasRole(req, USER_ROLE.MANAGER) || !_.isUndefined(_.find(members, m => m.userId === currentUserId)); - // if user is co-pilot and the project doesn't have any copilots then user can access the project + // if user is co-pilot and the project doesn't have any copilots then + // user can access the project if (util.hasRole(req, USER_ROLE.COPILOT)) { return models.Project.getProjectIdsForCopilot(currentUserId) .then((ids) => { diff --git a/src/permissions/projectMember.delete.js b/src/permissions/projectMember.delete.js index 9a07c185..634bb557 100644 --- a/src/permissions/projectMember.delete.js +++ b/src/permissions/projectMember.delete.js @@ -1,37 +1,40 @@ - -/* globals Promise */ - +import _ from 'lodash'; import util from '../util'; import models from '../models'; -import { USER_ROLE, PROJECT_MEMBER_ROLE } from '../constants'; -import _ from 'lodash'; +import { + USER_ROLE, + PROJECT_MEMBER_ROLE, +} from '../constants'; + /** * Super admin, Topcoder Managers are allowed to edit any project * Rest can add members only if they are currently part of the project team. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise */ -module.exports = req => new Promise((resolve, reject) => { - const projectId = _.parseInt(req.params.projectId); +module.exports = freq => new Promise((resolve, reject) => { + const projectId = _.parseInt(freq.params.projectId); return models.ProjectMember.getActiveProjectMembers(projectId) - .then((members) => { - req.context = req.context || {}; - req.context.currentProjectMembers = members; - const authMember = _.find(members, m => m.userId === req.authUser.userId); - const prjMemberId = _.parseInt(req.params.id); - const memberToBeRemoved = _.find(members, m => m.id === prjMemberId); - // check if auth user has acecss to this project - const hasAccess = util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) - || authMember && memberToBeRemoved && ( - authMember.role === PROJECT_MEMBER_ROLE.MANAGER - || authMember.role === PROJECT_MEMBER_ROLE.CUSTOMER && authMember.isPrimary - && memberToBeRemoved.role === PROJECT_MEMBER_ROLE.CUSTOMER - || memberToBeRemoved.userId === req.authUser.userId); + .then((members) => { + const req = freq; + req.context = req.context || {}; + req.context.currentProjectMembers = members; + const authMember = _.find(members, m => m.userId === req.authUser.userId); + const prjMemberId = _.parseInt(req.params.id); + const memberToBeRemoved = _.find(members, m => m.id === prjMemberId); + // check if auth user has acecss to this project + const hasAccess = util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) + || (authMember && memberToBeRemoved && (authMember.role === PROJECT_MEMBER_ROLE.MANAGER || + (authMember.role === PROJECT_MEMBER_ROLE.CUSTOMER && authMember.isPrimary && + memberToBeRemoved.role === PROJECT_MEMBER_ROLE.CUSTOMER) || + memberToBeRemoved.userId === req.authUser.userId)); - if (!hasAccess) { - // user is not an admin nor is a registered project member - return reject(new Error('You do not have permissions to perform this action')); - } - return resolve(true); - }); + if (!hasAccess) { + // user is not an admin nor is a registered project member + return reject(new Error('You do not have permissions to perform this action')); + } + return resolve(true); + }); }); diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 53abd5a7..c0cbedf1 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -8,10 +8,9 @@ import _ from 'lodash'; import config from 'config'; import Joi from 'joi'; import path from 'path'; - +import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; const permissions = tcMiddleware.permissions; @@ -113,19 +112,24 @@ module.exports = [ }) .then((resp) => { req.log.debug('Retreiving Presigned Url resp: ', JSON.stringify(resp.data, null, 2)); - if (resp.status !== 200 || resp.data.result.status !== 200) { - return Promise.reject(new Error('Unable to fetch pre-signed url')); - } - let response = _.cloneDeep(newAttachment); - response = _.omit(response, ['filePath', 'deletedAt']); + return new Promise((accept, reject) => { + if (resp.status !== 200 || resp.data.result.status !== 200) { + reject(new Error('Unable to fetch pre-signed url')); + } else { + let response = _.cloneDeep(newAttachment); + response = _.omit(response, ['filePath', 'deletedAt']); - response.downloadUrl = resp.data.result.content.preSignedURL; - res.status(201).json(util.wrapResponse(req.id, response, 1, 201)); + response.downloadUrl = resp.data.result.content.preSignedURL; + res.status(201).json(util.wrapResponse(req.id, response, 1, 201)); + accept(); + } + }); }) .catch((err) => { req.log.error('Error adding attachment', err); - err.status = err.status || 500; - next(err); + const rerr = err; + rerr.status = rerr.status || 500; + next(rerr); }); }, ]; diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js index 98617cb2..2aff8b54 100644 --- a/src/routes/attachments/create.spec.js +++ b/src/routes/attachments/create.spec.js @@ -1,8 +1,7 @@ - +/* eslint-disable no-unused-expressions */ import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; - import server from '../../app'; import models from '../../models'; import util from '../../util'; @@ -99,10 +98,7 @@ describe('Project Attachments', () => { const getSpy = sinon.spy(mockHttpClient, 'get'); const stub = sinon.stub(util, 'getHttpClient', () => mockHttpClient); // mock util s3FileTransfer - util.s3FileTransfer = (req, source, dest) => { - console.log(source, dest); - return Promise.resolve(true); - }; + util.s3FileTransfer = () => Promise.resolve(true); request(server) .post(`/v4/projects/${project1.id}/attachments/`) .set({ @@ -113,22 +109,18 @@ describe('Project Attachments', () => { .expect(201) .end((err, res) => { if (err) { - console.log(err); - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + postSpy.should.have.been.calledOnce; + getSpy.should.have.been.calledOnce; + stub.restore(); + resJson.title.should.equal('Spec.pdf'); + resJson.downloadUrl.should.exist; + resJson.projectId.should.equal(project1.id); + done(); } - - const resJson = res.body.result.content; - should.exist(resJson); - - - postSpy.should.have.been.calledOnce; - getSpy.should.have.been.calledOnce; - stub.restore(); - console.log(JSON.stringify(resJson, null, 2)); - resJson.title.should.equal('Spec.pdf'); - resJson.downloadUrl.should.exist; - resJson.projectId.should.equal(project1.id); - done(); }); }); }); diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index a76b918e..b5aa452c 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -2,12 +2,11 @@ // import validate from 'express-validation' import _ from 'lodash'; - -import models from '../../models'; -import fileService from '../../services/fileService'; import { middleware as tcMiddleware, } from 'tc-core-library-js'; +import models from '../../models'; +import fileService from '../../services/fileService'; /** * API to delete a project member. diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js index 8634f941..87727788 100644 --- a/src/routes/attachments/delete.spec.js +++ b/src/routes/attachments/delete.spec.js @@ -1,6 +1,5 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; -import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; @@ -11,9 +10,8 @@ import testUtil from '../../tests/util'; describe('Project Attachments delete', () => { - let project1, - member1, - attachment; + let project1; + let attachment; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -37,23 +35,20 @@ describe('Project Attachments delete', () => { isPrimary: true, createdBy: 1, updatedBy: 1, - }).then((pm) => { - member1 = pm; - return models.ProjectAttachment.create({ - projectId: project1.id, - title: 'test.txt', - description: 'blah', - contentType: 'application/unknown', - size: 12312, - category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', - createdBy: 1, - updatedBy: 1, - }).then((a1) => { - attachment = a1; - done(); - }); - }); + }).then(() => models.ProjectAttachment.create({ + projectId: project1.id, + title: 'test.txt', + description: 'blah', + contentType: 'application/unknown', + size: 12312, + category: null, + filePath: 'https://media.topcoder.com/projects/1/test.txt', + createdBy: 1, + updatedBy: 1, + }).then((a1) => { + attachment = a1; + done(); + })); }); }); }); @@ -116,17 +111,11 @@ describe('Project Attachments delete', () => { .expect(204) .end((err) => { if (err) { - return done(err); + done(err); + } else { + deleteSpy.should.have.been.calledOnce; + done(); } - deleteSpy.should.have.been.calledOnce; - done(); - // models.ProjectAttachment - // .count({}) - // .then(count=>{ - // count.should.equal(0) - // done() - // }) - // .catch(err => done(err)) }); }); }); diff --git a/src/routes/attachments/update.js b/src/routes/attachments/update.js index 3cb7e40e..e5ee7a34 100644 --- a/src/routes/attachments/update.js +++ b/src/routes/attachments/update.js @@ -2,12 +2,11 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; - -import models from '../../models'; -import util from '../../util'; import { middleware as tcMiddleware, } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; /** * API to update a project member. @@ -43,19 +42,21 @@ module.exports = [ }, returning: true, }) - .then((resp) => { - const affectedCount = resp.shift(); - if (affectedCount == 0) { - // handle 404 - const err = new Error(`project attachment not found for project id ${projectId} and member id ${attachmentId}`); - err.status = 404; - return Promise.reject(err); - } - - const attachment = resp.shift()[0]; - req.log.debug('updated project attachment', JSON.stringify(attachment, null, 2)); - res.json(util.wrapResponse(req.id, attachment)); - }) - .catch(err => next(err))); + .then(resp => new Promise((accept, reject) => { + const affectedCount = resp.shift(); + if (affectedCount === 0) { + // handle 404 + const err = new Error('project attachment not found for project id ' + + `${projectId} and member id ${attachmentId}`); + err.status = 404; + reject(err); + } else { + const attachment = resp.shift()[0]; + req.log.debug('updated project attachment', JSON.stringify(attachment, null, 2)); + res.json(util.wrapResponse(req.id, attachment)); + accept(); + } + })) + .catch(err => next(err))); }, ]; diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js index abcf17fe..3735eca2 100644 --- a/src/routes/attachments/update.spec.js +++ b/src/routes/attachments/update.spec.js @@ -1,19 +1,17 @@ - +/* eslint-disable no-unused-expressions */ import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; import models from '../../models'; -import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; const should = chai.should(); describe('Project Attachments update', () => { - let project1, - member1, - attachment; + let project1; + let attachment; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -37,23 +35,20 @@ describe('Project Attachments update', () => { isPrimary: true, createdBy: 1, updatedBy: 1, - }).then((pm) => { - member1 = pm; - return models.ProjectAttachment.create({ - projectId: project1.id, - title: 'test.txt', - description: 'blah', - contentType: 'application/unknown', - size: 12312, - category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', - createdBy: 1, - updatedBy: 1, - }).then((a1) => { - attachment = a1; - done(); - }); - }); + }).then(() => models.ProjectAttachment.create({ + projectId: project1.id, + title: 'test.txt', + description: 'blah', + contentType: 'application/unknown', + size: 12312, + category: null, + filePath: 'https://media.topcoder.com/projects/1/test.txt', + createdBy: 1, + updatedBy: 1, + }).then((a1) => { + attachment = a1; + done(); + })); }); }); }); @@ -101,13 +96,14 @@ describe('Project Attachments update', () => { .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.title.should.equal('updated title'); + resJson.description.should.equal('updated description'); + done(); } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.title.should.equal('updated title'); - resJson.description.should.equal('updated description'); - done(); }); }); }); diff --git a/src/routes/index.js b/src/routes/index.js index 8889c61b..10c0558a 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -21,6 +21,7 @@ router.get('/_health', (req, res) => { // All project service endpoints need authentication const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; + router.all('/v4/projects*', jwtAuth()); // Register all the routes @@ -77,11 +78,10 @@ router.use((err, req, res, next) => { // eslint-disable-line no-unused-vars body.result.details = err.details; } } - err.status = err.status || 500; - req.log.error(err); - res - .status(err.status) - .send(body); + const rerr = err; + rerr.status = rerr.status || 500; + req.log.error(rerr); + res.status(rerr.status).send(body); }); // catch 404 and forward to error handler diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 805728f6..64f2dfe7 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -3,11 +3,10 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE } from '../../constants'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import { EVENT } from '../../constants'; +import { PROJECT_MEMBER_ROLE, EVENT } from '../../constants'; /** * API to add a project member. @@ -20,7 +19,8 @@ const addMemberValidations = { param: Joi.object().keys({ userId: Joi.number().required(), isPrimary: Joi.boolean(), - role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.COPILOT).required(), + role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.COPILOT).required(), }).required(), }, }; diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index bd25f5fe..0c714775 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; @@ -12,47 +12,47 @@ import testUtil from '../../tests/util'; const should = chai.should(); describe('Project Members create', () => { - let project1, - project2; + let project1; + let project2; before((done) => { testUtil.clearDb() - .then(() => { + .then(() => { + models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + }).then((p) => { + project1 = p; + // create members + models.ProjectMember.create({ + userId: 40051332, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + }).then(() => models.Project.create({ type: 'generic', - directProjectId: 1, billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'draft', + name: 'test2', + description: 'test project2', + status: 'reviewed', details: {}, createdBy: 1, updatedBy: 1, - }).then((p) => { - project1 = p; - // create members - models.ProjectMember.create({ - userId: 40051332, - projectId: project1.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - }).then(() => - models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test2', - description: 'test project2', - status: 'reviewed', - details: {}, - createdBy: 1, - updatedBy: 1, - }).then((p2) => { - project2 = p2; - done(); - })); - }); + }).then((p2) => { + project2 = p2; + done(); + })); + }); }); after((done) => { @@ -70,46 +70,62 @@ describe('Project Members create', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ param: { userId: 1, role: 'customer' } }) - .expect('Content-Type', /json/) - .expect(403, done); + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + param: { + userId: 1, + role: 'customer', + }, + }) + .expect('Content-Type', /json/) + .expect(403, done); }); it('should return 400 if user is already registered', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send({ param: { userId: 40051332, role: 'customer' } }) - .expect('Content-Type', /json/) - .expect(400) - .end((err, res) => { - if (err) { - return done(err); - } + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: { + userId: 40051332, + role: 'customer', + }, + }) + .expect('Content-Type', /json/) + .expect(400) + .end((err, res) => { + if (err) { + done(err); + } else { res.body.result.status.should.equal(400); done(); - }); + } + }); }); it('should return 201 and register copilot member for project', (done) => { request(server) - .post(`/v4/projects/${project2.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ param: { userId: 1, role: 'copilot' } }) - .expect('Content-Type', /json/) - .expect(201) - .end((err, res) => { - if (err) { - return done(err); - } + .post(`/v4/projects/${project2.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + userId: 1, + role: 'copilot', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.role.should.equal('copilot'); @@ -118,22 +134,28 @@ describe('Project Members create', () => { resJson.userId.should.equal(1); server.services.pubsub.publish.calledWith('project.member.added').should.be.true; done(); - }); + } + }); }); it('should return 201 and register customer member', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ param: { userId: 1, role: 'customer' } }) - .expect('Content-Type', /json/) - .expect(201) - .end((err, res) => { - if (err) { - return done(err); - } + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + userId: 1, + role: 'customer', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.role.should.equal('customer'); @@ -142,7 +164,8 @@ describe('Project Members create', () => { resJson.userId.should.equal(1); server.services.pubsub.publish.calledWith('project.member.added').should.be.true; done(); - }); + } + }); }); /* @@ -195,17 +218,22 @@ describe('Project Members create', () => { // var amqPubSpy = sinon.spy(server.services.pubsub, 'publish') sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ param: { userId: 3, role: 'copilot' } }) - .expect('Content-Type', /json/) - .expect(201) - .end((err, res) => { - if (err) { - return done(err); - } + .post(`/v4/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + userId: 3, + role: 'copilot', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.role.should.equal('copilot'); @@ -215,7 +243,8 @@ describe('Project Members create', () => { postSpy.should.have.been.calledOnce; server.services.pubsub.publish.calledWith('project.member.added').should.be.true; done(); - }); + } + }); }); }); }); diff --git a/src/routes/projectMembers/delete.js b/src/routes/projectMembers/delete.js index 27e13f17..6471b94b 100644 --- a/src/routes/projectMembers/delete.js +++ b/src/routes/projectMembers/delete.js @@ -1,9 +1,8 @@ import _ from 'lodash'; - -import models from '../../models'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; import { EVENT, PROJECT_MEMBER_ROLE } from '../../constants'; /** @@ -29,7 +28,7 @@ module.exports = [ err.status = 404; return Promise.reject(err); } - return member.destroy({ logging: console.log }); + return member.destroy({ logging: console.log }); // eslint-disable-line no-console }) .then(member => member.save()) // if primary co-pilot is removed promote the next co-pilot to primary #43 @@ -67,15 +66,15 @@ module.exports = [ accept(member); } }))).then((member) => { - // only return the response after transaction is committed - // fire event - member = member.get({ plain: true }); + // only return the response after transaction is committed + // fire event + const pmember = member.get({ plain: true }); req.app.services.pubsub.publish( - EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, - member, - { correlationId: req.id }, - ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, member }); + EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, + pmember, + { correlationId: req.id }, + ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, pmember }); res.status(204).json({}); }).catch(err => next(err)); }, diff --git a/src/routes/projectMembers/delete.spec.js b/src/routes/projectMembers/delete.spec.js index d209e63c..e600742d 100644 --- a/src/routes/projectMembers/delete.spec.js +++ b/src/routes/projectMembers/delete.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; @@ -12,48 +12,48 @@ import testUtil from '../../tests/util'; const should = chai.should(); describe('Project members delete', () => { - let project1, - member1, - member2; + let project1; + let member1; + let member2; beforeEach((done) => { testUtil.clearDb() - .then(() => { - models.Project.create({ - type: 'generic', - directProjectId: 1, - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'draft', - details: {}, + .then(() => { + models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + }).then((p) => { + project1 = p; + // create members + return models.ProjectMember.create({ + userId: 40051332, + projectId: project1.id, + role: 'copilot', + isPrimary: true, createdBy: 1, updatedBy: 1, - }).then((p) => { - project1 = p; - // create members + }).then((pm) => { + member1 = pm; return models.ProjectMember.create({ - userId: 40051332, + userId: 40051334, projectId: project1.id, - role: 'copilot', + role: 'manager', isPrimary: true, createdBy: 1, updatedBy: 1, - }).then((pm) => { - member1 = pm; - return models.ProjectMember.create({ - userId: 40051334, - projectId: project1.id, - role: 'manager', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }).then((pm2) => { - member2 = pm2; - done(); - }); + }).then((pm2) => { + member2 = pm2; + done(); }); }); }); + }); }); after((done) => { @@ -71,52 +71,67 @@ describe('Project members delete', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } }) - .expect(403, done); + .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + param: { + userId: 1, + projectId: project1.id, + role: 'customer', + }, + }) + .expect(403, done); }); it('should return 403 if user not found', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/8888888`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } }) - .expect(403, done); + .delete(`/v4/projects/${project1.id}/members/8888888`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + userId: 1, + projectId: project1.id, + role: 'customer', + }, + }) + .expect(403, done); }); it('should return 204 if copilot user has access to the project', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .expect(204) - .end((err) => { - if (err) { - return done(err); - } + .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { const removedMember = { projectId: project1.id, userId: 40051332, role: 'copilot', isPrimary: true, }; - server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true; + server.services.pubsub.publish.calledWith('project.member.removed', + sinon.match(removedMember)).should.be.true; done(); - // models.ProjectMember - // .count({where: { projectId: project1.id, deletedAt: { $eq: null } }}) - // .then(count=>{ - // console.log(JSON.stringify(count, null, 2)) - // count.length.should.equal(1) - // done() - // }) - // .catch(err=>done(err)) - }); + } + + // models.ProjectMember + // .count({where: { projectId: project1.id, deletedAt: { $eq: null } }}) + // .then(count=>{ + // console.log(JSON.stringify(count, null, 2)) + // count.length.should.equal(1) + // done() + // }) + // .catch(err=>done(err)) + }); }); it('should return 204 if copilot is removed (promote the next copilot to primary)', (done) => { @@ -156,26 +171,37 @@ describe('Project members delete', () => { .expect(204) .end((err) => { if (err) { - return done(err); + done(err); + } else { + const removedMember = { + projectId: project1.id, + userId: 40051332, + role: 'copilot', + isPrimary: true, + }; + server.services.pubsub.publish.calledWith('project.member.removed', + sinon.match(removedMember)).should.be.true; + // validate the primary copilot + models.ProjectMember.findAll({ + paranoid: true, + where: { + projectId: project1.id, + role: 'copilot', + isPrimary: true, + }, + }) + .then((members) => { + should.exist(members); + members.length.should.equal(1); + const plain = members[0].get({ + plain: true, + }); + plain.role.should.equal('copilot'); + plain.isPrimary.should.equal(true); + plain.userId.should.equal(40051331); + done(); + }); } - const removedMember = { - projectId: project1.id, - userId: 40051332, - role: 'copilot', - isPrimary: true, - }; - server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true; - // validate the primary copilot - models.ProjectMember.findAll({ paranoid: true, where: { projectId: project1.id, role: 'copilot', isPrimary: true } }) - .then((members) => { - should.exist(members); - members.length.should.equal(1); - const plain = members[0].get({ plain: true }); - plain.role.should.equal('copilot'); - plain.isPrimary.should.equal(true); - plain.userId.should.equal(40051331); - done(); - }); }); }); }); @@ -190,7 +216,7 @@ describe('Project members delete', () => { result: { success: true, status: 200, - content: { }, + content: {}, }, }, }), @@ -198,25 +224,27 @@ describe('Project members delete', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .expect(204) - .end((err) => { - if (err) { - return done(err); - } + .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { const removedMember = { projectId: project1.id, userId: 40051334, role: 'manager', isPrimary: true, }; - server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true; + server.services.pubsub.publish.calledWith('project.member.removed', + sinon.match(removedMember)).should.be.true; postSpy.should.have.been.calledOnce; done(); - }); + } + }); }); it('should return 204 if manager is removed from the project (without direct project id)', (done) => { @@ -229,14 +257,20 @@ describe('Project members delete', () => { result: { success: true, status: 200, - content: { }, + content: {}, }, }, }), }); const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); - models.Project.update({ directProjectId: null }, { where: { id: project1.id } }) + models.Project.update({ + directProjectId: null, + }, { + where: { + id: project1.id, + }, + }) .then(() => { request(server) .delete(`/v4/projects/${project1.id}/members/${member2.id}`) @@ -246,28 +280,30 @@ describe('Project members delete', () => { .expect(204) .end((err) => { if (err) { - return done(err); + done(err); + } else { + const removedMember = { + projectId: project1.id, + userId: 40051334, + role: 'manager', + isPrimary: true, + }; + server.services.pubsub.publish.calledWith('project.member.removed', + sinon.match(removedMember)).should.be.true; + postSpy.should.not.have.been.calledOnce; + done(); } - const removedMember = { - projectId: project1.id, - userId: 40051334, - role: 'manager', - isPrimary: true, - }; - server.services.pubsub.publish.calledWith('project.member.removed', sinon.match(removedMember)).should.be.true; - postSpy.should.not.have.been.calledOnce; - done(); }); }); }); it('should return 403 if copilot user is trying to remove a manager', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .expect(403, done); + .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); }); }); }); diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index ac89f67c..47626083 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -2,12 +2,10 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; - +import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; import { EVENT, PROJECT_MEMBER_ROLE } from '../../constants'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import directProject from '../../services/directProject'; /** * API to update a project member. @@ -18,7 +16,8 @@ const updateProjectMemberValdiations = { body: { param: Joi.object().keys({ isPrimary: Joi.boolean(), - role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.COPILOT).required(), + role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.COPILOT).required(), }), }, }; @@ -31,21 +30,22 @@ module.exports = [ * Update a projectMember if the user has access */ (req, res, next) => { - let projectMember, - updatedProps = req.body.param; + let projectMember; + let updatedProps = req.body.param; const projectId = _.parseInt(req.params.projectId); const memberRecordId = _.parseInt(req.params.id); updatedProps = _.pick(updatedProps, ['isPrimary', 'role']); let previousValue; - let newValue; + // let newValue; models.sequelize.transaction(() => models.ProjectMember.findOne({ where: { id: memberRecordId, projectId }, }) .then((_member) => { if (!_member) { // handle 404 - const err = new Error(`project member not found for project id ${projectId} and member id ${memberRecordId}`); + const err = new Error(`project member not found for project id ${projectId} ` + + `and member id ${memberRecordId}`); err.status = 404; return Promise.reject(err); } @@ -53,11 +53,12 @@ module.exports = [ projectMember = _member; previousValue = _.clone(projectMember.get({ plain: true })); _.assign(projectMember, updatedProps); - newValue = projectMember.get({ plain: true }); + // newValue = projectMember.get({ plain: true }); // no updates if no change if (updatedProps.role === previousValue.role && - (_.isUndefined(updatedProps.isPrimary) || updatedProps.isPrimary === previousValue.isPrimary)) { + (_.isUndefined(updatedProps.isPrimary) || + updatedProps.isPrimary === previousValue.isPrimary)) { return Promise.resolve(); } @@ -67,7 +68,8 @@ module.exports = [ if (updatedProps.isPrimary) { // if set as primary, other users with same role should no longer be primary - operations.push(models.ProjectMember.update({ isPrimary: false, updatedBy: req.authUser.userId }, + operations.push(models.ProjectMember.update({ isPrimary: false, + updatedBy: req.authUser.userId }, { where: { projectId, diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js index 799a2d5d..e45026ad 100644 --- a/src/routes/projectMembers/update.spec.js +++ b/src/routes/projectMembers/update.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; import chai from 'chai'; import request from 'supertest'; @@ -11,65 +11,71 @@ import testUtil from '../../tests/util'; const should = chai.should(); describe('Project members update', () => { - let project1, - member1, - member2, - member3; + let project1; + let member1; + let member2; + let member3; beforeEach((done) => { testUtil.clearDb() - .then(() => { - models.Project.create({ - type: 'generic', - directProjectId: 1, - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'draft', - details: {}, + .then(() => { + models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + }).then((p) => { + project1 = p; + // create members + models.ProjectMember.create({ + userId: 40051334, + projectId: project1.id, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }).then((pm) => { + member1 = pm.get({ + plain: true, + }); + models.ProjectMember.create({ + userId: 40051332, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }).then((pm2) => { + member2 = pm2.get({ + plain: true, + }); + models.ProjectMember.create({ + userId: 40051330, + projectId: project1.id, + role: 'copilot', + isPrimary: false, createdBy: 1, updatedBy: 1, - }).then((p) => { - project1 = p; - // create members - models.ProjectMember.create({ - userId: 40051334, - projectId: project1.id, - role: 'manager', - isPrimary: false, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((pm) => { - member1 = pm.get({ plain: true }); - models.ProjectMember.create({ - userId: 40051332, - projectId: project1.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((pm2) => { - member2 = pm2.get({ plain: true }); - models.ProjectMember.create({ - userId: 40051330, - projectId: project1.id, - role: 'copilot', - isPrimary: false, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((pm3) => { - member3 = pm3.get({ plain: true }); - done(); - }); - }); + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }).then((pm3) => { + member3 = pm3.get({ + plain: true, }); + done(); }); }); + }); + }); + }); }); after((done) => { @@ -94,125 +100,136 @@ describe('Project members update', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(body) - .expect(403, done); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); }); it('should return 422 if no role', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send({ param: {} }) - .expect(422, done); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: {}, + }) + .expect(422, done); }); it('should return 422 if role is invalid', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - param: { - role: 'wrong', - }, - }) - .expect(422, done); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + param: { + role: 'wrong', + }, + }) + .expect(422, done); }); it('should return 422 if isPrimary is invalid', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - param: { - isPrimary: 'wrong', - }, - }) - .expect(422, done); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + param: { + isPrimary: 'wrong', + }, + }) + .expect(422, done); }); it('should return 404 if not exist id', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/999999`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(404) - .end((err, res) => { - if (err) { - return done(err); - } - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(404); - result.content.message.should.equal(`project member not found for project id ${project1.id} and member id 999999`); - done(); - }); + .patch(`/v4/projects/${project1.id}/members/999999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(404) + .end((err, res) => { + if (err) { + done(err); + } else { + const result = res.body.result; + result.success.should.be.false; + result.status.should.equal(404); + result.content.message.should.equal('project member not found for project id' + + ` ${project1.id} and member id 999999`); + done(); + } + }); }); it('should return 200 if valid user and data(no isPrimary and no updates)', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - role: 'customer', - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal('customer'); - resJson.isPrimary.should.be.true; - resJson.updatedBy.should.equal(40051332); - server.services.pubsub.publish.calledWith('project.member.updated').should.be.true; - done(); - }); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + role: 'customer', + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('customer'); + resJson.isPrimary.should.be.true; + resJson.updatedBy.should.equal(40051332); + server.services.pubsub.publish.calledWith('project.member.updated').should.be.true; + done(); + } + }); }); it('should return 200 if valid user(not copilot any more) for project without direct project id', (done) => { - models.Project.update({ directProjectId: null }, { where: { id: project1.id } }) - .then(() => { - request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal(body.param.role); - resJson.isPrimary.should.be.false; - resJson.updatedBy.should.equal(40051332); - server.services.pubsub.publish.calledWith('project.member.updated').should.be.true; - done(); - }); - }, - ); + models.Project.update({ + directProjectId: null, + }, { + where: { + id: project1.id, + }, + }) + .then(() => { + request(server) + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal(body.param.role); + resJson.isPrimary.should.be.false; + resJson.updatedBy.should.equal(40051332); + server.services.pubsub.publish.calledWith('project.member.updated').should.be.true; + done(); + } + }); + }); }); it('should return 200 if valid user(not copilot any more) and data', (done) => { @@ -233,26 +250,27 @@ describe('Project members update', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal(body.param.role); - resJson.isPrimary.should.be.false; - resJson.updatedBy.should.equal(40051332); - deleteSpy.should.have.been.calledOnce; - server.services.pubsub.publish.calledWith('project.member.removed').should.be.true; - done(); - }); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal(body.param.role); + resJson.isPrimary.should.be.false; + resJson.updatedBy.should.equal(40051332); + deleteSpy.should.have.been.calledOnce; + server.services.pubsub.publish.calledWith('project.member.removed').should.be.true; + done(); + } + }); }); it.skip('should return 500 if error to remove copilot from direct project', (done) => { @@ -262,24 +280,25 @@ describe('Project members update', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(500) - .end((err, res) => { - if (err) { - return done(err); - } - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(500); - result.content.message.should.equal('error message'); - deleteSpy.should.have.been.calledOnce; - done(); - }); + .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(500) + .end((err, res) => { + if (err) { + done(err); + } else { + const result = res.body.result; + result.success.should.be.false; + result.status.should.equal(500); + result.content.message.should.equal('error message'); + deleteSpy.should.have.been.calledOnce; + done(); + } + }); }); it('should return 200 if valid user(become manager) and data', (done) => { @@ -292,7 +311,7 @@ describe('Project members update', () => { result: { success: true, status: 200, - content: { }, + content: {}, }, }, }), @@ -300,79 +319,87 @@ describe('Project members update', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member3.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send({ - param: { - role: 'manager', - isPrimary: false, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal('manager'); - resJson.isPrimary.should.be.false; - resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); - resJson.updatedBy.should.equal(40051334); - postSpy.should.have.been.calledOnce; - done(); - }); + .patch(`/v4/projects/${project1.id}/members/${member3.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + role: 'manager', + isPrimary: false, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('manager'); + resJson.isPrimary.should.be.false; + resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); + resJson.updatedBy.should.equal(40051334); + postSpy.should.have.been.calledOnce; + done(); + } + }); }); it('should return 200 if valid user(become manager) and data (without directProjectId)', (done) => { - models.Project.update({ directProjectId: null }, { where: { id: project1.id } }) - .then(() => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - post: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { }, - }, - }, - }), - }); - const postSpy = sinon.spy(mockHttpClient, 'post'); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); - request(server) - .patch(`/v4/projects/${project1.id}/members/${member3.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send({ - param: { - role: 'manager', - isPrimary: false, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal('manager'); - resJson.isPrimary.should.be.false; - resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); - resJson.updatedBy.should.equal(40051334); - postSpy.should.not.have.been.calledOnce; - done(); - }); - }); + models.Project.update({ + directProjectId: null, + }, { + where: { + id: project1.id, + }, + }) + .then(() => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: {}, + }, + }, + }), + }); + const postSpy = sinon.spy(mockHttpClient, 'post'); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .patch(`/v4/projects/${project1.id}/members/${member3.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + role: 'manager', + isPrimary: false, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('manager'); + resJson.isPrimary.should.be.false; + resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); + resJson.updatedBy.should.equal(40051334); + postSpy.should.not.have.been.calledOnce; + done(); + } + }); + }); }); it('should return 200 if valid user(become copilot) and data', (done) => { @@ -395,39 +422,32 @@ describe('Project members update', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member1.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - role: 'copilot', - isPrimary: true, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.role.should.equal('copilot'); - resJson.isPrimary.should.be.true; - resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); - resJson.updatedBy.should.equal(40051332); - postSpy.should.have.been.calledOnce; - done(); - // models.ProjectMember.findById(member2.id) - // .then(pm=> { - // pm.isPrimary.should.be.false - // pm.updatedAt.should.not.equal("2016-06-30 00:33:07+00") - // pm.updatedBy.should.equal(40051332) - // done() - // }) - // .catch(err => done(err)) - }); + .patch(`/v4/projects/${project1.id}/members/${member1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + role: 'copilot', + isPrimary: true, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.role.should.equal('copilot'); + resJson.isPrimary.should.be.true; + resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); + resJson.updatedBy.should.equal(40051332); + postSpy.should.have.been.calledOnce; + done(); + } + }); }); }); }); diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 76d61b7b..df629acd 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -34,7 +34,8 @@ const createProjectValdiations = { title: Joi.string(), address: Joi.string(), })).optional().allow(null), - estimatedPrice: Joi.number().precision(2).positive().optional().allow(null), + estimatedPrice: Joi.number().precision(2).positive().optional() + .allow(null), terms: Joi.array().items(Joi.number().positive()).optional(), external: Joi.object().keys({ id: Joi.string(), diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 477e9ecb..c41fa612 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; @@ -12,9 +12,7 @@ import RabbitMQService from '../../services/rabbitmq'; const should = chai.should(); sinon.stub(RabbitMQService.prototype, 'init', () => {}); -sinon.stub(RabbitMQService.prototype, 'publish', () => { - console.log('publish called'); -}); +sinon.stub(RabbitMQService.prototype, 'publish', () => {}); describe('Project create', () => { before((done) => { @@ -83,13 +81,14 @@ describe('Project create', () => { .expect(201) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const result = res.body.result; + result.success.should.be.truthy; + result.status.should.equal(201); + server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; + done(); } - const result = res.body.result; - result.success.should.be.truthy; - result.status.should.equal(201); - server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; - done(); }); }); @@ -121,25 +120,26 @@ describe('Project create', () => { .expect(201) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + should.exist(resJson.billingAccountId); + should.exist(resJson.name); + resJson.directProjectId.should.be.eql(128); + resJson.status.should.be.eql('draft'); + resJson.type.should.be.eql(body.param.type); + resJson.members.should.have.lengthOf(1); + resJson.members[0].role.should.be.eql('customer'); + resJson.members[0].userId.should.be.eql(40051331); + resJson.members[0].projectId.should.be.eql(resJson.id); + resJson.members[0].isPrimary.should.be.truthy; + resJson.bookmarks.should.have.lengthOf(1); + resJson.bookmarks[0].title.should.be.eql('title1'); + resJson.bookmarks[0].address.should.be.eql('address1'); + server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; + done(); } - const resJson = res.body.result.content; - should.exist(resJson); - should.exist(resJson.billingAccountId); - should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); - resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); - resJson.members.should.have.lengthOf(1); - resJson.members[0].role.should.be.eql('customer'); - resJson.members[0].userId.should.be.eql(40051331); - resJson.members[0].projectId.should.be.eql(resJson.id); - resJson.members[0].isPrimary.should.be.truthy; - resJson.bookmarks.should.have.lengthOf(1); - resJson.bookmarks[0].title.should.be.eql('title1'); - resJson.bookmarks[0].address.should.be.eql('address1'); - server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; - done(); }); }); }); diff --git a/src/routes/projects/delete.js b/src/routes/projects/delete.js index d5f03aaa..a2ea2b19 100644 --- a/src/routes/projects/delete.js +++ b/src/routes/projects/delete.js @@ -1,11 +1,8 @@ - -// import validate from 'express-validation' import _ from 'lodash'; -import { EVENT } from '../../constants.js'; -import models from '../../models'; -import fileService from '../../services/fileService'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT } from '../../constants'; +import models from '../../models'; /** * API to delete a project member. diff --git a/src/routes/projects/delete.spec.js b/src/routes/projects/delete.spec.js index 99587089..bf9ffd81 100644 --- a/src/routes/projects/delete.spec.js +++ b/src/routes/projects/delete.spec.js @@ -1,21 +1,13 @@ - -import _ from 'lodash'; -import chai from 'chai'; -import sinon from 'sinon'; +/* eslint-disable no-unused-expressions */ import request from 'supertest'; import models from '../../models'; -import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; describe('Project delete test', () => { - let project1, - owner, - teamMember, - manager, - copilot; + let project1; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -71,11 +63,7 @@ describe('Project delete test', () => { }), ]; Promise.all(promises) - .then((res) => { - owner = res[0]; - manager = res[2]; - copilot = res[3]; - teamMember = res[4]; + .then(() => { done(); }); }); @@ -103,12 +91,13 @@ describe('Project delete test', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .expect(204) - .end((err, resp) => { + .end((err) => { if (err) { - return done(err); + done(err); + } else { + server.services.pubsub.publish.calledWith('project.deleted').should.be.true; + done(); } - server.services.pubsub.publish.calledWith('project.deleted').should.be.true; - done(); }); }); }); diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 8150715c..077f06e4 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -2,11 +2,9 @@ /* globals Promise */ import _ from 'lodash'; - +import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; - /** /** diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index b8004a0c..e1251041 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; @@ -11,8 +11,8 @@ import testUtil from '../../tests/util'; const should = chai.should(); describe('GET Project', () => { - let project1, - project2; + let project1; + let project2; before((done) => { testUtil.clearDb() .then(() => { @@ -103,16 +103,17 @@ describe('GET Project', () => { .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + should.not.exist(resJson.deletedAt); + should.not.exist(resJson.billingAccountId); + should.exist(resJson.name); + resJson.status.should.be.eql('draft'); + resJson.members.should.have.lengthOf(2); + done(); } - const resJson = res.body.result.content; - should.exist(resJson); - should.not.exist(resJson.deletedAt); - should.not.exist(resJson.billingAccountId); - should.exist(resJson.name); - resJson.status.should.be.eql('draft'); - resJson.members.should.have.lengthOf(2); - done(); }); }); @@ -126,11 +127,12 @@ describe('GET Project', () => { .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + done(); } - const resJson = res.body.result.content; - should.exist(resJson); - done(); }); }); @@ -171,16 +173,17 @@ describe('GET Project', () => { .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + spy.should.have.been.calledOnce; + resJson.attachments.should.have.lengthOf(1); + resJson.attachments[0].filePath.should.equal(attachment.filePath); + resJson.attachments[0].downloadUrl.should.exist; + stub.restore(); + done(); } - const resJson = res.body.result.content; - should.exist(resJson); - spy.should.have.been.calledOnce; - resJson.attachments.should.have.lengthOf(1); - resJson.attachments[0].filePath.should.equal(attachment.filePath); - resJson.attachments[0].downloadUrl.should.exist; - stub.restore(); - done(); }); }); }); diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index eeadb656..9d083764 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -27,10 +27,10 @@ const PROJECT_ATTACHMENT_ATTRIBUTES = _.without( 'deletedAt', ); -const _retrieveProjects = (req, criteria, sort, fields) => { +const retrieveProjects = (req, criteria, sort, ffields) => { // order by const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']]; - fields = fields ? fields.split(',') : []; + let fields = ffields ? ffields.split(',') : []; // parse the fields string to determine what fields are to be returned fields = util.parseFields(fields, { projects: PROJECT_ATTRIBUTES, @@ -75,7 +75,8 @@ const _retrieveProjects = (req, criteria, sort, fields) => { .then((values) => { const allMembers = retrieveMembers ? values.shift() : []; const allAttachments = retrieveAttachments ? values.shift() : []; - _.forEach(rows, (p) => { + _.forEach(rows, (fp) => { + const p = fp; // if values length is 1 it could be either attachments or members if (retrieveMembers) { p.members = _.filter(allMembers, m => m.projectId === p.id); @@ -98,7 +99,7 @@ module.exports = [ // handle filters let filters = util.parseQueryFilter(req.query.filter); let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; - if (sort && sort.indexOf(' ') == -1) { + if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } const sortableProps = [ @@ -128,7 +129,7 @@ module.exports = [ && (util.hasRole(req, USER_ROLE.TOPCODER_ADMIN) || util.hasRole(req, USER_ROLE.MANAGER))) { // admins & topcoder managers can see all projects - return _retrieveProjects(req, criteria, sort, req.query.fields) + return retrieveProjects(req, criteria, sort, req.query.fields) .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) .catch(err => next(err)); } @@ -150,7 +151,7 @@ module.exports = [ } else { criteria.filters.id = { $in: accessibleProjectIds }; } - return _retrieveProjects(req, criteria, sort, req.query.fields); + return retrieveProjects(req, criteria, sort, req.query.fields); }) .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) .catch(err => next(err)); diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index a69a8c2c..d4787d56 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -1,5 +1,4 @@ - - +/* eslint-disable no-unused-expressions */ import chai from 'chai'; import request from 'supertest'; @@ -11,118 +10,121 @@ const should = chai.should(); /** * Add full text index for projects. + * @return {Promise} returns the promise */ function addFullTextIndex() { if (models.sequelize.options.dialect !== 'postgres') { - console.log('Not creating search index, must be using POSTGRES to do this'); - return; + return null; } return models.sequelize - .query('ALTER TABLE projects ADD COLUMN "projectFullText" text;') + .query('ALTER TABLE projects ADD COLUMN "projectFullText" text;') + .then(() => models.sequelize + .query('UPDATE projects SET "projectFullText" = lower(' + + 'name || \' \' || coalesce(description, \'\') || \' \' || coalesce(details#>>\'{utm, code}\', \'\'));')) + .then(() => models.sequelize + .query('CREATE EXTENSION IF NOT EXISTS pg_trgm;')).then(() => models.sequelize + .query('CREATE INDEX project_text_search_idx ON projects USING GIN("projectFullText" gin_trgm_ops);')) + .then(() => models.sequelize + .query('CREATE OR REPLACE FUNCTION project_text_update_trigger() RETURNS trigger AS $$ ' + + 'begin ' + + 'new."projectFullText" := ' + + 'lower(new.name || \' \' || coalesce(new.description, \'\') || \' \' || ' + + ' coalesce(new.details#>>\'{utm, code}\', \'\')); ' + + 'return new; ' + + 'end ' + + '$$ LANGUAGE plpgsql;')) + .then(() => models.sequelize + .query('DROP TRIGGER IF EXISTS project_text_update ON projects;')) .then(() => models.sequelize - .query('UPDATE projects SET "projectFullText" = lower(' + - 'name || \' \' || coalesce(description, \'\') || \' \' || coalesce(details#>>\'{utm, code}\', \'\'));')).then(() => models.sequelize - .query('CREATE EXTENSION IF NOT EXISTS pg_trgm;')).then(() => models.sequelize - .query('CREATE INDEX project_text_search_idx ON projects USING GIN("projectFullText" gin_trgm_ops);')).then(() => models.sequelize - .query('CREATE OR REPLACE FUNCTION project_text_update_trigger() RETURNS trigger AS $$ ' + - 'begin ' + - 'new."projectFullText" := ' + - 'lower(new.name || \' \' || coalesce(new.description, \'\') || \' \' || coalesce(new.details#>>\'{utm, code}\', \'\')); ' + - 'return new; ' + - 'end ' + - '$$ LANGUAGE plpgsql;')).then(() => models.sequelize - .query('DROP TRIGGER IF EXISTS project_text_update ON projects;')).then(() => models.sequelize - .query('CREATE TRIGGER project_text_update BEFORE INSERT OR UPDATE ON projects' + - ' FOR EACH ROW EXECUTE PROCEDURE project_text_update_trigger();')).catch((err) => { - console.log('Failed: ', err); - }); + .query('CREATE TRIGGER project_text_update BEFORE INSERT OR UPDATE ON projects' + + ' FOR EACH ROW EXECUTE PROCEDURE project_text_update_trigger();')); } describe('LIST Project', () => { - let project1, - project2; + let project1; + let project2; before((done) => { testUtil.clearDb() - .then(() => addFullTextIndex()) - .then(() => { - const p1 = models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'active', - details: { - utm: { - code: 'code1', - }, + .then(() => addFullTextIndex()) + .then(() => { + const p1 = models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'active', + details: { + utm: { + code: 'code1', }, + }, + createdBy: 1, + updatedBy: 1, + }).then((p) => { + project1 = p; + // create members + const pm1 = models.ProjectMember.create({ + userId: 40051331, + projectId: project1.id, + role: 'customer', + isPrimary: true, createdBy: 1, updatedBy: 1, - }).then((p) => { - project1 = p; - // create members - const pm1 = models.ProjectMember.create({ - userId: 40051331, - projectId: project1.id, - role: 'customer', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - const pm2 = models.ProjectMember.create({ - userId: 40051332, - projectId: project1.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - const pa1 = models.ProjectAttachment.create({ - title: 'Spec', - projectId: project1.id, - description: 'specification', - filePath: 'projects/1/spec.pdf', - contentType: 'application/pdf', - createdBy: 1, - updatedBy: 1, - }); - return Promise.all([pm1, pm2, pa1]); }); - - const p2 = models.Project.create({ - type: 'visual_design', - billingAccountId: 1, - name: 'test2', - description: 'test project2', - status: 'draft', - details: {}, + const pm2 = models.ProjectMember.create({ + userId: 40051332, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + const pa1 = models.ProjectAttachment.create({ + title: 'Spec', + projectId: project1.id, + description: 'specification', + filePath: 'projects/1/spec.pdf', + contentType: 'application/pdf', createdBy: 1, updatedBy: 1, - }).then((p) => { - project2 = p; - return models.ProjectMember.create({ - userId: 40051332, - projectId: project2.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); }); - const p3 = models.Project.create({ - type: 'visual_design', - billingAccountId: 1, - name: 'test2', - description: 'test project3', - status: 'reviewed', - details: {}, + return Promise.all([pm1, pm2, pa1]); + }); + + const p2 = models.Project.create({ + type: 'visual_design', + billingAccountId: 1, + name: 'test2', + description: 'test project2', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + }).then((p) => { + project2 = p; + return models.ProjectMember.create({ + userId: 40051332, + projectId: project2.id, + role: 'copilot', + isPrimary: true, createdBy: 1, updatedBy: 1, }); - return Promise.all([p1, p2, p3]) - .then(() => done()); }); + const p3 = models.Project.create({ + type: 'visual_design', + billingAccountId: 1, + name: 'test2', + description: 'test project3', + status: 'reviewed', + details: {}, + createdBy: 1, + updatedBy: 1, + }); + return Promise.all([p1, p2, p3]) + .then(() => done()); + }); }); after((done) => { @@ -132,48 +134,51 @@ describe('LIST Project', () => { describe('GET All /projects/', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/') - .expect(403, done); + .get('/v4/projects/') + .expect(403, done); }); it('should return 200 and no projects if user does not have access', (done) => { request(server) - .get(`/v4/projects/?filter=id%3Din%28${project2.id}%29`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get(`/v4/projects/?filter=id%3Din%28${project2.id}%29`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { res.body.result.content.should.have.lengthOf(0); done(); - }); + } + }); }); it('should return the project when registerd member attempts to access the project', (done) => { request(server) - .get('/v4/projects/?filter=status%3Ddraft') - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?filter=status%3Ddraft') + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; res.body.result.metadata.totalCount.should.equal(1); should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].id.should.equal(project2.id); done(); - }); + } + }); }); - it('should return the project when project that is in reviewed state AND does not yet have a co-pilot assigned', (done) => { + it('should return the project when project that is in reviewed state AND does not yet' + + 'have a co-pilot assigned', (done) => { request(server) .get('/v4/projects') .set({ @@ -183,111 +188,117 @@ describe('LIST Project', () => { .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + res.body.result.metadata.totalCount.should.equal(3); + should.exist(resJson); + resJson.should.have.lengthOf(3); + done(); } - const resJson = res.body.result.content; - res.body.result.metadata.totalCount.should.equal(3); - should.exist(resJson); - resJson.should.have.lengthOf(3); - done(); }); }); it('should return the project for administrator ', (done) => { request(server) - .get('/v4/projects/?fields=id%2Cmembers.id') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?fields=id%2Cmembers.id') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.should.have.lengthOf(3); done(); - }); + } + }); }); it('should return all projects that match when filtering by name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dtest') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?filter=keyword%3Dtest') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.should.have.lengthOf(3); done(); - }); + } + }); }); it('should return the project when filtering by keyword, which matches the name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3D1') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?filter=keyword%3D1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); done(); - }); + } + }); }); it('should return the project when filtering by keyword, which matches the description', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dproject') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?filter=keyword%3Dproject') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.should.have.lengthOf(3); done(); - }); + } + }); }); it('should return the project when filtering by keyword, which matches the details', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dcode') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .get('/v4/projects/?filter=keyword%3Dcode') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); done(); - }); + } + }); }); }); }); diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 4c0aa7e8..448357f4 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -33,6 +33,7 @@ const mergeCustomizer = (objValue, srcValue) => { if (_.isArray(objValue)) { return srcValue; } + return undefined; }; const updateProjectValdiations = { @@ -112,7 +113,8 @@ module.exports = [ let updatedProps = req.body.param; const projectId = _.parseInt(req.params.projectId); // prune any fields that cannot be updated directly - updatedProps = _.omit(updatedProps, ['createdBy', 'createdAt', 'updatedBy', 'updatedAt', 'id', 'directProjectId']); + updatedProps = _.omit(updatedProps, ['createdBy', 'createdAt', 'updatedBy', 'updatedAt', + 'id', 'directProjectId']); let previousValue; models.sequelize.transaction(() => models.Project.findOne({ @@ -153,7 +155,8 @@ module.exports = [ _.isUndefined(_.find(members, m => m.userId === req.authUser.userId && matchRole(m.role))) ) { - const err = new Error('Only assigned topcoder-managers or topcoder admins should be allowed to launch a project'); + const err = new Error('Only assigned topcoder-managers or topcoder admins should be allowed ' + + 'to launch a project'); err.status = 403; return Promise.reject(err); } diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index d27031af..03e27cc1 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -1,4 +1,4 @@ - +/* eslint-disable no-unused-expressions */ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; @@ -8,14 +8,16 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import util from '../../util'; -import { PROJECT_STATUS } from '../../constants'; +import { + PROJECT_STATUS, +} from '../../constants'; const should = chai.should(); describe('Project', () => { - let project1, - project2, - project3; + let project1; + let project2; + let project3; beforeEach((done) => { testUtil.clearDb(done); }); @@ -69,118 +71,129 @@ describe('Project', () => { createdAt: '2016-06-30 00:33:07+00', updatedAt: '2016-06-30 00:33:07+00', }]) - .then(() => models.Project.findAll()) - .then((projects) => { - project1 = projects[0]; - project2 = projects[1]; - project3 = projects[2]; - return models.ProjectMember.bulkCreate([{ - projectId: project1.id, - role: 'copilot', - userId: 40051332, - createdBy: 1, - updatedBy: 1, - }, { - projectId: project1.id, - role: 'manager', - userId: 40051334, - createdBy: 1, - updatedBy: 1, - }, { - projectId: project2.id, - role: 'copilot', - userId: 40051332, - createdBy: 1, - updatedBy: 1, - }]); - }).then(() => done()); + .then(() => models.Project.findAll()) + .then((projects) => { + project1 = projects[0]; + project2 = projects[1]; + project3 = projects[2]; + return models.ProjectMember.bulkCreate([{ + projectId: project1.id, + role: 'copilot', + userId: 40051332, + createdBy: 1, + updatedBy: 1, + }, { + projectId: project1.id, + role: 'manager', + userId: 40051334, + createdBy: 1, + updatedBy: 1, + }, { + projectId: project2.id, + role: 'copilot', + userId: 40051332, + createdBy: 1, + updatedBy: 1, + }]); + }).then(() => done()); }); it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .send(body) - .expect(403, done); + .patch(`/v4/projects/${project1.id}`) + .send(body) + .expect(403, done); }); it('should return 400 if update completed project', (done) => { request(server) - .patch(`/v4/projects/${project2.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(body) - .expect('Content-Type', /json/) - .expect(400) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project2.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(400) + .end((err, res) => { + if (err) { + done(err); + } else { const result = res.body.result; result.success.should.be.false; result.status.should.equal(400); result.content.message.should.equal('Unable to update project'); done(); - }); + } + }); }); it('should return 403 if invalid user will launch a project', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - status: 'active', - }, - }) - .expect('Content-Type', /json/) - .expect(403) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + status: 'active', + }, + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { const result = res.body.result; result.success.should.be.false; result.status.should.equal(403); result.content.message.should.equal('Only assigned topcoder-managers or topcoder admins' + ' should be allowed to launch a project'); done(); - }); + } + }); }); it('should return 200 if topcoder manager user will launch a project', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.manager}` }) - .send({ - param: { - status: 'active', - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + status: 'active', + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const result = res.body.result; result.success.should.be.true; result.status.should.equal(200); result.content.status.should.equal('active'); server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); - }); + } + }); }); it('should return 200 if valid user and data', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(body) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.name.should.equal('updatedProject name'); @@ -188,23 +201,29 @@ describe('Project', () => { resJson.updatedBy.should.equal(40051332); server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); - }); + } + }); }); it('should return 200 and project history should be updated (status is not set)', (done) => { - const sbody = _.cloneDeep(body); - // set project status to be updated - sbody.param.status = PROJECT_STATUS.IN_REVIEW; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.IN_REVIEW, + }, + }; request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(sbody) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(mbody) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.name.should.equal('updatedProject name'); @@ -214,32 +233,45 @@ describe('Project', () => { // validate that project history is updated models.ProjectHistory.findAll({ limit: 1, - where: { projectId: project1.id }, - order: [['createdAt', 'DESC']], + where: { + projectId: project1.id, + }, + order: [ + ['createdAt', 'DESC'], + ], }).then((histories) => { should.exist(histories); histories.length.should.equal(1); - const history = histories[0].get({ plain: true }); + const history = histories[0].get({ + plain: true, + }); history.status.should.equal(PROJECT_STATUS.IN_REVIEW); history.projectId.should.equal(project1.id); done(); }); - }); + } + }); }); it('should return 200 and project history should not be updated (status is not updated)', (done) => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.DRAFT; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.DRAFT, + }, + }; request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(sbody) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(mbody) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.name.should.equal('updatedProject name'); @@ -248,49 +280,65 @@ describe('Project', () => { server.services.pubsub.publish.calledWith('project.updated').should.be.true; // validate that project history is not updated models.ProjectHistory.findAll({ - where: { projectId: project1.id }, + where: { + projectId: project1.id, + }, }).then((histories) => { should.exist(histories); histories.length.should.equal(0); done(); }); - }); + } + }); }); it('should return 422 as cancel reason is mandatory if project status is cancelled', (done) => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.CANCELLED; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.CANCELLED, + }, + }; request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(sbody) - .expect('Content-Type', /json/) - .expect(422) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(mbody) + .expect('Content-Type', /json/) + .expect(422) + .end((err, res) => { + if (err) { + done(err); + } else { const result = res.body.result; result.success.should.be.false; result.status.should.equal(422); done(); - }); + } + }); }); it('should return 200 and project history should be updated for cancelled project', (done) => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.CANCELLED; - sbody.param.cancelReason = 'price/cost'; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.CANCELLED, + cancelReason: 'price/cost', + }, + }; request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(sbody) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(mbody) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.name.should.equal('updatedProject name'); @@ -299,125 +347,179 @@ describe('Project', () => { server.services.pubsub.publish.calledWith('project.updated').should.be.true; // validate that project history is updated models.ProjectHistory.findAll({ - where: { projectId: project1.id }, + where: { + projectId: project1.id, + }, }).then((histories) => { should.exist(histories); histories.length.should.equal(1); - const history = histories[0].get({ plain: true }); + const history = histories[0].get({ + plain: true, + }); history.status.should.equal(PROJECT_STATUS.CANCELLED); history.projectId.should.equal(project1.id); history.cancelReason.should.equal('price/cost'); done(); }); - }); + } + }); }); it('should return 200, manager is allowed to transition project out of cancel status', (done) => { - models.Project.update({ status: PROJECT_STATUS.CANCELLED }, { where: { id: project1.id } }) + models.Project.update({ + status: PROJECT_STATUS.CANCELLED, + }, { + where: { + id: project1.id, + }, + }) .then(() => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.ACTIVE; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }, + }; request(server) .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.manager}` }) - .send(sbody) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(mbody) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.name.should.equal('updatedProject name'); + resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); + resJson.updatedBy.should.equal(40051334); + server.services.pubsub.publish.calledWith('project.updated').should.be.true; + // validate that project history is updated + models.ProjectHistory.findAll({ + where: { + projectId: project1.id, + }, + }).then((histories) => { + should.exist(histories); + histories.length.should.equal(1); + const history = histories[0].get({ + plain: true, + }); + history.status.should.equal(PROJECT_STATUS.ACTIVE); + history.projectId.should.equal(project1.id); + done(); + }); } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.name.should.equal('updatedProject name'); - resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); - resJson.updatedBy.should.equal(40051334); - server.services.pubsub.publish.calledWith('project.updated').should.be.true; - // validate that project history is updated - models.ProjectHistory.findAll({ - where: { projectId: project1.id }, - }).then((histories) => { - should.exist(histories); - histories.length.should.equal(1); - const history = histories[0].get({ plain: true }); - history.status.should.equal(PROJECT_STATUS.ACTIVE); - history.projectId.should.equal(project1.id); - done(); - }); }); }); }); it('should return 200, admin is allowed to transition project out of cancel status', (done) => { - models.Project.update({ status: PROJECT_STATUS.CANCELLED }, { where: { id: project1.id } }) + models.Project.update({ + status: PROJECT_STATUS.CANCELLED, + }, { + where: { + id: project1.id, + }, + }) .then(() => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.ACTIVE; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }, + }; request(server) .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.admin}` }) - .send(sbody) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(mbody) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.name.should.equal('updatedProject name'); + resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); + resJson.updatedBy.should.equal(40051333); + server.services.pubsub.publish.calledWith('project.updated').should.be.true; + // validate that project history is updated + models.ProjectHistory.findAll({ + where: { + projectId: project1.id, + }, + }).then((histories) => { + should.exist(histories); + histories.length.should.equal(1); + const history = histories[0].get({ + plain: true, + }); + history.status.should.equal(PROJECT_STATUS.ACTIVE); + history.projectId.should.equal(project1.id); + done(); + }); } - const resJson = res.body.result.content; - should.exist(resJson); - resJson.name.should.equal('updatedProject name'); - resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); - resJson.updatedBy.should.equal(40051333); - server.services.pubsub.publish.calledWith('project.updated').should.be.true; - // validate that project history is updated - models.ProjectHistory.findAll({ - where: { projectId: project1.id }, - }).then((histories) => { - should.exist(histories); - histories.length.should.equal(1); - const history = histories[0].get({ plain: true }); - history.status.should.equal(PROJECT_STATUS.ACTIVE); - history.projectId.should.equal(project1.id); - done(); - }); }); }); }); it('should return 403, copilot is not allowed to transition project out of cancel status', (done) => { - models.Project.update({ status: PROJECT_STATUS.CANCELLED }, { where: { id: project1.id } }) + models.Project.update({ + status: PROJECT_STATUS.CANCELLED, + }, { + where: { + id: project1.id, + }, + }) .then(() => { - const sbody = _.cloneDeep(body); - sbody.param.status = PROJECT_STATUS.ACTIVE; + const mbody = { + param: { + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }, + }; request(server) .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(sbody) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(mbody) .expect('Content-Type', /json/) .expect(403) .end((err, res) => { if (err) { - return done(err); + done(err); + } else { + const result = res.body.result; + result.success.should.be.false; + result.status.should.equal(403); + done(); } - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(403); - done(); }); }); }); it('should return 200 and project history should not be updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send(body) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.name.should.equal('updatedProject name'); @@ -426,13 +528,16 @@ describe('Project', () => { server.services.pubsub.publish.calledWith('project.updated').should.be.true; // validate that project history is not updated models.ProjectHistory.findAll({ - where: { projectId: project1.id }, + where: { + projectId: project1.id, + }, }).then((histories) => { should.exist(histories); histories.length.should.equal(0); done(); }); - }); + } + }); }); it('should return 500 if error to sync billing account id', (done) => { @@ -441,25 +546,28 @@ describe('Project', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - billingAccountId: 123, - }, - }) - .expect('Content-Type', /json/) - .expect(500) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + billingAccountId: 123, + }, + }) + .expect('Content-Type', /json/) + .expect(500) + .end((err, res) => { + if (err) { + done(err); + } else { const result = res.body.result; result.success.should.be.false; result.status.should.equal(500); result.content.message.should.equal('error message'); done(); - }); + } + }); }); it('should return 200 and sync new billing account id', (done) => { @@ -482,19 +590,21 @@ describe('Project', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - billingAccountId: 123, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + billingAccountId: 123, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.billingAccountId.should.equal(123); @@ -503,48 +613,54 @@ describe('Project', () => { postSpy.should.have.been.calledOnce; server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); - }); + } + }); }); it('should return 200 and not sync same billing account id', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - billingAccountId: 1, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + billingAccountId: 1, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.billingAccountId.should.equal(1); resJson.billingAccountId.should.equal(1); server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); - }); + } + }); }); it('should return 200 and not sync same billing account id for project without direct project id', (done) => { request(server) - .patch(`/v4/projects/${project3.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.admin}` }) - .send({ - param: { - billingAccountId: 1, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project3.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: { + billingAccountId: 1, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { const resJson = res.body.result.content; should.exist(resJson); resJson.billingAccountId.should.equal(1); @@ -552,53 +668,60 @@ describe('Project', () => { resJson.updatedBy.should.equal(40051333); server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); - }); + } + }); }); it.skip('should return 200 and update bookmarks', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - bookmarks: [{ - title: 'title1', - address: 'address1', - }], - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + bookmarks: [{ + title: 'title1', + address: 'address1', + }], + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { let resJson = res.body.result.content; should.exist(resJson); resJson.bookmarks.should.have.lengthOf(1); resJson.bookmarks[0].title.should.be.eql('title1'); resJson.bookmarks[0].address.should.be.eql('address1'); request(server) - .patch(`/v4/projects/${project1.id}`) - .set({ Authorization: `Bearer ${testUtil.jwts.copilot}` }) - .send({ - param: { - bookmarks: null, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - if (err) { - return done(err); - } - resJson = res.body.result.content; - should.exist(resJson); - should.not.exist(resJson.bookmarks); - server.services.pubsub.publish.calledWith('project.updated').should.be.true; - done(); - }); - }); + .patch(`/v4/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + bookmarks: null, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((error, resp) => { + if (error) { + done(error); + } else { + resJson = resp.body.result.content; + should.exist(resJson); + should.not.exist(resJson.bookmarks); + server.services.pubsub.publish.calledWith('project.updated').should.be.true; + done(); + } + }); + } + }); }); }); }); diff --git a/src/services/fileService.js b/src/services/fileService.js index fdbc56cb..944f3e2f 100644 --- a/src/services/fileService.js +++ b/src/services/fileService.js @@ -1,17 +1,17 @@ - -import util from '../util'; -import config from 'config'; /** * Service methods to handle direct project. */ +import config from 'config'; +import util from '../util'; + /** * Build custom http client for request - * @param req request - * @returns custom http client + * @param {Object} req request + * @return {Object} custom http client * @private */ -function _getHttpClient(req) { +function getHttpClient(req) { const httpClient = util.getHttpClient(req); httpClient.defaults.headers.common.Authorization = req.headers.authorization; httpClient.defaults.baseURL = config.get('fileServiceEndpoint'); @@ -31,8 +31,12 @@ function _getHttpClient(req) { export default { /** * Delete file from S3 using fileservice + * + * @param {Object} req the request + * @param {String} filePath the file path + * @return {Void} the function returns void */ deleteFile(req, filePath) { - _getHttpClient(req).delete('', { params: { filter: `filePath%3D${filePath}` } }); + getHttpClient(req).delete('', { params: { filter: `filePath%3D${filePath}` } }); }, }; diff --git a/src/services/index.js b/src/services/index.js index 9eaaabc6..fc0453e2 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -6,11 +6,17 @@ import RabbitMQService from './rabbitmq'; /** * Responsible for establishing connections to all external services * Also has a hook to load mock services for unit testing. + * + * @param {Object} fapp the app object + * @param {Object} logger the logger to use + * + * @return {Void} the function returns void */ -module.exports = (app, logger) => { +module.exports = (fapp, logger) => { + const app = fapp; app.services = app.service || {}; if (process.env.NODE_ENV.toLowerCase() === 'test') { - require('../tests/serviceMocks')(app); + require('../tests/serviceMocks')(app); // eslint-disable-line global-require } else { // RabbitMQ Initialization app.services.pubsub = new RabbitMQService(app, logger); diff --git a/src/tests/seed.js b/src/tests/seed.js index 54fb5100..a1f53c84 100644 --- a/src/tests/seed.js +++ b/src/tests/seed.js @@ -1,4 +1,5 @@ import models from '../models'; + models.sequelize.sync({ force: true }) .then(() => models.Project.bulkCreate([{ @@ -102,7 +103,6 @@ models.sequelize.sync({ force: true }) return Promise.all(operations); }) .then(() => { - console.log('Success'); process.exit(0); }) - .catch(err => console.log('Failed: ', err)); + .catch(() => process.exit(1)); diff --git a/src/tests/util.js b/src/tests/util.js index 4387755b..3035a1bc 100644 --- a/src/tests/util.js +++ b/src/tests/util.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import models from '../models'; diff --git a/src/util.js b/src/util.js index 463e5387..0b1a38b3 100644 --- a/src/util.js +++ b/src/util.js @@ -14,14 +14,19 @@ import _ from 'lodash'; import querystring from 'querystring'; import config from 'config'; +const exec = require('child_process').exec; +const models = require('./models').default; + const util = _.cloneDeep(require('tc-core-library-js').util(config)); + _.assignIn(util, { /** * Handle error - * @param defaultMessage the default error message - * @param err the err - * @param next the next function - * @returns next function with error + * @param {String} msg the default error message + * @param {Error} err the err + * @param {Object} req the request + * @param {Function} next the next function + * @returns {Function} next function with error */ handleError: (msg, err, req, next) => { req.log.error({ @@ -37,9 +42,9 @@ _.assignIn(util, { }, /** * Validates if filters are valid - * @param {object} filters object with filters - * @param {array} validValues valid filter values - * @return {boolean} + * @param {object} filters object with filters + * @param {array} validValues valid filter values + * @return {boolean} true if filters are valid otherwise false */ isValidFilter: (filters, validValues) => { let valid = true; @@ -64,8 +69,9 @@ _.assignIn(util, { /** * Parses query fields and groups them per table - * @param {array} queryFields list of query fields - * @return {object} + * @param {array} queryFields list of query fields + * @param {Object} allowedFields the allowed fields + * @return {object} the parsed fields */ parseFields: (queryFields, allowedFields) => { const fields = _.cloneDeep(allowedFields); @@ -85,12 +91,12 @@ _.assignIn(util, { }, /** - * [description] - * @param {[type]} queryFilter [description] - * @return {[type]} [description] + * Parse the query filters + * @param {String} fqueryFilter the query filter string + * @return {Object} the parsed array */ - parseQueryFilter: (queryFilter) => { - queryFilter = querystring.parse(queryFilter); + parseQueryFilter: (fqueryFilter) => { + let queryFilter = querystring.parse(fqueryFilter); // convert in to array queryFilter = _.mapValues(queryFilter, (val) => { if (val.indexOf('in(') > -1) { @@ -118,8 +124,6 @@ _.assignIn(util, { `"${dest}"`, '--region us-east-1', ], ' '); - - const exec = require('child_process').exec; exec(cmdStr, (error, stdout, stderr) => { req.log.debug(`s3FileTransfer: stdout: ${stdout}`); req.log.debug(`s3FileTransfer: stderr: ${stderr}`); @@ -134,9 +138,9 @@ _.assignIn(util, { /** * retrieve download urls for all attachments - * @param {[type]} req original request - * @param {[type]} attachments list of attachments to retrieve urls for - * @return {[type]} [description] + * @param {Object} req original request + * @param {String} filePath the file path + * @return {String} the download url */ getFileDownloadUrl: (req, filePath) => { if (!filePath) { @@ -164,7 +168,6 @@ _.assignIn(util, { }); }, getProjectAttachments: (req, projectId) => { - const models = require('./models').default; let attachments = []; return models.ProjectAttachment.getActiveProjectAttachments(projectId) .then((_attachments) => { @@ -185,7 +188,8 @@ _.assignIn(util, { // result is an array of 'tuples' => [[path, url], [path,url]] // convert it to a map for easy lookup const urls = _.fromPairs(result); - _.each(attachments, (a) => { + _.each(attachments, (at) => { + const a = at; a.downloadUrl = urls[a.filePath]; }); return attachments; @@ -195,7 +199,8 @@ _.assignIn(util, { getSystemUserToken: (logger, id = 'system') => { const httpClient = util.getHttpClient({ id, log: logger }); const url = `${config.get('identityServiceEndpoint')}authorizations`; - const formData = `clientId=${config.get('systemUserClientId')}&secret=${encodeURIComponent(config.get('systemUserClientSecret'))}`; + const formData = `clientId=${config.get('systemUserClientId')}&` + + `secret=${encodeURIComponent(config.get('systemUserClientSecret'))}`; return httpClient.post(url, formData, { timeout: 4000, @@ -208,11 +213,11 @@ _.assignIn(util, { /** * Fetches the topcoder user details using the given JWT token. * - * @param userId id of the user to be fetched - * @param jwtToken JWT token of the admin user or JWT token of the user to be fecthed - * @param logger logger to be used for logging purposes + * @param {Number} userId id of the user to be fetched + * @param {String} jwtToken JWT token of the admin user or JWT token of the user to be fecthed + * @param {Object} logger logger to be used for logging purposes * - * @return promise which resolves to the user's information + * @return {Promise} promise which resolves to the user's information */ getTopcoderUser: (userId, jwtToken, logger) => { const httpClient = util.getHttpClient({ id: `userService_${userId}`, log: logger }); @@ -222,7 +227,7 @@ _.assignIn(util, { httpClient.defaults.headers.common.Authorization = `Bearer ${jwtToken}`; return httpClient.get(`${config.userServiceUrl}/${userId}`).then((response) => { if (response.data && response.data.result - && response.data.result.status == 200 && response.data.result.content) { + && response.data.result.status === 200 && response.data.result.content) { return response.data.result.content; } return null;