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));
- });
- });
-});