From 76d88f97992fc75e96b3a34e8d5b8880ed5a360e Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Mon, 8 Feb 2021 20:13:20 +0530 Subject: [PATCH 1/2] feat: git#620-New API endpoint to bring Billing Accounts available to the logged in user - Initial draft --- config/custom-environment-variables.json | 8 +- config/default.json | 8 +- src/constants.js | 1 + src/permissions/constants.js | 22 ++++ src/permissions/index.js | 4 + src/routes/billingAccounts/list.js | 39 +++++++ src/routes/billingAccounts/list.spec.js | 133 +++++++++++++++++++++++ src/routes/index.js | 3 + src/services/salesforceService.js | 77 +++++++++++++ src/tests/util.js | 2 + 10 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/routes/billingAccounts/list.js create mode 100644 src/routes/billingAccounts/list.spec.js create mode 100644 src/services/salesforceService.js diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index c414fbce..8dd29cf4 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -70,5 +70,11 @@ "EMBED_REPORTS_MAPPING": "EMBED_REPORTS_MAPPING", "ALLOWED_USERS": "REPORTS_ALLOWED_USERS" }, - "DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID" + "DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID", + "salesforce": { + "CLIENT_AUDIENCE": "SALESFORCE_AUDIENCE", + "CLIENT_KEY": "SALESFORCE_CLIENT_KEY", + "SUBJECT": "SALESFORCE_SUBJECT", + "CLIENT_ID": "SALESFORCE_CLIENT_ID" + } } diff --git a/config/default.json b/config/default.json index b302a52f..c67a8bdb 100644 --- a/config/default.json +++ b/config/default.json @@ -76,5 +76,11 @@ "ALLOWED_USERS": "[]" }, "DEFAULT_M2M_USERID": -101, - "taasJobApiUrl": "https://api.topcoder.com/v5/jobs" + "taasJobApiUrl": "https://api.topcoder.com/v5/jobs", + "salesforce": { + "CLIENT_KEY": "", + "CLIENT_AUDIENCE": "", + "SUBJECT": "", + "CLIENT_ID": "" + } } diff --git a/src/constants.js b/src/constants.js index 97b0371f..8b8a1f9d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -274,6 +274,7 @@ export const M2M_SCOPES = { ALL: 'all:projects', READ: 'read:projects', WRITE: 'write:projects', + READ_BILLING_ACCOUNTS: 'read:user-billing-accounts', WRITE_BILLING_ACCOUNTS: 'write:projects-billing-accounts', }, PROJECT_MEMBERS: { diff --git a/src/permissions/constants.js b/src/permissions/constants.js index aa9f4ee9..02c7c2ca 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -91,6 +91,15 @@ const SCOPES_PROJECTS_WRITE = [ M2M_SCOPES.PROJECTS.WRITE, ]; +/** + * M2M scopes to "read" available Billing Accounts for the project + */ +const SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS = [ + M2M_SCOPES.CONNECT_PROJECT_ADMIN, + M2M_SCOPES.READ_BILLING_ACCOUNTS, + M2M_SCOPES.PROJECTS.ALL, +]; + /** * M2M scopes to "write" billingAccountId property */ @@ -252,6 +261,19 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export scopes: SCOPES_PROJECTS_WRITE, }, + /* + * Project Invite + */ + READ_AVL_PROJECT_BILLING_ACCOUNTS: { + meta: { + title: 'Read Available Project Billing Accounts', + group: 'Project Billing Accounts', + description: 'Who can view the Billing Accounts available for the project', + }, + topcoderRoles: ALL, + scopes: SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS, + }, + /* * Project Member */ diff --git a/src/permissions/index.js b/src/permissions/index.js index a37fdb04..2e62dc7f 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -20,6 +20,10 @@ module.exports = () => { Authorizer.setPolicy('project.edit', generalPermission(PERMISSION.UPDATE_PROJECT)); Authorizer.setPolicy('project.delete', generalPermission(PERMISSION.DELETE_PROJECT)); + Authorizer.setPolicy('projectBillingAccounts.view', generalPermission([ + PERMISSION.READ_AVL_PROJECT_BILLING_ACCOUNTS, + ])); + Authorizer.setPolicy('projectMember.create', generalPermission([ PERMISSION.CREATE_PROJECT_MEMBER_OWN, PERMISSION.CREATE_PROJECT_MEMBER_NOT_OWN, diff --git a/src/routes/billingAccounts/list.js b/src/routes/billingAccounts/list.js new file mode 100644 index 00000000..77166d56 --- /dev/null +++ b/src/routes/billingAccounts/list.js @@ -0,0 +1,39 @@ +// import _ from 'lodash'; +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import SalesforceService from '../../services/salesforceService'; + +/** + * API to get project attachments. + * + */ + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectBillingAccounts.view'), + async (req, res, next) => { + // const projectId = _.parseInt(req.params.projectId); + const userId = req.authUser.userId; + try { + const { accessToken, instanceUrl } = await SalesforceService.authenticate(); + // eslint-disable-next-line + const sql = `SELECT Topcoder_Billing_Account__r.id, Topcoder_Billing_Account__r.TopCoder_Billing_Account_Id__c, Topcoder_Billing_Account__r.Billing_Account_Name__c, Topcoder_Billing_Account__r.Start_Date__c, Topcoder_Billing_Account__r.End_Date__c from Topcoder_Billing_Account_Resource__c tbar where UserID__c='${userId}'`; + // and Topcoder_Billing_Account__r.TC_Connect_Project_ID__c='${projectId}' + req.log.debug(sql); + const billingAccounts = await SalesforceService.query(sql, accessToken, instanceUrl, req.log); + res.json(billingAccounts); + } catch (error) { + req.log.error(error); + next(error); + } + }, +]; diff --git a/src/routes/billingAccounts/list.spec.js b/src/routes/billingAccounts/list.spec.js new file mode 100644 index 00000000..1bf4144f --- /dev/null +++ b/src/routes/billingAccounts/list.spec.js @@ -0,0 +1,133 @@ +/* eslint-disable no-unused-expressions */ +// import chai from 'chai'; +import request from 'supertest'; +import sinon from 'sinon'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import SalesforceService from '../../services/salesforceService'; + +// const should = chai.should(); + +describe('Project Billing Accounts list', () => { + let project1; + let salesforceAuthenticate; + let salesforceQuery; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => testUtil.clearES()) + .then(() => { + models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project1 = p; + // create members + return models.ProjectMember.create({ + userId: testUtil.userIds.copilot, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then(() => models.ProjectMember.create({ + userId: testUtil.userIds.member, + projectId: project1.id, + role: 'customer', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + })); + }).then(() => { + salesforceAuthenticate = sinon.stub(SalesforceService, 'authenticate', () => Promise.resolve({ + accessToken: 'mock', + instanceUrl: 'mock_url', + })); + salesforceQuery = sinon.stub(SalesforceService, 'query', () => Promise.resolve([{ + accessToken: 'mock', + instanceUrl: 'mock_url', + }])); + done(); + }); + }); + }); + + afterEach((done) => { + salesforceAuthenticate.restore(); + salesforceQuery.restore(); + done(); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('List /projects/{id}/billingAccounts', () => { + it('should return 403 for anonymous user', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .expect(403, done); + }); + + it('should return 403 for a regular user who is not a member of the project', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .send() + .expect(403, done); + }); + + it('should return all attachments to admin', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send() + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + resJson.should.have.length(2); + // TODO verify BA fields + done(); + } + }); + }); + + xit('should return all attachments using M2M token with "read:user-billing-accounts" scope', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .set({ + Authorization: `Bearer ${testUtil.m2m['read:user-billing-accounts']}`, + }) + .send() + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + resJson.should.have.length(2); + // TODO verify BA fields + done(); + } + }); + }); + }); +}); diff --git a/src/routes/index.js b/src/routes/index.js index 29a180a0..5a28ee64 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -121,6 +121,9 @@ router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests/:requestId(\\d+) .patch(require('./scopeChangeRequests/update')); // .delete(require('./scopeChangeRequests/delete')); +router.route('/v5/projects/:projectId(\\d+)/billingAccounts') + .get(require('./billingAccounts/list')); + router.route('/v5/projects/:projectId(\\d+)/members') .get(require('./projectMembers/list')) .post(require('./projectMembers/create')); diff --git a/src/services/salesforceService.js b/src/services/salesforceService.js new file mode 100644 index 00000000..ea3f775f --- /dev/null +++ b/src/services/salesforceService.js @@ -0,0 +1,77 @@ +/** + * Represents the Salesforce service + */ +import _ from 'lodash'; +import config from 'config'; +import jwt from 'jsonwebtoken'; + +const axios = require('axios'); + +const loginBaseUrl = config.salesforce.CLIENT_AUDIENCE || 'https://login.salesforce.com'; +// we are using dummy private key to fail safe when key is not provided in env +let privateKey = config.salesforce.CLIENT_KEY || 'privateKey'; +privateKey = privateKey.replace(/\\n/g, '\n'); + +const urlEncodeForm = k => + Object.keys(k).reduce((a, b) => `${a}&${b}=${encodeURIComponent(k[b])}`, ''); + +/** + * Helper class to abstract salesforce API calls + */ +class SalesforceService { + /** + * Authenticate to Salesforce with pre-configured credentials + * @returns {{accessToken: String, instanceUrl: String}} the result + */ + static authenticate() { + const jwtToken = jwt.sign({}, privateKey, { + expiresIn: '1h', // any expiration + issuer: config.salesforce.CLIENT_ID, + audience: config.salesforce.CLIENT_AUDIENCE, + subject: config.salesforce.SUBJECT, + algorithm: 'RS256', + }); + return axios({ + method: 'post', + url: `${loginBaseUrl}/services/oauth2/token`, + data: urlEncodeForm({ + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: jwtToken, + }), + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }).then(res => ({ + accessToken: res.data.access_token, + instanceUrl: res.data.instance_url, + })); + } + + /** + * Run the query statement + * @param {String} sql the Saleforce sql statement + * @param {String} accessToken the access token + * @param {String} instanceUrl the salesforce instance url + * @param {Object} logger logger to be used for logging + * @returns {{totalSize: Number, done: Boolean, records: Array}} the result + */ + static query(sql, accessToken, instanceUrl, logger) { + return axios({ + url: `${instanceUrl}/services/data/v37.0/query?q=${sql}`, + method: 'get', + headers: { authorization: `Bearer ${accessToken}` }, + }).then((res) => { + if (logger) { + logger.debug(_.get(res, 'data.records', [])); + } + const billingAccounts = _.get(res, 'data.records', []).map(o => ({ + sfBillingAccountId: _.get(o, 'Topcoder_Billing_Account__r.Id'), + tcBillingAccountId: _.get(o, 'Topcoder_Billing_Account__r.TopCoder_Billing_Account_Id__c'), + name: _.get(o, 'Topcoder_Billing_Account__r.Billing_Account_Name__c'), + startDate: _.get(o, 'Topcoder_Billing_Account__r.Start_Date__c'), + endDate: _.get(o, 'Topcoder_Billing_Account__r.End_Date__c'), + })); + return billingAccounts; + }); + } +} + +export default new SalesforceService(); diff --git a/src/tests/util.js b/src/tests/util.js index a98e7b5c..7a8fcf23 100644 --- a/src/tests/util.js +++ b/src/tests/util.js @@ -39,6 +39,8 @@ export default { }, m2m: { [M2M_SCOPES.CONNECT_PROJECT_ADMIN]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOmNvbm5lY3RfcHJvamVjdCIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.q34b2IC1pw3ksl5RtnSEW5_HGwN0asx2MD3LV9-Wffg', + // TODO update token to have correct scope, as of now it is copied from CONNECT_PROJECT_ADMIN + [M2M_SCOPES.PROJECTS.READ_BILLING_ACCOUNTS]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOmNvbm5lY3RfcHJvamVjdCIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.q34b2IC1pw3ksl5RtnSEW5_HGwN0asx2MD3LV9-Wffg', [M2M_SCOPES.PROJECTS.ALL]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOnByb2plY3RzIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.ixFXMCsBmIN9mQ9Z3s-Apkg20A3d86Pm9RouL7bZMV4', [M2M_SCOPES.PROJECTS.READ]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoicmVhZDpwcm9qZWN0cyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.IpYgfbem-eR6tGjBoxQBPDw6YIulBTZLBn48NuyJT_g', [M2M_SCOPES.PROJECTS.WRITE]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoid3JpdGU6cHJvamVjdHMiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.cAMbmnSKXB8Xl4s4Nlo1LduPySBcvKz2Ygilq5b0OD0', From f986af1bd57aa45d935da718457b65df5bcbff29 Mon Sep 17 00:00:00 2001 From: maxceem Date: Thu, 11 Feb 2021 10:07:05 +0200 Subject: [PATCH 2/2] fix: sfdc billing accounts - fixed permissions - renamed variables to keep them clear - fixed and implemented unit tests - fixed code for the Salesforce Service --- src/constants.js | 4 +- src/permissions/constants.js | 15 +++-- src/routes/billingAccounts/list.spec.js | 73 +++++++++++++++++++++---- src/services/salesforceService.js | 2 +- src/tests/util.js | 2 +- 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/constants.js b/src/constants.js index 8b8a1f9d..6fe0011b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -274,8 +274,8 @@ export const M2M_SCOPES = { ALL: 'all:projects', READ: 'read:projects', WRITE: 'write:projects', - READ_BILLING_ACCOUNTS: 'read:user-billing-accounts', - WRITE_BILLING_ACCOUNTS: 'write:projects-billing-accounts', + READ_USER_BILLING_ACCOUNTS: 'read:user-billing-accounts', + WRITE_PROJECTS_BILLING_ACCOUNTS: 'write:projects-billing-accounts', }, PROJECT_MEMBERS: { ALL: 'all:project-members', diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 02c7c2ca..41b7d3be 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -96,16 +96,15 @@ const SCOPES_PROJECTS_WRITE = [ */ const SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS = [ M2M_SCOPES.CONNECT_PROJECT_ADMIN, - M2M_SCOPES.READ_BILLING_ACCOUNTS, - M2M_SCOPES.PROJECTS.ALL, + M2M_SCOPES.READ_USER_BILLING_ACCOUNTS, ]; /** * M2M scopes to "write" billingAccountId property */ -const SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS = [ +const SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS = [ M2M_SCOPES.CONNECT_PROJECT_ADMIN, - M2M_SCOPES.PROJECTS.WRITE_BILLING_ACCOUNTS, + M2M_SCOPES.PROJECTS.WRITE_PROJECTS_BILLING_ACCOUNTS, ]; /** @@ -240,7 +239,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ADMIN, ], - scopes: SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS, + scopes: SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS, }, DELETE_PROJECT: { @@ -270,7 +269,11 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export group: 'Project Billing Accounts', description: 'Who can view the Billing Accounts available for the project', }, - topcoderRoles: ALL, + projectRoles: [ + ...PROJECT_ROLES_MANAGEMENT, + PROJECT_MEMBER_ROLE.COPILOT, + ], + topcoderRoles: TOPCODER_ROLES_ADMINS, scopes: SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS, }, diff --git a/src/routes/billingAccounts/list.spec.js b/src/routes/billingAccounts/list.spec.js index 1bf4144f..8b0bd316 100644 --- a/src/routes/billingAccounts/list.spec.js +++ b/src/routes/billingAccounts/list.spec.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-expressions */ -// import chai from 'chai'; +import chai from 'chai'; import request from 'supertest'; import sinon from 'sinon'; @@ -8,7 +8,24 @@ import server from '../../app'; import testUtil from '../../tests/util'; import SalesforceService from '../../services/salesforceService'; -// const should = chai.should(); +chai.should(); + +// demo data which might be returned by the `SalesforceService.query` +const billingAccountsData = [ + { + sfBillingAccountId: 123, + tcBillingAccountId: 123123, + name: 'Billing Account 1', + startDate: '2021-02-10T18:51:27Z', + endDate: '2021-03-10T18:51:27Z', + }, { + sfBillingAccountId: 456, + tcBillingAccountId: 456456, + name: 'Billing Account 2', + startDate: '2011-02-10T18:51:27Z', + endDate: '2011-03-10T18:51:27Z', + }, +]; describe('Project Billing Accounts list', () => { let project1; @@ -54,10 +71,7 @@ describe('Project Billing Accounts list', () => { accessToken: 'mock', instanceUrl: 'mock_url', })); - salesforceQuery = sinon.stub(SalesforceService, 'query', () => Promise.resolve([{ - accessToken: 'mock', - instanceUrl: 'mock_url', - }])); + salesforceQuery = sinon.stub(SalesforceService, 'query', () => Promise.resolve(billingAccountsData)); done(); }); }); @@ -80,17 +94,48 @@ describe('Project Billing Accounts list', () => { .expect(403, done); }); - it('should return 403 for a regular user who is not a member of the project', (done) => { + it('should return 403 for a customer user who is a member of the project', (done) => { request(server) .get(`/v5/projects/${project1.id}/billingAccounts`) .set({ - Authorization: `Bearer ${testUtil.jwts.member2}`, + Authorization: `Bearer ${testUtil.jwts.member}`, }) .send() .expect(403, done); }); - it('should return all attachments to admin', (done) => { + it('should return 403 for a topcoder user who is not a member of the project', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilotManager}`, + }) + .send() + .expect(403, done); + }); + + it('should return all billing accounts for a topcoder user who is a member of the project', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/billingAccounts`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send() + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + resJson.should.have.length(2); + resJson.should.include(billingAccountsData[0]); + resJson.should.include(billingAccountsData[1]); + done(); + } + }); + }); + + it('should return all billing accounts to admin', (done) => { request(server) .get(`/v5/projects/${project1.id}/billingAccounts`) .set({ @@ -104,13 +149,15 @@ describe('Project Billing Accounts list', () => { } else { const resJson = res.body; resJson.should.have.length(2); - // TODO verify BA fields + resJson.should.have.length(2); + resJson.should.include(billingAccountsData[0]); + resJson.should.include(billingAccountsData[1]); done(); } }); }); - xit('should return all attachments using M2M token with "read:user-billing-accounts" scope', (done) => { + it('should return all billing accounts using M2M token with "read:user-billing-accounts" scope', (done) => { request(server) .get(`/v5/projects/${project1.id}/billingAccounts`) .set({ @@ -124,7 +171,9 @@ describe('Project Billing Accounts list', () => { } else { const resJson = res.body; resJson.should.have.length(2); - // TODO verify BA fields + resJson.should.have.length(2); + resJson.should.include(billingAccountsData[0]); + resJson.should.include(billingAccountsData[1]); done(); } }); diff --git a/src/services/salesforceService.js b/src/services/salesforceService.js index ea3f775f..8ddc283c 100644 --- a/src/services/salesforceService.js +++ b/src/services/salesforceService.js @@ -74,4 +74,4 @@ class SalesforceService { } } -export default new SalesforceService(); +export default SalesforceService; diff --git a/src/tests/util.js b/src/tests/util.js index 7a8fcf23..85a4bc64 100644 --- a/src/tests/util.js +++ b/src/tests/util.js @@ -40,7 +40,7 @@ export default { m2m: { [M2M_SCOPES.CONNECT_PROJECT_ADMIN]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOmNvbm5lY3RfcHJvamVjdCIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.q34b2IC1pw3ksl5RtnSEW5_HGwN0asx2MD3LV9-Wffg', // TODO update token to have correct scope, as of now it is copied from CONNECT_PROJECT_ADMIN - [M2M_SCOPES.PROJECTS.READ_BILLING_ACCOUNTS]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOmNvbm5lY3RfcHJvamVjdCIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.q34b2IC1pw3ksl5RtnSEW5_HGwN0asx2MD3LV9-Wffg', + [M2M_SCOPES.PROJECTS.READ_USER_BILLING_ACCOUNTS]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOmNvbm5lY3RfcHJvamVjdCIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.q34b2IC1pw3ksl5RtnSEW5_HGwN0asx2MD3LV9-Wffg', [M2M_SCOPES.PROJECTS.ALL]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoiYWxsOnByb2plY3RzIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.ixFXMCsBmIN9mQ9Z3s-Apkg20A3d86Pm9RouL7bZMV4', [M2M_SCOPES.PROJECTS.READ]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoicmVhZDpwcm9qZWN0cyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.IpYgfbem-eR6tGjBoxQBPDw6YIulBTZLBn48NuyJT_g', [M2M_SCOPES.PROJECTS.WRITE]: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoidGVzdEBjbGllbnRzIiwiYXVkIjoiaHR0cHM6Ly9tMm0udG9wY29kZXItZGV2LmNvbS8iLCJpYXQiOjE1ODc3MzI0NTksImV4cCI6MjU4NzgxODg1OSwiYXpwIjoidGVzdCIsInNjb3BlIjoid3JpdGU6cHJvamVjdHMiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.cAMbmnSKXB8Xl4s4Nlo1LduPySBcvKz2Ygilq5b0OD0',