diff --git a/.circleci/config.yml b/.circleci/config.yml index c48b1a79..ddcc016c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,16 +1,18 @@ -version: 2 +version: 2.1 python_env: &python_env docker: - - image: circleci/python:2.7-stretch-browsers - + - image: cimg/python:3.11.0-browsers install_awscli: &install_awscli name: "Install awscli" command: | - sudo pip install awscli --upgrade + sudo apt update + sudo apt install jq + sudo apt install python3-pip + sudo pip3 install awscli --upgrade install_deploysuite: &install_deploysuite name: Installation of install_deploysuite. command: | - git clone --branch v1.4.1 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript + git clone --branch v1.4.14 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript cp ./../buildscript/master_deploy.sh . cp ./../buildscript/buildenv.sh . cp ./../buildscript/awsconfiguration.sh . diff --git a/pom.xml b/pom.xml index 8cb902e2..c8aec09b 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ ${jmeter-maven-plugin.version} true - 60 + 61 false ${project.version} diff --git a/src/constants.js b/src/constants.js index 18a9a19d..98c12d7b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -34,7 +34,6 @@ export const PROJECT_MEMBER_ROLE = { export const PROJECT_MEMBER_MANAGER_ROLES = [ PROJECT_MEMBER_ROLE.MANAGER, - PROJECT_MEMBER_ROLE.OBSERVER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, PROJECT_MEMBER_ROLE.PROJECT_MANAGER, @@ -142,6 +141,7 @@ export const BUS_API_EVENT = { PROJECT_CREATED: 'project.action.create', PROJECT_UPDATED: 'project.action.update', PROJECT_DELETED: 'project.action.delete', + PROJECT_BILLING_ACCOUNT_UPDATED: 'project.action.billingAccount.update', PROJECT_MEMBER_ADDED: 'project.action.create', PROJECT_MEMBER_REMOVED: 'project.action.delete', diff --git a/src/events/busApi.js b/src/events/busApi.js index c2bd666d..a9fcc0ce 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -96,6 +96,18 @@ module.exports = (app, logger) => { projectUrl: connectProjectUrl(updated.id), }), logger); + if (!_.isEqual(original.billingAccountId, updated.billingAccountId)) { + logger.debug('project billing account is updated'); + createEvent(BUS_API_EVENT.PROJECT_BILLING_ACCOUNT_UPDATED, { + projectId: updated.id, + projectName: updated.name, + directProjectId: updated.directProjectId, + status: updated.status, + oldBillingAccountId: original.billingAccountId, + newBillingAccountId: updated.billingAccountId, + }, logger); + } + /* Send event for Notification Service */ diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 1d7949b2..bb0f7b0f 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -216,7 +216,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'There are additional limitations on editing some parts of the project.', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECTS_WRITE, }, @@ -365,7 +368,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can update project members with "customer" role.', }, topcoderRoles: TOPCODER_ROLES_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECT_MEMBERS_WRITE, }, @@ -387,7 +393,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can delete project members with "customer" role.', }, topcoderRoles: TOPCODER_ROLES_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECT_MEMBERS_WRITE, }, @@ -412,7 +421,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export ...TOPCODER_ROLES_ADMINS, USER_ROLE.COPILOT_MANAGER, ], - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECT_MEMBERS_WRITE, }, @@ -436,7 +448,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can view invites of other users.', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECT_INVITES_READ, }, @@ -447,7 +462,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can invite project members with "customer" role.', }, topcoderRoles: TOPCODER_ROLES_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECT_INVITES_WRITE, }, @@ -576,7 +594,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export group: 'Project Attachment', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECTS_WRITE, }, @@ -608,7 +629,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can edit attachment they created.', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECTS_WRITE, }, @@ -629,7 +653,10 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export description: 'Who can delete attachment they created.', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], scopes: SCOPES_PROJECTS_WRITE, }, @@ -708,6 +735,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export */ export const PROJECT_TO_TOPCODER_ROLES_MATRIX = { [PROJECT_MEMBER_ROLE.CUSTOMER]: _.values(USER_ROLE), + [PROJECT_MEMBER_ROLE.OBSERVER]: _.values(USER_ROLE), [PROJECT_MEMBER_ROLE.MANAGER]: [ USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN, diff --git a/src/routes/scopeChangeRequests/create.spec.js b/src/routes/scopeChangeRequests/create.spec.js index ca15f656..e69de29b 100644 --- a/src/routes/scopeChangeRequests/create.spec.js +++ b/src/routes/scopeChangeRequests/create.spec.js @@ -1,280 +0,0 @@ -import sinon from 'sinon'; -import request from 'supertest'; -import _ from 'lodash'; -import Promise from 'bluebird'; - -import models from '../../models'; -import server from '../../app'; -import testUtil from '../../tests/util'; - -import { PROJECT_STATUS, PROJECT_MEMBER_ROLE, SCOPE_CHANGE_REQ_STATUS } from '../../constants'; - -/** - * Creates a project with given status - * @param {string} status - Status of the project - * - * @returns {Promise} - promise for project creation - */ -function createProject(status) { - const newMember = (userId, role, project) => ({ - userId, - projectId: project.id, - role, - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - - return models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status, - details: {}, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then(project => - Promise.all([ - models.ProjectMember.create(newMember(testUtil.userIds.member, PROJECT_MEMBER_ROLE.CUSTOMER, project)), - models.ProjectMember.create(newMember(testUtil.userIds.manager, PROJECT_MEMBER_ROLE.MANAGER, project)), - ]).then(() => project), - ); -} - -/** - * creates a new scope change request object - * @returns {Object} - scope change request object - */ -function newScopeChangeRequest() { - return { - newScope: { - appDefinition: { - numberScreens: '5-8', - }, - }, - oldScope: { - appDefinition: { - numberScreens: '2-4', - }, - }, - }; -} - -/** - * Asserts the status of the Scope change request - * @param {Object} response - Response object from the post service - * @param {string} expectedStatus - Expected status of the Scope Change Request - * - * @returns {undefined} - throws error if assertion failed - */ -function assertStatus(response, expectedStatus) { - const status = _.get(response, 'body.status'); - sinon.assert.match(status, expectedStatus); -} - -/** - * Updaes the status of scope change requests for the given project in db - * @param {Object} project - the project - * @param {string} status - the new status for update - * - * @returns {Promise} the promise to update the status - */ -function updateScopeChangeStatuses(project, status) { - return models.ScopeChangeRequest.update({ status }, { where: { projectId: project.id } }); -} - - -describe('Create Scope Change Rquest', () => { - let projects; - let projectWithPendingChange; - let projectWithApprovedChange; - - before((done) => { - const projectStatuses = [ - PROJECT_STATUS.DRAFT, - PROJECT_STATUS.IN_REVIEW, - PROJECT_STATUS.REVIEWED, - PROJECT_STATUS.ACTIVE, - ]; - - Promise.all(projectStatuses.map(status => createProject(status))) - .then(_projects => _projects.map((project, i) => [projectStatuses[i], project])) - .then((_projectStatusPairs) => { - projects = _.fromPairs(_projectStatusPairs); - }) - .then(() => done()); - }); - - after((done) => { - testUtil.clearDb(done); - }); - - describe('POST projects/{projectId}/scopeChangeRequests', () => { - it('Should create scope change request for project in reviewed status', (done) => { - const project = projects[PROJECT_STATUS.REVIEWED]; - - request(server) - .post(`/v5/projects/${project.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send(newScopeChangeRequest()) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - projectWithPendingChange = project; - - assertStatus(res, SCOPE_CHANGE_REQ_STATUS.PENDING); - done(); - } - }); - }); - - it('Should create scope change request for project in active status', (done) => { - const project = projects[PROJECT_STATUS.ACTIVE]; - - request(server) - .post(`/v5/projects/${project.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - projectWithApprovedChange = project; - - assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); - done(); - } - }); - }); - - it('Should return error with status 403 if project is in draft status', (done) => { - const project = projects[PROJECT_STATUS.DRAFT]; - - request(server) - .post(`/v5/projects/${project.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(403) - .end(err => done(err)); - }); - - it('Should return error with status 403 if project is in in_review status', (done) => { - const project = projects[PROJECT_STATUS.IN_REVIEW]; - - request(server) - .post(`/v5/projects/${project.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(403) - .end(err => done(err)); - }); - - it('Should return error with status 404 if project not present', (done) => { - const nonExistentProjectId = 341212; - request(server) - .post(`/v5/projects/${nonExistentProjectId}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send(newScopeChangeRequest()) - .expect(404) - .end(err => done(err)); - }); - - it('Should return error with status 403 if there is a request in pending status', (done) => { - request(server) - .post(`/v5/projects/${projectWithPendingChange.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(403) - .end(err => done(err)); - }); - - it('Should return error with status 403 if there is a request in approved status', (done) => { - request(server) - .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(403) - .end(err => done(err)); - }); - - it('Should create scope change request if there is a request in canceled status', (done) => { - updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.CANCELED).then(() => { - request(server) - .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); - done(); - } - }); - }); - }); - - it('Should create scope change request if there is a request in rejected status', (done) => { - updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.REJECTED).then(() => { - request(server) - .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); - done(); - } - }); - }); - }); - - it('Should create scope change request if there is a request in activated status', (done) => { - updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.ACTIVATED).then(() => { - request(server) - .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(newScopeChangeRequest()) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); - done(); - } - }); - }); - }); - }); -}); diff --git a/src/routes/scopeChangeRequests/update.spec.js b/src/routes/scopeChangeRequests/update.spec.js index cbceca57..e69de29b 100644 --- a/src/routes/scopeChangeRequests/update.spec.js +++ b/src/routes/scopeChangeRequests/update.spec.js @@ -1,213 +0,0 @@ -import sinon from 'sinon'; -import request from 'supertest'; -import _ from 'lodash'; - -import models from '../../models'; -import server from '../../app'; -import testUtil from '../../tests/util'; - -import { PROJECT_STATUS, PROJECT_MEMBER_ROLE, SCOPE_CHANGE_REQ_STATUS } from '../../constants'; - -/** - * Creates a project with given status - * @param {string} status - Status of the project - * - * @returns {Promise} - promise for project creation - */ -function createProject(status) { - const newMember = (userId, role, project) => ({ - userId, - projectId: project.id, - role, - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - - return models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status, - details: {}, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then(project => - Promise.all([ - models.ProjectMember.create(newMember(testUtil.userIds.member, PROJECT_MEMBER_ROLE.CUSTOMER, project)), - models.ProjectMember.create(newMember(testUtil.userIds.manager, PROJECT_MEMBER_ROLE.MANAGER, project)), - ]).then(() => project), - ); -} - -/** - * Asserts the status of the Scope change request - * @param {Object} updatedScopeChangeRequest - the updated scope change request from db - * @param {string} expectedStatus - Expected status of the Scope Change Request - * - * @returns {undefined} - throws error if assertion failed - */ -function assertStatus(updatedScopeChangeRequest, expectedStatus) { - sinon.assert.match(updatedScopeChangeRequest.status, expectedStatus); -} - -/** - * create scope change request for the given project - * @param {Object} project - the project - * - * @returns {Promise} - the promise to create scope change request - */ -function createScopeChangeRequest(project) { - return models.ScopeChangeRequest.create({ - newScope: { - appDefinition: { - numberScreens: '5-8', - }, - }, - oldScope: { - appDefinition: { - numberScreens: '2-4', - }, - }, - projectId: project.id, - status: SCOPE_CHANGE_REQ_STATUS.PENDING, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }); -} - -/** - * Updates the details json of the project - * @param {string} projectId The project id - * @param {Object} detailsChange The changes to be merged with details json - * - * @returns {Promise} A promise to update details json in the project - */ -function updateProjectDetails(projectId, detailsChange) { - return models.Project.findByPk(projectId).then((project) => { - const updatedDetails = _.merge({}, project.details, detailsChange); - return project.update({ details: updatedDetails }); - }); -} - -describe('Update Scope Change Rquest', () => { - let project; - let scopeChangeRequest; - - before((done) => { - testUtil - .clearDb() - .then(() => createProject(PROJECT_STATUS.REVIEWED)) - .then((_project) => { - project = _project; - return project; - }) - .then(_project => createScopeChangeRequest(_project)) - .then((_scopeChangeRequest) => { - scopeChangeRequest = _scopeChangeRequest; - return scopeChangeRequest; - }) - .then(() => done()); - }); - - after((done) => { - testUtil.clearDb(done); - }); - - describe('PATCH projects/{projectId}/scopeChangeRequests/{requestId}', () => { - it('Should approve change request with customer login', (done) => { - request(server) - .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - status: SCOPE_CHANGE_REQ_STATUS.APPROVED, - }) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - models.ScopeChangeRequest.findOne({ where: { id: scopeChangeRequest.id } }).then((_scopeChangeRequest) => { - assertStatus(_scopeChangeRequest, SCOPE_CHANGE_REQ_STATUS.APPROVED); - done(); - }); - } - }); - }); - - it('Should activate change request with manager login', (done) => { - // Updating project details before activation. This is used in a later test case - updateProjectDetails(project.id, { apiDefinition: { notes: 'Please include swagger docs' } }).then(() => { - request(server) - .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send({ - status: SCOPE_CHANGE_REQ_STATUS.ACTIVATED, - }) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - models.ScopeChangeRequest.findOne({ where: { id: scopeChangeRequest.id } }) - .then((_scopeChangeRequest) => { - assertStatus(_scopeChangeRequest, SCOPE_CHANGE_REQ_STATUS.ACTIVATED); - done(); - }); - } - }); - }); - }); - - it('Should update details field of project on activation', (done) => { - models.Project.findOne({ where: { id: project.id } }).then((_project) => { - const numberScreens = _.get(_project, 'details.appDefinition.numberScreens'); - sinon.assert.match(numberScreens, '5-8'); - done(); - }); - }); - - it("Should preserve fields of details json that doesn't change the scope on activation", (done) => { - models.Project.findOne({ where: { id: project.id } }).then((_project) => { - const apiNotes = _.get(_project, 'details.apiDefinition.notes'); - sinon.assert.match(apiNotes, 'Please include swagger docs'); - done(); - }); - }); - - it('Should not allow updating oldScope', (done) => { - request(server) - .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send({ - oldScope: {}, - }) - .expect(400) - .end(err => done(err)); - }); - - it('Should not allow updating newScope', (done) => { - request(server) - .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send({ - newScope: {}, - }) - .expect(400) - .end(err => done(err)); - }); - }); -});