From d08f9f3bab7a8caee5a3226f821da58cf8bc520e Mon Sep 17 00:00:00 2001 From: xxcxy Date: Tue, 25 Feb 2020 13:23:14 +0800 Subject: [PATCH 1/2] Add reports tests --- src/routes/projectReports/getEmbedReport.js | 4 +- .../projectReports/getEmbedReport.spec.js | 289 ++++++++++++++++ src/routes/projectReports/getReport.js | 2 +- src/routes/projectReports/getReport.spec.js | 309 ++++++++++++++++++ 4 files changed, 601 insertions(+), 3 deletions(-) create mode 100644 src/routes/projectReports/getEmbedReport.spec.js create mode 100644 src/routes/projectReports/getReport.spec.js diff --git a/src/routes/projectReports/getEmbedReport.js b/src/routes/projectReports/getEmbedReport.js index a600aa26..6f6fd9d5 100644 --- a/src/routes/projectReports/getEmbedReport.js +++ b/src/routes/projectReports/getEmbedReport.js @@ -14,7 +14,7 @@ module.exports = [ permissions('projectReporting.view'), async (req, res, next) => { const projectId = Number(req.params.projectId); - const mockReport = config.lookerConfig.USE_MOCK === 'true'; + const mockReport = config.get('lookerConfig.USE_MOCK') === 'true'; let reportName = mockReport ? 'mock' : req.query.reportName; const authUser = req.authUser; let REPORTS = null; @@ -22,7 +22,7 @@ module.exports = [ try { allowedUsers = JSON.parse(_.get(config, 'lookerConfig.ALLOWED_USERS', '[]')); req.log.trace(allowedUsers, 'allowedUsers'); - REPORTS = JSON.parse(config.lookerConfig.EMBED_REPORTS_MAPPING); + REPORTS = JSON.parse(config.get('lookerConfig.EMBED_REPORTS_MAPPING')); } catch (error) { req.log.error(error); req.log.debug('Invalid reports mapping. Should be a valid JSON.'); diff --git a/src/routes/projectReports/getEmbedReport.spec.js b/src/routes/projectReports/getEmbedReport.spec.js new file mode 100644 index 00000000..270c0198 --- /dev/null +++ b/src/routes/projectReports/getEmbedReport.spec.js @@ -0,0 +1,289 @@ +import chai from 'chai'; +import sinon from 'sinon'; +import request from 'supertest'; +import config from 'config'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import util from '../../util'; +import lookerSerivce from '../../services/lookerService'; + +const should = chai.should(); + +describe('GET embed report', () => { + let project0; + let project1; + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Project.create({ + type: 'generic', + directProjectId: 0, + billingAccountId: 0, + name: 'test0', + description: 'test project0', + status: 'reviewed', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + })) + .then((p0) => { + project0 = p0; + return models.ProjectMember.create({ + userId: 40051331, + projectId: project0.id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + }) + .then(() => models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'concrete', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + })) + .then(temp => models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'reviewed', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + templateId: temp.id, + lastActivityUserId: '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(() => models.ProjectMember.create({ + userId: 40051334, + projectId: project1.id, + role: 'manager', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + })) + .then(() => models.ProjectMember.create({ + userId: 40051331, + projectId: project1.id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + })) + .then(() => { + done(); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{id}/reports/embed', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should return 403 if user does not have permissions', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/reports/embed`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 403 if project not exist', (done) => { + request(server) + .get('/v5/projects/100100/reports/embed') + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 404 when report name not mock and not in EMBED_REPORTS_MAPPING', (done) => { + const cfg = sinon.stub(config, 'get'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports/embed?reportName=random`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(404, () => { + cfg.restore(); + done(); + }); + }); + + it('should return 500 when get admin user error', (done) => { + const cfg = sinon.stub(config, 'get'); + const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(500, () => { + gem.restore(); + cfg.restore(); + done(); + }); + }); + + it('should return 404 when the project template is not found', (done) => { + const cfg = sinon.stub(config, 'get'); + const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-customer": "/embed/looks/2"}'); + request(server) + .get(`/v5/projects/${project0.id}/reports/embed?reportName=mock`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(404, () => { + gem.restore(); + cfg.restore(); + done(); + }); + }); + + it('should return generate customer url', (done) => { + const cfg = sinon.stub(config, 'get'); + const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING') + .returns('{"mock-concrete-customer": "/customer/embed/looks/2"}'); + request(server) + .get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + gem.restore(); + cfg.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.equal('generatedUrl'); + const [user, project, member, embedUrl] = gem.lastCall.args; + user.userId.should.equal(40051331); + project.should.deep.equal({ id: project1.id }); + member.userId.should.equal(40051331); + member.role.should.equal('customer'); + embedUrl.should.equal('/customer/embed/looks/2'); + done(); + } + }); + }); + + it('should return generate admin url', (done) => { + const cfg = sinon.stub(config, 'get'); + const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); + const getAdmin = sinon.stub(util, 'getTopcoderUser', () => ({ + firstName: 'fn', + lastName: 'ln', + userId: 40051333, + })); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-topcoder": "/admin/embed/looks/2"}'); + request(server) + .get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + getAdmin.restore(); + gem.restore(); + cfg.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.equal('generatedUrl'); + const [user, project, member, embedUrl] = gem.lastCall.args; + user.userId.should.equal(40051333); + project.should.deep.equal({ id: project1.id }); + member.userId.should.equal(40051333); + member.firstName.should.equal('fn'); + member.lastName.should.equal('ln'); + member.role.should.equal(''); + embedUrl.should.equal('/admin/embed/looks/2'); + done(); + } + }); + }); + + it('should return generate copilot url', (done) => { + const cfg = sinon.stub(config, 'get'); + const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-copilot": "/copilot/embed/looks/2"}'); + request(server) + .get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + gem.restore(); + cfg.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.equal('generatedUrl'); + const [user, project, member, embedUrl] = gem.lastCall.args; + user.userId.should.equal(40051332); + project.should.deep.equal({ id: project1.id }); + member.userId.should.equal(40051332); + member.role.should.equal('copilot'); + embedUrl.should.equal('/copilot/embed/looks/2'); + done(); + } + }); + }); + }); +}); diff --git a/src/routes/projectReports/getReport.js b/src/routes/projectReports/getReport.js index fe842fd6..d306b09b 100644 --- a/src/routes/projectReports/getReport.js +++ b/src/routes/projectReports/getReport.js @@ -17,7 +17,7 @@ module.exports = [ const projectId = Number(req.params.projectId); const reportName = req.query.reportName; - if (config.lookerConfig.USE_MOCK === 'true') { + if (config.get('lookerConfig.USE_MOCK') === 'true') { req.log.info('using mock'); // using mock return mock(projectId, reportName, req, res); diff --git a/src/routes/projectReports/getReport.spec.js b/src/routes/projectReports/getReport.spec.js new file mode 100644 index 00000000..443cf41e --- /dev/null +++ b/src/routes/projectReports/getReport.spec.js @@ -0,0 +1,309 @@ +import chai from 'chai'; +import sinon from 'sinon'; +import request from 'supertest'; +import config from 'config'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const summaryJson = require('./mockFiles/summary.json'); +const projectBudget = require('./mockFiles/projectBudget.json'); +const axios = require('axios'); + +const should = chai.should(); + +describe('GET report', () => { + let project1; + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'reviewed', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '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(() => models.ProjectMember.create({ + userId: 40051334, + projectId: project1.id, + role: 'manager', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then(() => models.ProjectMember.create({ + userId: 40051331, + projectId: project1.id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then(() => { + done(); + }), + ), + ); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{id}/reports', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should return 403 if user does not have permissions', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/reports/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 403 if project not exist', (done) => { + request(server) + .get('/v5/projects/100100/reports/') + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 400 if report not exist and lookerConfig.USE_MOCK is true', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/reports/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return mock summary report when lookerConfig.USE_MOCK is true', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=summary`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal(summaryJson); + done(); + } + }); + }); + + it('should return mock projectBudget report when lookerConfig.USE_MOCK is true', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=projectBudget`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal(projectBudget); + done(); + } + }); + }); + + it('should return 404 when report name illegal', (done) => { + const cfg = sinon.stub(config, 'get'); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=random`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(404, () => { + cfg.restore(); + done(); + }); + }); + + it('should return summary report when a customer get summary report', (done) => { + const cfg = sinon.stub(config, 'get'); + const ast = sinon.stub(axios, 'post', () => Promise.resolve({ data: { report: 'summary' } })); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=summary`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + cfg.restore(); + ast.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal({ report: 'summary' }); + const accessArgs = ast.lastCall.args; + accessArgs[0].should.equal('/queries/run/json'); + accessArgs[1].id.should.equal(1234); + accessArgs[1].model.should.equal('topcoder_model_main'); + accessArgs[1].view.should.equal('challenge'); + accessArgs[1].filters.should.deep.equal({ 'connect_project.id': 1 }); + accessArgs[1].fields[0].should.equal('connect_project.id'); + accessArgs[1].fields[1].should.equal('challenge.track'); + accessArgs[1].fields[2].should.equal('challenge.num_registrations'); + accessArgs[1].fields[3].should.equal('challenge.num_submissions'); + accessArgs[1].limit.should.equal(10); + accessArgs[1].query_timezon.should.equal('America/Los_Angeles'); + done(); + } + }); + }); + + it('should return projectBudget report when a customer get projectBudget report', (done) => { + const cfg = sinon.stub(config, 'get'); + const ast = sinon.stub(axios, 'post', () => Promise.resolve({ data: { report: 'projectBudget' } })); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=projectBudget`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + cfg.restore(); + ast.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal({ report: 'projectBudget' }); + const accessArgs = ast.lastCall.args; + accessArgs[0].should.equal('/queries/run/json'); + accessArgs[1].id.should.equal(123); + accessArgs[1].model.should.equal('topcoder_model_main'); + accessArgs[1].view.should.equal('project_stream'); + accessArgs[1].filters.should.deep.equal({ 'project_stream.tc_connect_project_id': 1 }); + accessArgs[1].fields[0].should.equal('project_stream.tc_connect_project_id'); + accessArgs[1].fields[1].should.equal('project_stream.total_invoiced_amount'); + accessArgs[1].fields[2].should.equal('project_stream.remaining_invoiced_budget'); + accessArgs[1].limit.should.equal(10); + accessArgs[1].query_timezon.should.equal('America/Los_Angeles'); + done(); + } + }); + }); + + it('should return projectBudget report when a copilot get projectBudget report', (done) => { + const cfg = sinon.stub(config, 'get'); + const ast = sinon.stub(axios, 'post', () => Promise.resolve({ data: { report: 'projectBudget' } })); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=projectBudget`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + cfg.restore(); + ast.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal({ report: 'projectBudget' }); + const accessArgs = ast.lastCall.args; + accessArgs[0].should.equal('/queries/run/json'); + accessArgs[1].id.should.equal(123); + accessArgs[1].model.should.equal('topcoder_model_main'); + accessArgs[1].view.should.equal('project_stream'); + accessArgs[1].filters.should.deep.equal({ 'project_stream.tc_connect_project_id': 1 }); + accessArgs[1].fields[0].should.equal('project_stream.tc_connect_project_id'); + accessArgs[1].fields[1].should.equal('project_stream.total_actual_member_payment'); + accessArgs[1].limit.should.equal(10); + accessArgs[1].query_timezon.should.equal('America/Los_Angeles'); + done(); + } + }); + }); + + it('should return projectBudget report when an admin get projectBudget report', (done) => { + const cfg = sinon.stub(config, 'get'); + const ast = sinon.stub(axios, 'post', () => Promise.resolve({ data: { report: 'projectBudget' } })); + cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + request(server) + .get(`/v5/projects/${project1.id}/reports?reportName=projectBudget`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + cfg.restore(); + ast.restore(); + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.deep.equal({ report: 'projectBudget' }); + const accessArgs = ast.lastCall.args; + accessArgs[0].should.equal('/queries/run/json'); + accessArgs[1].id.should.equal(123); + accessArgs[1].model.should.equal('topcoder_model_main'); + accessArgs[1].view.should.equal('project_stream'); + accessArgs[1].filters.should.deep.equal({ 'project_stream.tc_connect_project_id': 1 }); + accessArgs[1].fields[0].should.equal('project_stream.tc_connect_project_id'); + accessArgs[1].fields[1].should.equal('project_stream.total_actual_challenge_fee'); + accessArgs[1].fields[2].should.equal('project_stream.total_actual_member_payment'); + accessArgs[1].fields[3].should.equal('project_stream.total_invoiced_amount'); + accessArgs[1].fields[4].should.equal('project_stream.remaining_invoiced_budget'); + accessArgs[1].limit.should.equal(10); + accessArgs[1].query_timezon.should.equal('America/Los_Angeles'); + done(); + } + }); + }); + }); +}); From 18b880be9985df0ef0df2738b43e86046770650a Mon Sep 17 00:00:00 2001 From: xxcxy Date: Tue, 25 Feb 2020 16:28:31 +0800 Subject: [PATCH 2/2] Fix reports tests --- src/routes/projectReports/getEmbedReport.spec.js | 15 +++++++-------- src/routes/projectReports/getReport.spec.js | 5 ++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/routes/projectReports/getEmbedReport.spec.js b/src/routes/projectReports/getEmbedReport.spec.js index 270c0198..d3a71e30 100644 --- a/src/routes/projectReports/getEmbedReport.spec.js +++ b/src/routes/projectReports/getEmbedReport.spec.js @@ -140,10 +140,9 @@ describe('GET embed report', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect('Content-Type', /json/) - .expect(404, () => { + .expect(404, (err) => { cfg.restore(); - done(); + done(err); }); }); @@ -151,15 +150,16 @@ describe('GET embed report', () => { const cfg = sinon.stub(config, 'get'); const gem = sinon.stub(lookerSerivce, 'generateEmbedUrl', () => 'generatedUrl'); cfg.withArgs('lookerConfig.USE_MOCK').returns(false); + cfg.withArgs('lookerConfig.EMBED_REPORTS_MAPPING').returns('{"mock-concrete-customer": "/embed/looks/2"}'); request(server) .get(`/v5/projects/${project1.id}/reports/embed?reportName=mock`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(500, () => { + .expect(500, (err) => { gem.restore(); cfg.restore(); - done(); + done(err); }); }); @@ -173,11 +173,10 @@ describe('GET embed report', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect('Content-Type', /json/) - .expect(404, () => { + .expect(404, (err) => { gem.restore(); cfg.restore(); - done(); + done(err); }); }); diff --git a/src/routes/projectReports/getReport.spec.js b/src/routes/projectReports/getReport.spec.js index 443cf41e..9bdf5d3c 100644 --- a/src/routes/projectReports/getReport.spec.js +++ b/src/routes/projectReports/getReport.spec.js @@ -153,10 +153,9 @@ describe('GET report', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect('Content-Type', /json/) - .expect(404, () => { + .expect(404, (err) => { cfg.restore(); - done(); + done(err); }); });