From 3d69a54ba3f72ffd87d4c46580e256d6df239108 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Mon, 28 Mar 2022 14:40:00 +0100 Subject: [PATCH 01/29] adds code outline for adobe sign token fetching function --- .../routes/wdf/grantLetter/echoSign/index.js | 23 ++++++++ .../unit/routes/wdf/grantLetter/index.spec.js | 58 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 server/routes/wdf/grantLetter/echoSign/index.js create mode 100644 server/test/unit/routes/wdf/grantLetter/index.spec.js diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/echoSign/index.js new file mode 100644 index 0000000000..62a14ce77f --- /dev/null +++ b/server/routes/wdf/grantLetter/echoSign/index.js @@ -0,0 +1,23 @@ +const config = require('../../../../config/config'); +const axios = require('axios'); + +const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); + +module.exports.generateToken = async () => { + const body = new URLSearchParams({ + grant_type: 'authorization_code', + }); + const axiosConfig = { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }; + + return axios + .post(`${adobeSignBaseUrl}/oauth/v2/token`, body, axiosConfig) + .then((response) => { + if (!response['access_token']) throw new Error('token not returned'); + return response['access_token']; + }) + .catch((err) => { + return err; + }); +}; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js new file mode 100644 index 0000000000..deb87d6448 --- /dev/null +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -0,0 +1,58 @@ +const expect = require('chai').expect; +const sinon = require('sinon'); +const axios = require('axios'); +// const httpMocks = require('node-mocks-http'); +const config = require('../../../../../config/config'); + +const { generateToken } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); + +describe.only('GrantLetter', () => { + const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); + let axiosStub; + + beforeEach(() => { + axiosStub = sinon.stub(axios, 'post'); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('Adobe Signs Utils', () => { + it('returns the generated token from adobe sign', async () => { + axiosStub.resolves({ + access_token: 'access_token', + refresh_token: 'refresh_token', + api_access_point: 'https://api.eu2.adobesign.com/', + web_access_point: 'https://secure.eu2.adobesign.com/', + token_type: 'Bearer', + expires_in: 3600, + }); + + const token = await generateToken(); + + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(token).to.equal('access_token'); + }); + + it('returns a token error message if Adobe Sign fails to return a token', async () => { + axiosStub.resolves({ + message: 'something wrong', + }); + + const output = await generateToken(); + + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(output.message).to.equal('token not returned'); + }); + + it('returns error returned if Adobe Sign API call fails', async () => { + axiosStub.rejects(Error('some error returned')); + + const output = await generateToken(); + + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(output.message).to.equal('some error returned'); + }); + }); +}); From 0369b219daa0d79f152ccffb2e27ad2f9d6c6a9c Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Mon, 28 Mar 2022 16:42:21 +0100 Subject: [PATCH 02/29] adds scaffold for creating adobe sign agreements --- .../routes/wdf/grantLetter/echoSign/index.js | 74 +++++++++++ .../unit/routes/wdf/grantLetter/index.spec.js | 121 ++++++++++++++---- 2 files changed, 171 insertions(+), 24 deletions(-) diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/echoSign/index.js index 62a14ce77f..05e76f9369 100644 --- a/server/routes/wdf/grantLetter/echoSign/index.js +++ b/server/routes/wdf/grantLetter/echoSign/index.js @@ -21,3 +21,77 @@ module.exports.generateToken = async () => { return err; }); }; + +module.exports.createAgreement = async (claimData) => { + const { name, email, address, town, county, postcode, contractNumber, organisation, partnershipName } = claimData; + const body = { + fileInfos: [ + { + libraryDocumentId: '', + }, + ], + participantSetsInfo: [ + { + role: 'SIGNER', + order: 1, + memberInfos: [ + { + email: email, + name: name, + }, + ], + }, + ], + signatureType: 'ESIGN', + state: 'IN_PROCESS', + status: 'OUT_FOR_SIGNATURE', + name, + mergeFieldInfo: [ + { + defaultValue: name.split(' ')[0], + fieldName: 'forename', + }, + { + defaultValue: name, + fieldName: 'full_name', + }, + { + defaultValue: organisation, + fieldName: 'organisation', + }, + { + defaultValue: partnershipName, + fieldName: 'partnership_name', + }, + { + defaultValue: address, + fieldName: 'address', + }, + { + defaultValue: town, + fieldName: 'town', + }, + { + defaultValue: county, + fieldName: 'county', + }, + { + defaultValue: postcode, + fieldName: 'postcode', + }, + { + defaultValue: contractNumber, + fieldName: 'contract_number', + }, + ], + }; + + return axios + .post(`${adobeSignBaseUrl}/api/rest/v6/agreements`, body, { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }) + .then((response) => response) + .catch((err) => err); +}; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index deb87d6448..a4018036f5 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -4,7 +4,7 @@ const axios = require('axios'); // const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); -const { generateToken } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); +const { generateToken, createAgreement } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); describe.only('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); @@ -19,40 +19,113 @@ describe.only('GrantLetter', () => { }); describe('Adobe Signs Utils', () => { - it('returns the generated token from adobe sign', async () => { - axiosStub.resolves({ - access_token: 'access_token', - refresh_token: 'refresh_token', - api_access_point: 'https://api.eu2.adobesign.com/', - web_access_point: 'https://secure.eu2.adobesign.com/', - token_type: 'Bearer', - expires_in: 3600, + describe('generateToken', () => { + it('returns the generated token from adobe sign', async () => { + axiosStub.resolves({ + access_token: 'access_token', + refresh_token: 'refresh_token', + api_access_point: 'https://api.eu2.adobesign.com/', + web_access_point: 'https://secure.eu2.adobesign.com/', + token_type: 'Bearer', + expires_in: 3600, + }); + + const token = await generateToken(); + + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(token).to.equal('access_token'); }); - const token = await generateToken(); + it('returns a token error message if Adobe Sign fails to return a token', async () => { + axiosStub.resolves({ + message: 'something wrong', + }); - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(token).to.equal('access_token'); - }); + const output = await generateToken(); - it('returns a token error message if Adobe Sign fails to return a token', async () => { - axiosStub.resolves({ - message: 'something wrong', + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(output.message).to.equal('token not returned'); }); - const output = await generateToken(); + it('returns error returned if Adobe Sign API call fails', async () => { + axiosStub.rejects(Error('some error returned')); - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(output.message).to.equal('token not returned'); + const output = await generateToken(); + + expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); + expect(output.message).to.equal('some error returned'); + }); }); - it('returns error returned if Adobe Sign API call fails', async () => { - axiosStub.rejects(Error('some error returned')); + describe('createAgreement', () => { + const data = { + name: 'name', + email: 'email', + address: 'address', + town: 'town', + county: 'county', + postcode: 'postcode', + contractNumber: 'contractNumber', + organisation: 'org', + partnershipName: 'partnership', + }; + it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement', async () => { + axiosStub.resolves({ id: 'an-id-goes-here' }); - const output = await generateToken(); + const output = await createAgreement(data); - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(output.message).to.equal('some error returned'); + expect(output).to.eql({ id: 'an-id-goes-here' }); + expect(axiosStub).to.be.calledOnceWithExactly( + `${adobeSignBaseUrl}/api/rest/v6/agreements`, + { + fileInfos: [ + { + libraryDocumentId: 'CBJCHBCAABAAWaloyEh2SuKJ7y-NFqAe7CwlouyqBFjj', + }, + ], + participantSetsInfo: [ + { + role: 'SIGNER', + order: 1, + memberInfos: [ + { + email: 'email', + name: 'name', + }, + ], + }, + ], + signatureType: 'ESIGN', + state: 'IN_PROCESS', + status: 'OUT_FOR_SIGNATURE', + name: 'name', + mergeFieldInfo: [ + { defaultValue: 'name', fieldName: 'forename' }, + { defaultValue: 'name', fieldName: 'full_name' }, + { defaultValue: 'org', fieldName: 'organisation' }, + { defaultValue: 'partnership', fieldName: 'partnership_name' }, + { defaultValue: 'address', fieldName: 'address' }, + { defaultValue: 'town', fieldName: 'town' }, + { defaultValue: 'county', fieldName: 'county' }, + { defaultValue: 'postcode', fieldName: 'postcode' }, + { defaultValue: 'contractNumber', fieldName: 'contract_number' }, + ], + }, + { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }, + ); + }); + + it('returns an error if Adobe Sign rejects request', async () => { + axiosStub.rejects(Error('something went wrong')); + + const output = await createAgreement(data); + expect(output).to.be.an('Error'); + expect(output.message).to.equal('something went wrong'); + }); }); }); }); From 475d09fbdb5cae3c532cdd660f6e9eab4347ef88 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 11:14:19 +0100 Subject: [PATCH 03/29] adds national org boolean to determine the grant letter template: --- .../routes/wdf/grantLetter/echoSign/index.js | 4 +- .../unit/routes/wdf/grantLetter/index.spec.js | 101 ++++++++++-------- 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/echoSign/index.js index 05e76f9369..9d97bccb05 100644 --- a/server/routes/wdf/grantLetter/echoSign/index.js +++ b/server/routes/wdf/grantLetter/echoSign/index.js @@ -22,12 +22,12 @@ module.exports.generateToken = async () => { }); }; -module.exports.createAgreement = async (claimData) => { +module.exports.createAgreement = async (claimData, isNationalOrg) => { const { name, email, address, town, county, postcode, contractNumber, organisation, partnershipName } = claimData; const body = { fileInfos: [ { - libraryDocumentId: '', + libraryDocumentId: config.get(`${isNationalOrg ? 'adobeSign.nationalOrgDoc' : 'adobeSign.directAccessDoc'}`), }, ], participantSetsInfo: [ diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index a4018036f5..4ae58d7302 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -69,54 +69,67 @@ describe.only('GrantLetter', () => { organisation: 'org', partnershipName: 'partnership', }; - it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement', async () => { + const expectedReturn = [ + `${adobeSignBaseUrl}/api/rest/v6/agreements`, + { + fileInfos: [ + { + libraryDocumentId: config.get('adobeSign.directAccessDoc'), + }, + ], + participantSetsInfo: [ + { + role: 'SIGNER', + order: 1, + memberInfos: [ + { + email: 'email', + name: 'name', + }, + ], + }, + ], + signatureType: 'ESIGN', + state: 'IN_PROCESS', + status: 'OUT_FOR_SIGNATURE', + name: 'name', + mergeFieldInfo: [ + { defaultValue: 'name', fieldName: 'forename' }, + { defaultValue: 'name', fieldName: 'full_name' }, + { defaultValue: 'org', fieldName: 'organisation' }, + { defaultValue: 'partnership', fieldName: 'partnership_name' }, + { defaultValue: 'address', fieldName: 'address' }, + { defaultValue: 'town', fieldName: 'town' }, + { defaultValue: 'county', fieldName: 'county' }, + { defaultValue: 'postcode', fieldName: 'postcode' }, + { defaultValue: 'contractNumber', fieldName: 'contract_number' }, + ], + }, + { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }, + ]; + it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { axiosStub.resolves({ id: 'an-id-goes-here' }); + const isNationalOrg = false; - const output = await createAgreement(data); + const output = await createAgreement(data, isNationalOrg); expect(output).to.eql({ id: 'an-id-goes-here' }); - expect(axiosStub).to.be.calledOnceWithExactly( - `${adobeSignBaseUrl}/api/rest/v6/agreements`, - { - fileInfos: [ - { - libraryDocumentId: 'CBJCHBCAABAAWaloyEh2SuKJ7y-NFqAe7CwlouyqBFjj', - }, - ], - participantSetsInfo: [ - { - role: 'SIGNER', - order: 1, - memberInfos: [ - { - email: 'email', - name: 'name', - }, - ], - }, - ], - signatureType: 'ESIGN', - state: 'IN_PROCESS', - status: 'OUT_FOR_SIGNATURE', - name: 'name', - mergeFieldInfo: [ - { defaultValue: 'name', fieldName: 'forename' }, - { defaultValue: 'name', fieldName: 'full_name' }, - { defaultValue: 'org', fieldName: 'organisation' }, - { defaultValue: 'partnership', fieldName: 'partnership_name' }, - { defaultValue: 'address', fieldName: 'address' }, - { defaultValue: 'town', fieldName: 'town' }, - { defaultValue: 'county', fieldName: 'county' }, - { defaultValue: 'postcode', fieldName: 'postcode' }, - { defaultValue: 'contractNumber', fieldName: 'contract_number' }, - ], - }, - { - headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, - }, - }, - ); + expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); + }); + + it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { + axiosStub.resolves({ id: 'an-id-goes-here' }); + expectedReturn[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); + const isNationalOrg = true; + + const output = await createAgreement(data, isNationalOrg); + + expect(output).to.eql({ id: 'an-id-goes-here' }); + expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); }); it('returns an error if Adobe Sign rejects request', async () => { From bbaa75217055509fe6223f4d5fa5f25b97f219c9 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 11:28:58 +0100 Subject: [PATCH 04/29] removes generateToken function as using key instead --- .../routes/wdf/grantLetter/echoSign/index.js | 21 +-------- .../unit/routes/wdf/grantLetter/index.spec.js | 46 ++----------------- 2 files changed, 5 insertions(+), 62 deletions(-) diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/echoSign/index.js index 9d97bccb05..e7b58bc773 100644 --- a/server/routes/wdf/grantLetter/echoSign/index.js +++ b/server/routes/wdf/grantLetter/echoSign/index.js @@ -3,25 +3,6 @@ const axios = require('axios'); const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); -module.exports.generateToken = async () => { - const body = new URLSearchParams({ - grant_type: 'authorization_code', - }); - const axiosConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }; - - return axios - .post(`${adobeSignBaseUrl}/oauth/v2/token`, body, axiosConfig) - .then((response) => { - if (!response['access_token']) throw new Error('token not returned'); - return response['access_token']; - }) - .catch((err) => { - return err; - }); -}; - module.exports.createAgreement = async (claimData, isNationalOrg) => { const { name, email, address, town, county, postcode, contractNumber, organisation, partnershipName } = claimData; const body = { @@ -92,6 +73,6 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, }, }) - .then((response) => response) + .then(({ data }) => data) .catch((err) => err); }; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 4ae58d7302..7ee0828a20 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -4,9 +4,9 @@ const axios = require('axios'); // const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); -const { generateToken, createAgreement } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); +const { createAgreement } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); -describe.only('GrantLetter', () => { +describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); let axiosStub; @@ -19,44 +19,6 @@ describe.only('GrantLetter', () => { }); describe('Adobe Signs Utils', () => { - describe('generateToken', () => { - it('returns the generated token from adobe sign', async () => { - axiosStub.resolves({ - access_token: 'access_token', - refresh_token: 'refresh_token', - api_access_point: 'https://api.eu2.adobesign.com/', - web_access_point: 'https://secure.eu2.adobesign.com/', - token_type: 'Bearer', - expires_in: 3600, - }); - - const token = await generateToken(); - - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(token).to.equal('access_token'); - }); - - it('returns a token error message if Adobe Sign fails to return a token', async () => { - axiosStub.resolves({ - message: 'something wrong', - }); - - const output = await generateToken(); - - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(output.message).to.equal('token not returned'); - }); - - it('returns error returned if Adobe Sign API call fails', async () => { - axiosStub.rejects(Error('some error returned')); - - const output = await generateToken(); - - expect(axiosStub).to.have.been.calledOnceWith(`${adobeSignBaseUrl}/oauth/v2/token`); - expect(output.message).to.equal('some error returned'); - }); - }); - describe('createAgreement', () => { const data = { name: 'name', @@ -112,7 +74,7 @@ describe.only('GrantLetter', () => { }, ]; it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { - axiosStub.resolves({ id: 'an-id-goes-here' }); + axiosStub.resolves({ data: { id: 'an-id-goes-here' } }); const isNationalOrg = false; const output = await createAgreement(data, isNationalOrg); @@ -122,7 +84,7 @@ describe.only('GrantLetter', () => { }); it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { - axiosStub.resolves({ id: 'an-id-goes-here' }); + axiosStub.resolves({ data: { id: 'an-id-goes-here' } }); expectedReturn[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); const isNationalOrg = true; From 3275c8e069513d71abb9f4591c7ecbe84148b537 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 11:57:49 +0100 Subject: [PATCH 05/29] adds adobe sign function to query an agreement by the ID --- .../routes/wdf/grantLetter/echoSign/index.js | 11 +++++ .../unit/routes/wdf/grantLetter/index.spec.js | 45 +++++++++++++++---- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/echoSign/index.js index e7b58bc773..f30b872a4c 100644 --- a/server/routes/wdf/grantLetter/echoSign/index.js +++ b/server/routes/wdf/grantLetter/echoSign/index.js @@ -76,3 +76,14 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { .then(({ data }) => data) .catch((err) => err); }; + +module.exports.queryAgreementStatus = async (agreementId) => { + return axios + .get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }) + .then(({ data }) => data) + .catch((err) => err); +}; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 7ee0828a20..f263269d79 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -4,7 +4,7 @@ const axios = require('axios'); // const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); -const { createAgreement } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); +const { createAgreement, queryAgreementStatus } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); @@ -77,9 +77,9 @@ describe('GrantLetter', () => { axiosStub.resolves({ data: { id: 'an-id-goes-here' } }); const isNationalOrg = false; - const output = await createAgreement(data, isNationalOrg); + const response = await createAgreement(data, isNationalOrg); - expect(output).to.eql({ id: 'an-id-goes-here' }); + expect(response).to.eql({ id: 'an-id-goes-here' }); expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); }); @@ -88,18 +88,47 @@ describe('GrantLetter', () => { expectedReturn[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); const isNationalOrg = true; - const output = await createAgreement(data, isNationalOrg); + const response = await createAgreement(data, isNationalOrg); - expect(output).to.eql({ id: 'an-id-goes-here' }); + expect(response).to.eql({ id: 'an-id-goes-here' }); expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); }); it('returns an error if Adobe Sign rejects request', async () => { axiosStub.rejects(Error('something went wrong')); - const output = await createAgreement(data); - expect(output).to.be.an('Error'); - expect(output.message).to.equal('something went wrong'); + const response = await createAgreement(data); + expect(response).to.be.an('Error'); + expect(response.message).to.equal('something went wrong'); + }); + }); + + describe('queryAgreementStatus', () => { + it('returns the current status of a given agreement', async () => { + axiosStub.resolves({ data: { status: 'SIGNED' } }); + + const response = await queryAgreementStatus('agreement-id'); + + expect(axiosStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }); + expect(response).to.include({ status: 'SIGNED' }); + }); + + it('returns the error from Abode Sign API if there was an issue querying agreement', async () => { + axiosStub.rejects(Error('something went wrong')); + + const response = await queryAgreementStatus('agreement-id'); + + expect(axiosStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + headers: { + Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + }, + }); + expect(response).to.be.an('Error'); + expect(response.message).to.equal('something went wrong'); }); }); }); From 7a6a943b96630c7c0dc4045cd1e22142be9cb4b2 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 12:32:32 +0100 Subject: [PATCH 06/29] renames utils folder and throws errors so they can be handled at route level --- .../{echoSign => adobeSign}/index.js | 13 +++-- .../unit/routes/wdf/grantLetter/index.spec.js | 52 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) rename server/routes/wdf/grantLetter/{echoSign => adobeSign}/index.js (88%) diff --git a/server/routes/wdf/grantLetter/echoSign/index.js b/server/routes/wdf/grantLetter/adobeSign/index.js similarity index 88% rename from server/routes/wdf/grantLetter/echoSign/index.js rename to server/routes/wdf/grantLetter/adobeSign/index.js index f30b872a4c..b843ba4cde 100644 --- a/server/routes/wdf/grantLetter/echoSign/index.js +++ b/server/routes/wdf/grantLetter/adobeSign/index.js @@ -2,6 +2,7 @@ const config = require('../../../../config/config'); const axios = require('axios'); const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); +const adobeApiKey = config.get('adobeSign.apiKey'); module.exports.createAgreement = async (claimData, isNationalOrg) => { const { name, email, address, town, county, postcode, contractNumber, organisation, partnershipName } = claimData; @@ -70,20 +71,24 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { return axios .post(`${adobeSignBaseUrl}/api/rest/v6/agreements`, body, { headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + Authorization: `Bearer ${adobeApiKey}`, }, }) .then(({ data }) => data) - .catch((err) => err); + .catch((err) => { + throw err; + }); }; module.exports.queryAgreementStatus = async (agreementId) => { return axios .get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + Authorization: `Bearer ${adobeApiKey}`, }, }) .then(({ data }) => data) - .catch((err) => err); + .catch((err) => { + throw err; + }); }; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index f263269d79..4abe7c54cb 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -8,17 +8,19 @@ const { createAgreement, queryAgreementStatus } = require('../../../../../../ser describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); - let axiosStub; + let axiosPostStub; + let axiosGetStub; beforeEach(() => { - axiosStub = sinon.stub(axios, 'post'); + axiosPostStub = sinon.stub(axios, 'post'); + axiosGetStub = sinon.stub(axios, 'get'); }); afterEach(() => { sinon.restore(); }); - describe('Adobe Signs Utils', () => { + describe('Adobe Sign Utils', () => { describe('createAgreement', () => { const data = { name: 'name', @@ -69,66 +71,72 @@ describe('GrantLetter', () => { }, { headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, }, }, ]; it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { - axiosStub.resolves({ data: { id: 'an-id-goes-here' } }); + axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); const isNationalOrg = false; const response = await createAgreement(data, isNationalOrg); expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedReturn); }); it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { - axiosStub.resolves({ data: { id: 'an-id-goes-here' } }); + axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); expectedReturn[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); const isNationalOrg = true; const response = await createAgreement(data, isNationalOrg); expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosStub).to.be.calledOnceWithExactly(...expectedReturn); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedReturn); }); it('returns an error if Adobe Sign rejects request', async () => { - axiosStub.rejects(Error('something went wrong')); - - const response = await createAgreement(data); - expect(response).to.be.an('Error'); - expect(response.message).to.equal('something went wrong'); + axiosPostStub.rejects(Error('something went wrong')); + + try { + await createAgreement(data); + } catch (err) { + expect(err).to.be.an('Error'); + expect(err.message).to.equal('something went wrong'); + } }); }); describe('queryAgreementStatus', () => { it('returns the current status of a given agreement', async () => { - axiosStub.resolves({ data: { status: 'SIGNED' } }); + axiosGetStub.resolves({ data: { status: 'SIGNED' } }); const response = await queryAgreementStatus('agreement-id'); - expect(axiosStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, }, }); expect(response).to.include({ status: 'SIGNED' }); }); it('returns the error from Abode Sign API if there was an issue querying agreement', async () => { - axiosStub.rejects(Error('something went wrong')); + axiosGetStub.rejects(Error('something went wrong')); - const response = await queryAgreementStatus('agreement-id'); + try { + await queryAgreementStatus('agreement-id'); + } catch (err) { + expect(err).to.be.an('Error'); + expect(err.message).to.equal('something went wrong'); + } - expect(axiosStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { headers: { - Authorization: `Bearer ${process.env.ADOBE_SIGN_KEY}`, + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, }, }); - expect(response).to.be.an('Error'); - expect(response.message).to.equal('something went wrong'); }); }); }); From ce86fb95870543bd2a1b373dfb42b21f08eca2a3 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 13:53:46 +0100 Subject: [PATCH 07/29] adds config details required for echo sign --- server/config/config.js | 49 +++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/server/config/config.js b/server/config/config.js index 918203caff..f93c9bdac7 100644 --- a/server/config/config.js +++ b/server/config/config.js @@ -158,20 +158,6 @@ const config = convict({ }, }, - redis: { - url: { - doc: 'The URI to redirect users to the Redis', - format: String, - default: 'redis://localhost:6379', - }, - serviceName: { - doc: 'Name of VCAP Service for Redis', - format: String, - default: 'Unknown', - env: 'REDIS_SERVICE_NAME', - }, - }, - notify: { key: { doc: 'The gov.uk notify key', @@ -629,6 +615,41 @@ const config = convict({ env: 'REDIS_SERVICE_NAME', }, }, + adobeSign: { + authBaseUrl: { + doc: 'The base URL for login into Adobe Sign', + format: String, + default: 'https://secure.eu2.adobesign.com', + }, + apiBaseUrl: { + doc: 'The base URL for adobe sign API calls', + format: String, + default: 'https://api.eu2.adobesign.com:443', + }, + directAccessDoc: { + doc: 'ID of direct access template', + format: String, + default: + { + production: 'prodDocIdHere', + }[this.env] || 'CBJCHBCAABAAWaloyEh2SuKJ7y-NFqAe7CwlouyqBFjj', + }, + nationalOrgDoc: { + doc: 'ID of national organisation template', + format: String, + default: + { + production: 'prodDocIdHere', + }[this.env] || 'CBJCHBCAABAAHk5Czg1lz5LNbIlbRgvEnc7twleqy98A', + }, + apiKey: { + doc: 'Adobe Sign API Key', + format: String, + default: '', + sensitive: true, + env: 'ADOBE_SIGN_KEY', + }, + }, }); // Load environment dependent configuration From a3ac11eff0e05ba01edfda2e7dc42cad9051bc57 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 16:22:41 +0100 Subject: [PATCH 08/29] adds migrations and model for db setup --- ...140351-createDevelopmentFundGrantsTable.js | 55 +++++++++++++++++++ server/models/developmentFundGrants.js | 50 +++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 migrations/20220329140351-createDevelopmentFundGrantsTable.js create mode 100644 server/models/developmentFundGrants.js diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js new file mode 100644 index 0000000000..fa3ac79cff --- /dev/null +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -0,0 +1,55 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable( + 'DevelopmentFundGrants', + { + AgreementID: { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true, + }, + EstablishmentID: { + type: Sequelize.INTEGER, + references: { + model: { + tableName: 'Establishment', + schema: 'cqc', + }, + key: 'EstablishmentID', + }, + unique: true, + }, + SignStatus: { + type: Sequelize.STRING(36), + allowNull: false, + }, + Receiver: { + type: Sequelize.STRING(100), + allowNull: false, + }, + DateSent: { + type: Sequelize.DATE, + allowNull: false, + default: Sequelize.NOW, + }, + DateCompleted: { + type: Sequelize.DATE, + allowNull: true, + default: null, + }, + }, + { + schema: 'cqc', + }, + ); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable({ + tableName: 'DevelopmentFundGrants', + schema: 'cqc', + }); + }, +}; diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js new file mode 100644 index 0000000000..fca8f2173f --- /dev/null +++ b/server/models/developmentFundGrants.js @@ -0,0 +1,50 @@ +'use strict'; + +module.exports = function (sequelize, DataTypes) { + const DevelopmentFundGrants = sequelize.define( + 'DevelopmentFundGrants', + { + AgreementID: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + }, + EstablishmentID: { + type: DataTypes.INTEGER, + references: { + model: { + tableName: 'Establishment', + schema: 'cqc', + }, + key: 'EstablishmentID', + }, + unique: true, + }, + SignStatus: { + type: DataTypes.STRING(36), + allowNull: false, + }, + Receiver: { + type: DataTypes.STRING(100), + allowNull: false, + }, + DateSent: { + type: DataTypes.DATE, + allowNull: false, + default: sequelize.NOW, + }, + DateCompleted: { + type: DataTypes.DATE, + allowNull: true, + default: null, + }, + }, + { + tableName: 'DevelopmentFundGrants', + createdAt: false, + updatedAt: false, + }, + ); + + return DevelopmentFundGrants; +}; From 6bb966337da827e1fb77d0d805441fc1ecaca025 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 16:23:15 +0100 Subject: [PATCH 09/29] renames folder to match db table name and removes unsed variable for adobe sign --- server/config/config.js | 5 ----- .../adobeSign/index.js | 0 2 files changed, 5 deletions(-) rename server/routes/wdf/{grantLetter => developmentFundGrants}/adobeSign/index.js (100%) diff --git a/server/config/config.js b/server/config/config.js index f93c9bdac7..83e24fa486 100644 --- a/server/config/config.js +++ b/server/config/config.js @@ -616,11 +616,6 @@ const config = convict({ }, }, adobeSign: { - authBaseUrl: { - doc: 'The base URL for login into Adobe Sign', - format: String, - default: 'https://secure.eu2.adobesign.com', - }, apiBaseUrl: { doc: 'The base URL for adobe sign API calls', format: String, diff --git a/server/routes/wdf/grantLetter/adobeSign/index.js b/server/routes/wdf/developmentFundGrants/adobeSign/index.js similarity index 100% rename from server/routes/wdf/grantLetter/adobeSign/index.js rename to server/routes/wdf/developmentFundGrants/adobeSign/index.js From bcf617d14c173ee0b165a2b027fa6a424d150980 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Tue, 29 Mar 2022 16:34:20 +0100 Subject: [PATCH 10/29] fix path in tests --- server/test/unit/routes/wdf/grantLetter/index.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 4abe7c54cb..2ca13a8b1c 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -4,7 +4,10 @@ const axios = require('axios'); // const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); -const { createAgreement, queryAgreementStatus } = require('../../../../../../server/routes/wdf/grantLetter/echoSign'); +const { + createAgreement, + queryAgreementStatus, +} = require('../../../../../../server/routes/wdf/developmentFundGrants/adobeSign'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); From 21dc04563832f2986e8992a74a82efa6a1db8fb5 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Wed, 30 Mar 2022 11:29:07 +0100 Subject: [PATCH 11/29] updates variables passed and tests associated with updates --- .../wdf/developmentFundGrants/adobeSign/index.js | 16 ++++++---------- .../unit/routes/wdf/grantLetter/index.spec.js | 9 ++++----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/server/routes/wdf/developmentFundGrants/adobeSign/index.js b/server/routes/wdf/developmentFundGrants/adobeSign/index.js index b843ba4cde..77b6714362 100644 --- a/server/routes/wdf/developmentFundGrants/adobeSign/index.js +++ b/server/routes/wdf/developmentFundGrants/adobeSign/index.js @@ -4,8 +4,8 @@ const axios = require('axios'); const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); const adobeApiKey = config.get('adobeSign.apiKey'); -module.exports.createAgreement = async (claimData, isNationalOrg) => { - const { name, email, address, town, county, postcode, contractNumber, organisation, partnershipName } = claimData; +module.exports.createAgreement = async (claimData) => { + const { name, email, address, town, county, postcode, organisation, isNationalOrg } = claimData; const body = { fileInfos: [ { @@ -18,8 +18,8 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { order: 1, memberInfos: [ { - email: email, - name: name, + email, + name, }, ], }, @@ -27,7 +27,7 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { signatureType: 'ESIGN', state: 'IN_PROCESS', status: 'OUT_FOR_SIGNATURE', - name, + name: 'Workplace Development Fund Grant Letter', // title of the agreement mergeFieldInfo: [ { defaultValue: name.split(' ')[0], @@ -41,10 +41,6 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { defaultValue: organisation, fieldName: 'organisation', }, - { - defaultValue: partnershipName, - fieldName: 'partnership_name', - }, { defaultValue: address, fieldName: 'address', @@ -62,7 +58,7 @@ module.exports.createAgreement = async (claimData, isNationalOrg) => { fieldName: 'postcode', }, { - defaultValue: contractNumber, + defaultValue: 'contractNumber', fieldName: 'contract_number', }, ], diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 2ca13a8b1c..602ff32420 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -34,7 +34,7 @@ describe('GrantLetter', () => { postcode: 'postcode', contractNumber: 'contractNumber', organisation: 'org', - partnershipName: 'partnership', + isNationalOrg: true, }; const expectedReturn = [ `${adobeSignBaseUrl}/api/rest/v6/agreements`, @@ -59,12 +59,11 @@ describe('GrantLetter', () => { signatureType: 'ESIGN', state: 'IN_PROCESS', status: 'OUT_FOR_SIGNATURE', - name: 'name', + name: 'Workplace Development Fund Grant Letter', mergeFieldInfo: [ { defaultValue: 'name', fieldName: 'forename' }, { defaultValue: 'name', fieldName: 'full_name' }, { defaultValue: 'org', fieldName: 'organisation' }, - { defaultValue: 'partnership', fieldName: 'partnership_name' }, { defaultValue: 'address', fieldName: 'address' }, { defaultValue: 'town', fieldName: 'town' }, { defaultValue: 'county', fieldName: 'county' }, @@ -79,10 +78,10 @@ describe('GrantLetter', () => { }, ]; it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { + const dataCopy = { ...data, isNationalOrg: false }; axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); - const isNationalOrg = false; - const response = await createAgreement(data, isNationalOrg); + const response = await createAgreement(dataCopy); expect(response).to.eql({ id: 'an-id-goes-here' }); expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedReturn); From 1e386ff414bc5a2a89bb39f18badc886d9aaece0 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Wed, 30 Mar 2022 13:26:35 +0100 Subject: [PATCH 12/29] removes forename from adobe sign payload --- server/routes/wdf/developmentFundGrants/adobeSign/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/routes/wdf/developmentFundGrants/adobeSign/index.js b/server/routes/wdf/developmentFundGrants/adobeSign/index.js index 77b6714362..2c482362e7 100644 --- a/server/routes/wdf/developmentFundGrants/adobeSign/index.js +++ b/server/routes/wdf/developmentFundGrants/adobeSign/index.js @@ -29,10 +29,6 @@ module.exports.createAgreement = async (claimData) => { status: 'OUT_FOR_SIGNATURE', name: 'Workplace Development Fund Grant Letter', // title of the agreement mergeFieldInfo: [ - { - defaultValue: name.split(' ')[0], - fieldName: 'forename', - }, { defaultValue: name, fieldName: 'full_name', From b1e49d3e2394fd8e52404b627b4fd239fa9be9a6 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Wed, 30 Mar 2022 15:36:39 +0100 Subject: [PATCH 13/29] adds endpoint routing for wdf grant letters --- .../routes/wdf/developmentFundGrants/index.js | 19 +++++++++++++++++++ server/routes/wdf/index.js | 2 ++ 2 files changed, 21 insertions(+) create mode 100644 server/routes/wdf/developmentFundGrants/index.js diff --git a/server/routes/wdf/developmentFundGrants/index.js b/server/routes/wdf/developmentFundGrants/index.js new file mode 100644 index 0000000000..1ca49d502a --- /dev/null +++ b/server/routes/wdf/developmentFundGrants/index.js @@ -0,0 +1,19 @@ +const router = require('express').Router(); +const { queryAgreementStatus } = require('./adobeSign'); + +router.post('/agreements', require('./generateDevelopmentFundGrantLetter')); + +router.get('/agreements/:agreementId', async (req, res, next) => { + const { agreementId } = req.params; + try { + const agreementStatus = await queryAgreementStatus(agreementId); + + // store update status in db + + return res.json(agreementStatus); + } catch (err) { + return next(Error(`unable to query agreement with ID ${agreementId}`)); + } +}); + +module.exports = router; diff --git a/server/routes/wdf/index.js b/server/routes/wdf/index.js index 9cb4bf42e5..1c398436d3 100644 --- a/server/routes/wdf/index.js +++ b/server/routes/wdf/index.js @@ -3,7 +3,9 @@ const express = require('express'); const router = express.Router(); const DisbursementFile = require('./disbursementFile'); +const DevelopmentFund = require('./developmentFundGrants'); router.use('/disbursementFile', DisbursementFile); +router.use('/developmentFund', DevelopmentFund); module.exports = router; From 0df0dc6a621a0a9cd3620e6bfb6f79797c66eec3 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Wed, 30 Mar 2022 15:37:05 +0100 Subject: [PATCH 14/29] adds model for fetching WDF data --- server/models/establishment.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/models/establishment.js b/server/models/establishment.js index e7e2d5f816..57841b0c5d 100644 --- a/server/models/establishment.js +++ b/server/models/establishment.js @@ -1931,5 +1931,14 @@ module.exports = function (sequelize, DataTypes) { }); }; + Establishment.getWDFClaimData = async function (establishmentId) { + return await this.findOne({ + attributes: ['NameValue', 'address1', 'town', 'county', 'postcode', 'IsNationalOrg'], + where: { + id: establishmentId, + }, + }); + }; + return Establishment; }; From b4d4bb691e3670788c3bba1f921e970f3ecb0b6c Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Wed, 30 Mar 2022 15:37:30 +0100 Subject: [PATCH 15/29] adds route handler for generating WDF grant letter --- .../generateDevelopmentFundGrantLetter.js | 29 ++++++++ .../unit/routes/wdf/grantLetter/index.spec.js | 69 ++++++++++++++----- 2 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js new file mode 100644 index 0000000000..6739fd34b7 --- /dev/null +++ b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js @@ -0,0 +1,29 @@ +const models = require('../../../models'); +const { createAgreement } = require('./adobeSign'); + +const generateDevelopmentFundGrantLetter = async (req, res, next) => { + try { + const { establishmentId, name, email } = req.body; + const { NameValue, address1, town, county, postcode, IsNationalOrg } = await models.establishment.getWDFClaimData( + establishmentId, + ); + // additional fields needed - funding_amount, grant_reference + const { id } = await createAgreement({ + name, + email, + organisation: NameValue, + address: address1, + town, + county, + postcode, + isNationalOrg: IsNationalOrg, + }); + // save to DB - success then continue, else cancel + + return res.status(201).json({ id }); + } catch (err) { + return next(Error('unable to create agreement')); + } +}; + +module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 602ff32420..a09c9aee78 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -1,29 +1,68 @@ const expect = require('chai').expect; const sinon = require('sinon'); const axios = require('axios'); -// const httpMocks = require('node-mocks-http'); +const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); +const models = require('../../../../../models'); const { createAgreement, queryAgreementStatus, } = require('../../../../../../server/routes/wdf/developmentFundGrants/adobeSign'); +const { + generateDevelopmentFundGrantLetter, +} = require('../../../../../../server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); - let axiosPostStub; - let axiosGetStub; - beforeEach(() => { - axiosPostStub = sinon.stub(axios, 'post'); - axiosGetStub = sinon.stub(axios, 'get'); - }); + describe('generateDevelopmentFundGrant', () => { + let axiosStub; + sinon.stub(models.establishment, 'getWDFClaimData').returns({ + address1: 'address', + town: 'town', + county: 'county', + postcode: 'postcode', + NameValue: 'org', + IsNationalOrg: true, + }); + + beforeEach(() => { + axiosStub = sinon.stub(axios, 'post'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('returns a 201 with an agreementId if agreement is successfully created', async () => { + axiosStub.resolves({ data: { id: 'someid' } }); + const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); + const res = httpMocks.createResponse(); + const next = sinon.fake(); + await generateDevelopmentFundGrantLetter(req, res, next); - afterEach(() => { - sinon.restore(); + // TODO check the createAgreement is called appropriately + + // TODO check db saving data + + expect(res.statusCode).to.equal(201); + expect(res._getJSONData()).to.eql({ id: 'someid' }); + }); }); describe('Adobe Sign Utils', () => { + let axiosPostStub; + let axiosGetStub; + + beforeEach(() => { + axiosPostStub = sinon.stub(axios, 'post'); + axiosGetStub = sinon.stub(axios, 'get'); + }); + + afterEach(() => { + sinon.restore(); + }); describe('createAgreement', () => { const data = { name: 'name', @@ -36,7 +75,7 @@ describe('GrantLetter', () => { organisation: 'org', isNationalOrg: true, }; - const expectedReturn = [ + const expectedAxiosCall = [ `${adobeSignBaseUrl}/api/rest/v6/agreements`, { fileInfos: [ @@ -61,7 +100,6 @@ describe('GrantLetter', () => { status: 'OUT_FOR_SIGNATURE', name: 'Workplace Development Fund Grant Letter', mergeFieldInfo: [ - { defaultValue: 'name', fieldName: 'forename' }, { defaultValue: 'name', fieldName: 'full_name' }, { defaultValue: 'org', fieldName: 'organisation' }, { defaultValue: 'address', fieldName: 'address' }, @@ -84,18 +122,17 @@ describe('GrantLetter', () => { const response = await createAgreement(dataCopy); expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedReturn); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); }); it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); - expectedReturn[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); - const isNationalOrg = true; + expectedAxiosCall[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); - const response = await createAgreement(data, isNationalOrg); + const response = await createAgreement(data); expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedReturn); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); }); it('returns an error if Adobe Sign rejects request', async () => { From 21bfebc0eaf9f7b9133f4e3023f30f61619438bc Mon Sep 17 00:00:00 2001 From: ZuhalAb Date: Thu, 31 Mar 2022 10:48:23 +0100 Subject: [PATCH 16/29] update the migration and model Co-authored-by: M Daley --- .../20220329140351-createDevelopmentFundGrantsTable.js | 8 ++++++-- server/models/developmentFundGrants.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js index fa3ac79cff..0b46cabf1a 100644 --- a/migrations/20220329140351-createDevelopmentFundGrantsTable.js +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -22,13 +22,17 @@ module.exports = { unique: true, }, SignStatus: { - type: Sequelize.STRING(36), + type: Sequelize.STRING(40), allowNull: false, }, - Receiver: { + ReceiverName: { type: Sequelize.STRING(100), allowNull: false, }, + ReceiverEmail: { + type: Sequelize.STRING(320), + allowNull: false, + }, DateSent: { type: Sequelize.DATE, allowNull: false, diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index fca8f2173f..64f846d649 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -21,10 +21,14 @@ module.exports = function (sequelize, DataTypes) { unique: true, }, SignStatus: { - type: DataTypes.STRING(36), + type: DataTypes.STRING(40), allowNull: false, }, - Receiver: { + ReceiverEmail: { + type: DataTypes.STRING(320), + allowNull: false, + }, + ReceiverName: { type: DataTypes.STRING(100), allowNull: false, }, From 027798bcad61417a187184712bc63d0fa0df5f6f Mon Sep 17 00:00:00 2001 From: ZuhalAb Date: Thu, 31 Mar 2022 14:54:53 +0100 Subject: [PATCH 17/29] save aggrement data Co-authored-by: M Daley --- ...140351-createDevelopmentFundGrantsTable.js | 4 ++- server/models/developmentFundGrants.js | 5 +++- .../generateDevelopmentFundGrantLetter.js | 26 ++++++++++++++++--- .../routes/wdf/developmentFundGrants/index.js | 4 ++- .../unit/routes/wdf/grantLetter/index.spec.js | 26 +++++++++++++++---- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js index 0b46cabf1a..b08967a2b4 100644 --- a/migrations/20220329140351-createDevelopmentFundGrantsTable.js +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -22,8 +22,10 @@ module.exports = { unique: true, }, SignStatus: { - type: Sequelize.STRING(40), + type: Sequelize.ENUM, allowNull: false, + default: 'SENT', + values: ['SENT', 'COMPLETED', 'CANCELLED'], }, ReceiverName: { type: Sequelize.STRING(100), diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index 64f846d649..ef46e7c780 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -21,8 +21,10 @@ module.exports = function (sequelize, DataTypes) { unique: true, }, SignStatus: { - type: DataTypes.STRING(40), + type: DataTypes.ENUM, allowNull: false, + default: 'SENT', + values: ['SENT', 'COMPLETED', 'CANCELLED'], }, ReceiverEmail: { type: DataTypes.STRING(320), @@ -47,6 +49,7 @@ module.exports = function (sequelize, DataTypes) { tableName: 'DevelopmentFundGrants', createdAt: false, updatedAt: false, + schema: 'cqc', }, ); diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js index 6739fd34b7..4a8cf60a2b 100644 --- a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js +++ b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js @@ -1,5 +1,8 @@ +const express = require('express'); +const router = express.Router({ mergeParams: true }); const models = require('../../../models'); -const { createAgreement } = require('./adobeSign'); +const DevelopmentFundGrants = require('../../../models/developmentFundGrants'); +const { createAgreement, queryAgreementStatus } = require('./adobeSign'); const generateDevelopmentFundGrantLetter = async (req, res, next) => { try { @@ -8,7 +11,7 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { establishmentId, ); // additional fields needed - funding_amount, grant_reference - const { id } = await createAgreement({ + const { id: agreementId } = await createAgreement({ name, email, organisation: NameValue, @@ -18,12 +21,29 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { postcode, isNationalOrg: IsNationalOrg, }); + // check sent date/time and signStatus + const data = await queryAgreementStatus(agreementId); + const statusMap = { + OUT_FOR_SIGNATURE: 'SENT', + OUT_FOR_DELIVERY: 'SENT', + }; // save to DB - success then continue, else cancel + await models.DevelopmentFundGrants.create({ + AgreementID: agreementId, + EstablishmentID: establishmentId, + ReceiverEmail: email, + ReceiverName: name, + SignStatus: statusMap[data.status], + DateSent: data.createdDate, + }); - return res.status(201).json({ id }); + return res.status(201).json({ agreementId }); } catch (err) { return next(Error('unable to create agreement')); } }; +router.route('/').post(generateDevelopmentFundGrantLetter); + +module.exports = router; module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; diff --git a/server/routes/wdf/developmentFundGrants/index.js b/server/routes/wdf/developmentFundGrants/index.js index 1ca49d502a..1443594d0d 100644 --- a/server/routes/wdf/developmentFundGrants/index.js +++ b/server/routes/wdf/developmentFundGrants/index.js @@ -1,7 +1,9 @@ const router = require('express').Router(); const { queryAgreementStatus } = require('./adobeSign'); -router.post('/agreements', require('./generateDevelopmentFundGrantLetter')); +const GenerateDevelopmentFundGrantLetter = require('./generateDevelopmentFundGrantLetter'); + +router.use('/agreements', GenerateDevelopmentFundGrantLetter); router.get('/agreements/:agreementId', async (req, res, next) => { const { agreementId } = req.params; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index a09c9aee78..3e967428d3 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -12,11 +12,12 @@ const { const { generateDevelopmentFundGrantLetter, } = require('../../../../../../server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter'); +const developmentFundGrants = require('../../../../../models/developmentFundGrants'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); - describe('generateDevelopmentFundGrant', () => { + describe.only('generateDevelopmentFundGrant', () => { let axiosStub; sinon.stub(models.establishment, 'getWDFClaimData').returns({ address1: 'address', @@ -40,15 +41,30 @@ describe('GrantLetter', () => { const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); const res = httpMocks.createResponse(); const next = sinon.fake(); - await generateDevelopmentFundGrantLetter(req, res, next); - - // TODO check the createAgreement is called appropriately - // TODO check db saving data + await generateDevelopmentFundGrantLetter(req, res, next); expect(res.statusCode).to.equal(201); expect(res._getJSONData()).to.eql({ id: 'someid' }); }); + + it('save the agreement data to the relevant table', async () => { + axiosStub.resolves({ data: { id: 'id-to-save-in-the-db', status: 'OUT_FOR_DELIVERY' } }); + const dbStub = sinon.stub(models.DevelopmentFundGrants, 'create'); + // ['OUT_FOR_SIGNATURE' or 'OUT_FOR_DELIVERY' or 'OUT_FOR_ACCEPTANCE' or 'OUT_FOR_FORM_FILLING' or 'OUT_FOR_APPROVAL' or 'AUTHORING' or 'CANCELLED' or 'SIGNED' or 'APPROVED' or 'DELIVERED' or 'ACCEPTED' or 'FORM_FILLED' or 'EXPIRED' or 'ARCHIVED' or 'PREFILL' or 'WIDGET_WAITING_FOR_VERIFICATION' or 'DRAFT' or 'DOCUMENTS_NOT_YET_PROCESSED' or 'WAITING_FOR_FAXIN' or 'WAITING_FOR_VERIFICATION' or 'WAITING_FOR_NOTARIZATION'] + const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); + const res = httpMocks.createResponse(); + const next = sinon.fake(); + + console.log(dbStub.callCount); + expect(dbStub).to.be.calledWith({ + AgreementID: 'id-to-save-in-the-db', + EstablishmentID: 1234, + ReceiverEmail: 'some email', + ReceiverName: 'some name', + SignStatus: 'SENT', + }); + }); }); describe('Adobe Sign Utils', () => { From 0cda8556fb750d3db6b8871a0fe11f929d5e8d17 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Thu, 31 Mar 2022 17:07:26 +0100 Subject: [PATCH 18/29] refactors to have the method within the model to allow easier stubbing of db --- server/models/developmentFundGrants.js | 11 +++++ .../generateDevelopmentFundGrantLetter.js | 17 ++++---- .../unit/routes/wdf/grantLetter/index.spec.js | 41 ++++++++++++++----- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index ef46e7c780..2c3fec4c1c 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -53,5 +53,16 @@ module.exports = function (sequelize, DataTypes) { }, ); + DevelopmentFundGrants.saveWDFData = function (data) { + return this.create({ + AgreementID: data.agreementId, + EstablishmentID: data.establishmentId, + ReceiverEmail: data.email, + ReceiverName: data.name, + SignStatus: data.signStatus, + DateSent: data.createdDate, + }); + }; + return DevelopmentFundGrants; }; diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js index 4a8cf60a2b..e7dd208440 100644 --- a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js +++ b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js @@ -1,7 +1,6 @@ const express = require('express'); const router = express.Router({ mergeParams: true }); const models = require('../../../models'); -const DevelopmentFundGrants = require('../../../models/developmentFundGrants'); const { createAgreement, queryAgreementStatus } = require('./adobeSign'); const generateDevelopmentFundGrantLetter = async (req, res, next) => { @@ -27,14 +26,14 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { OUT_FOR_SIGNATURE: 'SENT', OUT_FOR_DELIVERY: 'SENT', }; - // save to DB - success then continue, else cancel - await models.DevelopmentFundGrants.create({ - AgreementID: agreementId, - EstablishmentID: establishmentId, - ReceiverEmail: email, - ReceiverName: name, - SignStatus: statusMap[data.status], - DateSent: data.createdDate, + // save to DB + await models.DevelopmentFundGrants.saveWDFData({ + agreementId, + establishmentId, + email, + name, + signStatus: statusMap[data.status], + createdDate: data.createdDate, }); return res.status(201).json({ agreementId }); diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 3e967428d3..cf63618402 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -12,13 +12,13 @@ const { const { generateDevelopmentFundGrantLetter, } = require('../../../../../../server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter'); -const developmentFundGrants = require('../../../../../models/developmentFundGrants'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); - describe.only('generateDevelopmentFundGrant', () => { - let axiosStub; + describe('generateDevelopmentFundGrant', () => { + let axiosPostStub; + let axiosGetStub; sinon.stub(models.establishment, 'getWDFClaimData').returns({ address1: 'address', town: 'town', @@ -29,7 +29,8 @@ describe('GrantLetter', () => { }); beforeEach(() => { - axiosStub = sinon.stub(axios, 'post'); + axiosPostStub = sinon.stub(axios, 'post'); + axiosGetStub = sinon.stub(axios, 'get'); }); afterEach(() => { @@ -37,7 +38,12 @@ describe('GrantLetter', () => { }); it('returns a 201 with an agreementId if agreement is successfully created', async () => { - axiosStub.resolves({ data: { id: 'someid' } }); + axiosPostStub.resolves({ data: { id: 'someid' } }); + axiosGetStub.resolves({ + data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, + }); + sinon.stub(models.DevelopmentFundGrants, 'saveWDFData'); + const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); const res = httpMocks.createResponse(); const next = sinon.fake(); @@ -45,24 +51,39 @@ describe('GrantLetter', () => { await generateDevelopmentFundGrantLetter(req, res, next); expect(res.statusCode).to.equal(201); - expect(res._getJSONData()).to.eql({ id: 'someid' }); + expect(res._getJSONData()).to.eql({ agreementId: 'someid' }); }); - it('save the agreement data to the relevant table', async () => { - axiosStub.resolves({ data: { id: 'id-to-save-in-the-db', status: 'OUT_FOR_DELIVERY' } }); + it('saves the agreement data to the relevant table via the saveWDFData method', async () => { + axiosPostStub.resolves({ data: { id: 'id-to-save-in-the-db' } }); + axiosGetStub.resolves({ + data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, + }); + const saveWDFDataStub = sinon.stub(models.DevelopmentFundGrants, 'saveWDFData').callThrough(); const dbStub = sinon.stub(models.DevelopmentFundGrants, 'create'); - // ['OUT_FOR_SIGNATURE' or 'OUT_FOR_DELIVERY' or 'OUT_FOR_ACCEPTANCE' or 'OUT_FOR_FORM_FILLING' or 'OUT_FOR_APPROVAL' or 'AUTHORING' or 'CANCELLED' or 'SIGNED' or 'APPROVED' or 'DELIVERED' or 'ACCEPTED' or 'FORM_FILLED' or 'EXPIRED' or 'ARCHIVED' or 'PREFILL' or 'WIDGET_WAITING_FOR_VERIFICATION' or 'DRAFT' or 'DOCUMENTS_NOT_YET_PROCESSED' or 'WAITING_FOR_FAXIN' or 'WAITING_FOR_VERIFICATION' or 'WAITING_FOR_NOTARIZATION'] + const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); const res = httpMocks.createResponse(); const next = sinon.fake(); - console.log(dbStub.callCount); + await generateDevelopmentFundGrantLetter(req, res, next); + + expect(saveWDFDataStub).to.be.calledWith({ + agreementId: 'id-to-save-in-the-db', + establishmentId: 1234, + email: 'some email', + name: 'some name', + signStatus: 'SENT', + createdDate: '2022-03-31T15:43:32Z', + }); + expect(dbStub).to.be.calledWith({ AgreementID: 'id-to-save-in-the-db', EstablishmentID: 1234, ReceiverEmail: 'some email', ReceiverName: 'some name', SignStatus: 'SENT', + DateSent: '2022-03-31T15:43:32Z', }); }); }); From 200c139076c360932fa7cea8f30bf23be7022109 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 09:27:35 +0100 Subject: [PATCH 19/29] updates test with method and url --- .../test/unit/routes/wdf/grantLetter/index.spec.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index cf63618402..a4f555f278 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -44,7 +44,11 @@ describe('GrantLetter', () => { }); sinon.stub(models.DevelopmentFundGrants, 'saveWDFData'); - const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); + const req = httpMocks.createRequest({ + method: 'POST', + url: '/api/wdf/developmentfund/agreements', + body: { name: 'some name', email: 'some email', establishmentId: 1234 }, + }); const res = httpMocks.createResponse(); const next = sinon.fake(); @@ -62,7 +66,11 @@ describe('GrantLetter', () => { const saveWDFDataStub = sinon.stub(models.DevelopmentFundGrants, 'saveWDFData').callThrough(); const dbStub = sinon.stub(models.DevelopmentFundGrants, 'create'); - const req = httpMocks.createRequest({ body: { name: 'some name', email: 'some email', establishmentId: 1234 } }); + const req = httpMocks.createRequest({ + method: 'POST', + url: '/api/wdf/developmentfund/agreements', + body: { name: 'some name', email: 'some email', establishmentId: 1234 }, + }); const res = httpMocks.createResponse(); const next = sinon.fake(); From 1ca898ec261d86c93ac52b7d94b97fe50244cf43 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 12:10:52 +0100 Subject: [PATCH 20/29] fixes test issue with stub --- .../unit/routes/wdf/grantLetter/index.spec.js | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index a4f555f278..c0fdaed462 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -16,25 +16,27 @@ const { describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); + afterEach(() => { + sinon.restore(); + }); + describe('generateDevelopmentFundGrant', () => { let axiosPostStub; let axiosGetStub; - sinon.stub(models.establishment, 'getWDFClaimData').returns({ - address1: 'address', - town: 'town', - county: 'county', - postcode: 'postcode', - NameValue: 'org', - IsNationalOrg: true, - }); + let saveWDFDataStub; beforeEach(() => { axiosPostStub = sinon.stub(axios, 'post'); axiosGetStub = sinon.stub(axios, 'get'); - }); - - afterEach(() => { - sinon.restore(); + saveWDFDataStub = sinon.stub(models.DevelopmentFundGrants, 'saveWDFData'); + sinon.stub(models.establishment, 'getWDFClaimData').returns({ + address1: 'address', + town: 'town', + county: 'county', + postcode: 'postcode', + NameValue: 'org', + IsNationalOrg: true, + }); }); it('returns a 201 with an agreementId if agreement is successfully created', async () => { @@ -42,7 +44,6 @@ describe('GrantLetter', () => { axiosGetStub.resolves({ data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, }); - sinon.stub(models.DevelopmentFundGrants, 'saveWDFData'); const req = httpMocks.createRequest({ method: 'POST', @@ -63,8 +64,6 @@ describe('GrantLetter', () => { axiosGetStub.resolves({ data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, }); - const saveWDFDataStub = sinon.stub(models.DevelopmentFundGrants, 'saveWDFData').callThrough(); - const dbStub = sinon.stub(models.DevelopmentFundGrants, 'create'); const req = httpMocks.createRequest({ method: 'POST', @@ -84,15 +83,6 @@ describe('GrantLetter', () => { signStatus: 'SENT', createdDate: '2022-03-31T15:43:32Z', }); - - expect(dbStub).to.be.calledWith({ - AgreementID: 'id-to-save-in-the-db', - EstablishmentID: 1234, - ReceiverEmail: 'some email', - ReceiverName: 'some name', - SignStatus: 'SENT', - DateSent: '2022-03-31T15:43:32Z', - }); }); }); @@ -105,9 +95,6 @@ describe('GrantLetter', () => { axiosGetStub = sinon.stub(axios, 'get'); }); - afterEach(() => { - sinon.restore(); - }); describe('createAgreement', () => { const data = { name: 'name', From 80ed878a04b71d158752947959232b6efdf98880 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 12:15:17 +0100 Subject: [PATCH 21/29] updates db model and migration --- ...140351-createDevelopmentFundGrantsTable.js | 34 +++++++++++++++---- server/models/developmentFundGrants.js | 26 ++++++++++++-- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js index b08967a2b4..9d0df1b548 100644 --- a/migrations/20220329140351-createDevelopmentFundGrantsTable.js +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -19,22 +19,42 @@ module.exports = { }, key: 'EstablishmentID', }, - unique: true, }, SignStatus: { type: Sequelize.ENUM, allowNull: false, - default: 'SENT', - values: ['SENT', 'COMPLETED', 'CANCELLED'], - }, - ReceiverName: { - type: Sequelize.STRING(100), - allowNull: false, + values: [ + 'OUT_FOR_SIGNATURE', + 'OUT_FOR_DELIVERY', + 'OUT_FOR_ACCEPTANCE', + 'OUT_FOR_FORM_FILLING', + 'OUT_FOR_APPROVAL', + 'AUTHORING', + 'CANCELLED', + 'SIGNED', + 'APPROVED', + 'DELIVERED', + 'ACCEPTED', + 'FORM_FILLED', + 'EXPIRED', + 'ARCHIVED', + 'PREFILL', + 'WIDGET_WAITING_FOR_VERIFICATION', + 'DRAFT', + 'DOCUMENTS_NOT_YET_PROCESSED', + 'WAITING_FOR_FAXIN', + 'WAITING_FOR_VERIFICATION', + 'WAITING_FOR_NOTARIZATION', + ], }, ReceiverEmail: { type: Sequelize.STRING(320), allowNull: false, }, + ReceiverName: { + type: Sequelize.STRING(100), + allowNull: false, + }, DateSent: { type: Sequelize.DATE, allowNull: false, diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index 2c3fec4c1c..1a82a7bb8e 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -18,13 +18,33 @@ module.exports = function (sequelize, DataTypes) { }, key: 'EstablishmentID', }, - unique: true, }, SignStatus: { type: DataTypes.ENUM, allowNull: false, - default: 'SENT', - values: ['SENT', 'COMPLETED', 'CANCELLED'], + values: [ + 'OUT_FOR_SIGNATURE', + 'OUT_FOR_DELIVERY', + 'OUT_FOR_ACCEPTANCE', + 'OUT_FOR_FORM_FILLING', + 'OUT_FOR_APPROVAL', + 'AUTHORING', + 'CANCELLED', + 'SIGNED', + 'APPROVED', + 'DELIVERED', + 'ACCEPTED', + 'FORM_FILLED', + 'EXPIRED', + 'ARCHIVED', + 'PREFILL', + 'WIDGET_WAITING_FOR_VERIFICATION', + 'DRAFT', + 'DOCUMENTS_NOT_YET_PROCESSED', + 'WAITING_FOR_FAXIN', + 'WAITING_FOR_VERIFICATION', + 'WAITING_FOR_NOTARIZATION', + ], }, ReceiverEmail: { type: DataTypes.STRING(320), From 700b7b56d1ec3bdef307d735d0a511d484566443 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 12:19:45 +0100 Subject: [PATCH 22/29] saves the Adobe Sign status rather than mapping as status is not always accurate --- .../generateDevelopmentFundGrantLetter.js | 6 +----- server/test/unit/routes/wdf/grantLetter/index.spec.js | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js index e7dd208440..6c83c1e53e 100644 --- a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js +++ b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js @@ -22,17 +22,13 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { }); // check sent date/time and signStatus const data = await queryAgreementStatus(agreementId); - const statusMap = { - OUT_FOR_SIGNATURE: 'SENT', - OUT_FOR_DELIVERY: 'SENT', - }; // save to DB await models.DevelopmentFundGrants.saveWDFData({ agreementId, establishmentId, email, name, - signStatus: statusMap[data.status], + signStatus: data.status, createdDate: data.createdDate, }); diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index c0fdaed462..5b98c39f04 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -80,7 +80,7 @@ describe('GrantLetter', () => { establishmentId: 1234, email: 'some email', name: 'some name', - signStatus: 'SENT', + signStatus: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z', }); }); From e1aa30704310cd7907314541b22534e755a8c1b3 Mon Sep 17 00:00:00 2001 From: ZuhalAb Date: Fri, 1 Apr 2022 14:17:39 +0100 Subject: [PATCH 23/29] get status from echoSign Co-authored-by: M Daley --- .../developmentFundGrants/adobeSign/index.js | 18 ++++++++---- .../generateDevelopmentFundGrantLetter.js | 28 +++++++++++++++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/server/routes/wdf/developmentFundGrants/adobeSign/index.js b/server/routes/wdf/developmentFundGrants/adobeSign/index.js index 2c482362e7..00bb4e45fc 100644 --- a/server/routes/wdf/developmentFundGrants/adobeSign/index.js +++ b/server/routes/wdf/developmentFundGrants/adobeSign/index.js @@ -73,14 +73,20 @@ module.exports.createAgreement = async (claimData) => { }; module.exports.queryAgreementStatus = async (agreementId) => { - return axios - .get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { - headers: { - Authorization: `Bearer ${adobeApiKey}`, - }, - }) + const data = await axios.get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { + headers: { + Authorization: `Bearer ${adobeApiKey}`, + }, + }); + return data.status .then(({ data }) => data) + .catch((err) => { throw err; }); }; + +const statusMap = { + OUT_FOR_SIGNATURE: 'SENT', + OUT_FOR_DELIVERY: 'SENT', +}; diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js index 4a8cf60a2b..e3510d81e1 100644 --- a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js +++ b/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js @@ -21,12 +21,7 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { postcode, isNationalOrg: IsNationalOrg, }); - // check sent date/time and signStatus - const data = await queryAgreementStatus(agreementId); - const statusMap = { - OUT_FOR_SIGNATURE: 'SENT', - OUT_FOR_DELIVERY: 'SENT', - }; + // save to DB - success then continue, else cancel await models.DevelopmentFundGrants.create({ AgreementID: agreementId, @@ -43,7 +38,28 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { } }; +const getDevelopmentFundGrant = async (req, res) => { + try { + // check sent date/time and signStatus + const data = await queryAgreementStatus(agreementId); + + console.log(); + + await this.create({ + establishmentId: req.body.establishmentId, + Status: data.status, + }); + + return res.status(200).json({}); + } catch (err) { + console.error(err); + return res.status(500).send(); + } +}; + +router.route('/:establishmentId').get(getDevelopmentFundGrant); router.route('/').post(generateDevelopmentFundGrantLetter); module.exports = router; module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; +module.exports.getDevelopmentFundGrant = getDevelopmentFundGrant; From 0e8c872fdd74581375715d40ed307f29c2dd520f Mon Sep 17 00:00:00 2001 From: ZuhalAb Date: Fri, 1 Apr 2022 15:40:05 +0100 Subject: [PATCH 24/29] move the files to the right place Co-authored-by: M Daley --- .../generateDevelopmentFundGrantLetter.js | 13 +++------- .../establishments/wdfClaims/grantLetter.js | 14 +++-------- .../routes/establishments/wdfClaims/index.js | 2 +- .../routes/wdf/developmentFundGrants/index.js | 21 ---------------- server/routes/wdf/index.js | 2 -- .../adobeSign/index.js | 25 ++++++++----------- 6 files changed, 19 insertions(+), 58 deletions(-) rename server/routes/{wdf/developmentFundGrants => establishments/wdfClaims}/generateDevelopmentFundGrantLetter.js (81%) delete mode 100644 server/routes/wdf/developmentFundGrants/index.js rename server/{routes/wdf/developmentFundGrants => utils}/adobeSign/index.js (83%) diff --git a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js similarity index 81% rename from server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js rename to server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js index 53faf9df64..0b21beebbf 100644 --- a/server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter.js +++ b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router({ mergeParams: true }); const models = require('../../../models'); -const { createAgreement, queryAgreementStatus } = require('./adobeSign'); +const { createAgreement, queryAgreementStatus } = require('../../../utils/adobeSign'); const generateDevelopmentFundGrantLetter = async (req, res, next) => { try { @@ -38,18 +38,13 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { } }; -const getDevelopmentFundGrant = async (req, res) => { +const getDevelopmentFundGrantStatus = async (req, res) => { try { // check sent date/time and signStatus const data = await queryAgreementStatus(agreementId); console.log(); - await this.create({ - establishmentId: req.body.establishmentId, - Status: data.status, - }); - return res.status(200).json({}); } catch (err) { console.error(err); @@ -57,9 +52,9 @@ const getDevelopmentFundGrant = async (req, res) => { } }; -router.route('/:establishmentId').get(getDevelopmentFundGrant); +router.route('/:establishmentId').get(getDevelopmentFundGrantStatus); router.route('/').post(generateDevelopmentFundGrantLetter); module.exports = router; module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; -module.exports.getDevelopmentFundGrant = getDevelopmentFundGrant; +module.exports.getDevelopmentFundGrantStatus = getDevelopmentFundGrantStatus; diff --git a/server/routes/establishments/wdfClaims/grantLetter.js b/server/routes/establishments/wdfClaims/grantLetter.js index 237aa9f66b..e9cf9091c5 100644 --- a/server/routes/establishments/wdfClaims/grantLetter.js +++ b/server/routes/establishments/wdfClaims/grantLetter.js @@ -1,16 +1,8 @@ const router = require('express').Router(); const { hasPermission } = require('../../../utils/security/hasPermission'); +const { generateDevelopmentFundGrantLetter } = require('./generateDevelopmentFundGrantLetter'); -const updateBUDataChanges = async (res) => { - try { - res.status(200).send(); - } catch (error) { - console.log(error); - res.status(500).send(); - } -}; - -router.route('/').post(hasPermission('canManageWdfClaims'), updateBUDataChanges); +router.route('/').post(generateDevelopmentFundGrantLetter); module.exports = router; -module.exports.updateBUDataChanges = updateBUDataChanges; +module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; diff --git a/server/routes/establishments/wdfClaims/index.js b/server/routes/establishments/wdfClaims/index.js index 4b272f79c8..44e1bc8ea3 100644 --- a/server/routes/establishments/wdfClaims/index.js +++ b/server/routes/establishments/wdfClaims/index.js @@ -4,7 +4,7 @@ const { hasPermission } = require('../../../utils/security/hasPermission'); const router = require('express').Router(); -router.use('/', hasPermission('canManageWdfClaims')); +// router.use('/', hasPermission('canManageWdfClaims')); router.use('/grantLetter', require('./grantLetter.js')); diff --git a/server/routes/wdf/developmentFundGrants/index.js b/server/routes/wdf/developmentFundGrants/index.js deleted file mode 100644 index 1443594d0d..0000000000 --- a/server/routes/wdf/developmentFundGrants/index.js +++ /dev/null @@ -1,21 +0,0 @@ -const router = require('express').Router(); -const { queryAgreementStatus } = require('./adobeSign'); - -const GenerateDevelopmentFundGrantLetter = require('./generateDevelopmentFundGrantLetter'); - -router.use('/agreements', GenerateDevelopmentFundGrantLetter); - -router.get('/agreements/:agreementId', async (req, res, next) => { - const { agreementId } = req.params; - try { - const agreementStatus = await queryAgreementStatus(agreementId); - - // store update status in db - - return res.json(agreementStatus); - } catch (err) { - return next(Error(`unable to query agreement with ID ${agreementId}`)); - } -}); - -module.exports = router; diff --git a/server/routes/wdf/index.js b/server/routes/wdf/index.js index 1c398436d3..9cb4bf42e5 100644 --- a/server/routes/wdf/index.js +++ b/server/routes/wdf/index.js @@ -3,9 +3,7 @@ const express = require('express'); const router = express.Router(); const DisbursementFile = require('./disbursementFile'); -const DevelopmentFund = require('./developmentFundGrants'); router.use('/disbursementFile', DisbursementFile); -router.use('/developmentFund', DevelopmentFund); module.exports = router; diff --git a/server/routes/wdf/developmentFundGrants/adobeSign/index.js b/server/utils/adobeSign/index.js similarity index 83% rename from server/routes/wdf/developmentFundGrants/adobeSign/index.js rename to server/utils/adobeSign/index.js index 00bb4e45fc..45d9cbcc50 100644 --- a/server/routes/wdf/developmentFundGrants/adobeSign/index.js +++ b/server/utils/adobeSign/index.js @@ -1,4 +1,4 @@ -const config = require('../../../../config/config'); +const config = require('../../config/config'); const axios = require('axios'); const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); @@ -73,20 +73,17 @@ module.exports.createAgreement = async (claimData) => { }; module.exports.queryAgreementStatus = async (agreementId) => { - const data = await axios.get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { - headers: { - Authorization: `Bearer ${adobeApiKey}`, - }, - }); - return data.status - .then(({ data }) => data) - + return await axios + .get(`${adobeSignBaseUrl}/api/rest/v6/agreements/${agreementId}`, { + headers: { + Authorization: `Bearer ${adobeApiKey}`, + }, + }) + .then(({ data }) => { + console.log(data, '<<<< adobe log'); + return data; + }) .catch((err) => { throw err; }); }; - -const statusMap = { - OUT_FOR_SIGNATURE: 'SENT', - OUT_FOR_DELIVERY: 'SENT', -}; From 9edd46dcec1af33b42e4ac5a0d022d7262111197 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 15:52:02 +0100 Subject: [PATCH 25/29] updates tests following folder restructure --- .../unit/routes/wdf/grantLetter/index.spec.js | 135 +---------------- .../test/unit/utils/adobeSign/index.spec.js | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 server/test/unit/utils/adobeSign/index.spec.js diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 5b98c39f04..dfb3eedcc5 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -2,20 +2,13 @@ const expect = require('chai').expect; const sinon = require('sinon'); const axios = require('axios'); const httpMocks = require('node-mocks-http'); -const config = require('../../../../../config/config'); const models = require('../../../../../models'); -const { - createAgreement, - queryAgreementStatus, -} = require('../../../../../../server/routes/wdf/developmentFundGrants/adobeSign'); const { generateDevelopmentFundGrantLetter, -} = require('../../../../../../server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter'); +} = require('../../../../../../server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter'); describe('GrantLetter', () => { - const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); - afterEach(() => { sinon.restore(); }); @@ -85,130 +78,4 @@ describe('GrantLetter', () => { }); }); }); - - describe('Adobe Sign Utils', () => { - let axiosPostStub; - let axiosGetStub; - - beforeEach(() => { - axiosPostStub = sinon.stub(axios, 'post'); - axiosGetStub = sinon.stub(axios, 'get'); - }); - - describe('createAgreement', () => { - const data = { - name: 'name', - email: 'email', - address: 'address', - town: 'town', - county: 'county', - postcode: 'postcode', - contractNumber: 'contractNumber', - organisation: 'org', - isNationalOrg: true, - }; - const expectedAxiosCall = [ - `${adobeSignBaseUrl}/api/rest/v6/agreements`, - { - fileInfos: [ - { - libraryDocumentId: config.get('adobeSign.directAccessDoc'), - }, - ], - participantSetsInfo: [ - { - role: 'SIGNER', - order: 1, - memberInfos: [ - { - email: 'email', - name: 'name', - }, - ], - }, - ], - signatureType: 'ESIGN', - state: 'IN_PROCESS', - status: 'OUT_FOR_SIGNATURE', - name: 'Workplace Development Fund Grant Letter', - mergeFieldInfo: [ - { defaultValue: 'name', fieldName: 'full_name' }, - { defaultValue: 'org', fieldName: 'organisation' }, - { defaultValue: 'address', fieldName: 'address' }, - { defaultValue: 'town', fieldName: 'town' }, - { defaultValue: 'county', fieldName: 'county' }, - { defaultValue: 'postcode', fieldName: 'postcode' }, - { defaultValue: 'contractNumber', fieldName: 'contract_number' }, - ], - }, - { - headers: { - Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, - }, - }, - ]; - it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { - const dataCopy = { ...data, isNationalOrg: false }; - axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); - - const response = await createAgreement(dataCopy); - - expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); - }); - - it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { - axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); - expectedAxiosCall[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); - - const response = await createAgreement(data); - - expect(response).to.eql({ id: 'an-id-goes-here' }); - expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); - }); - - it('returns an error if Adobe Sign rejects request', async () => { - axiosPostStub.rejects(Error('something went wrong')); - - try { - await createAgreement(data); - } catch (err) { - expect(err).to.be.an('Error'); - expect(err.message).to.equal('something went wrong'); - } - }); - }); - - describe('queryAgreementStatus', () => { - it('returns the current status of a given agreement', async () => { - axiosGetStub.resolves({ data: { status: 'SIGNED' } }); - - const response = await queryAgreementStatus('agreement-id'); - - expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { - headers: { - Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, - }, - }); - expect(response).to.include({ status: 'SIGNED' }); - }); - - it('returns the error from Abode Sign API if there was an issue querying agreement', async () => { - axiosGetStub.rejects(Error('something went wrong')); - - try { - await queryAgreementStatus('agreement-id'); - } catch (err) { - expect(err).to.be.an('Error'); - expect(err.message).to.equal('something went wrong'); - } - - expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { - headers: { - Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, - }, - }); - }); - }); - }); }); diff --git a/server/test/unit/utils/adobeSign/index.spec.js b/server/test/unit/utils/adobeSign/index.spec.js new file mode 100644 index 0000000000..dea73ffd83 --- /dev/null +++ b/server/test/unit/utils/adobeSign/index.spec.js @@ -0,0 +1,137 @@ +const expect = require('chai').expect; +const sinon = require('sinon'); +const axios = require('axios'); +const config = require('../../../../config/config'); +const { createAgreement, queryAgreementStatus } = require('../../../../utils/adobeSign'); + +describe('Adobe Sign Utils', () => { + const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); + + let axiosPostStub; + let axiosGetStub; + + beforeEach(() => { + axiosPostStub = sinon.stub(axios, 'post'); + axiosGetStub = sinon.stub(axios, 'get'); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('createAgreement', () => { + const data = { + name: 'name', + email: 'email', + address: 'address', + town: 'town', + county: 'county', + postcode: 'postcode', + contractNumber: 'contractNumber', + organisation: 'org', + isNationalOrg: true, + }; + const expectedAxiosCall = [ + `${adobeSignBaseUrl}/api/rest/v6/agreements`, + { + fileInfos: [ + { + libraryDocumentId: config.get('adobeSign.directAccessDoc'), + }, + ], + participantSetsInfo: [ + { + role: 'SIGNER', + order: 1, + memberInfos: [ + { + email: 'email', + name: 'name', + }, + ], + }, + ], + signatureType: 'ESIGN', + state: 'IN_PROCESS', + status: 'OUT_FOR_SIGNATURE', + name: 'Workplace Development Fund Grant Letter', + mergeFieldInfo: [ + { defaultValue: 'name', fieldName: 'full_name' }, + { defaultValue: 'org', fieldName: 'organisation' }, + { defaultValue: 'address', fieldName: 'address' }, + { defaultValue: 'town', fieldName: 'town' }, + { defaultValue: 'county', fieldName: 'county' }, + { defaultValue: 'postcode', fieldName: 'postcode' }, + { defaultValue: 'contractNumber', fieldName: 'contract_number' }, + ], + }, + { + headers: { + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, + }, + }, + ]; + it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - Direct Access', async () => { + const dataCopy = { ...data, isNationalOrg: false }; + axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); + + const response = await createAgreement(dataCopy); + + expect(response).to.eql({ id: 'an-id-goes-here' }); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); + }); + + it('calls the adobe agreements endpoint with passed data and returns an ID for the agreement - National Organisation', async () => { + axiosPostStub.resolves({ data: { id: 'an-id-goes-here' } }); + expectedAxiosCall[1].fileInfos[0].libraryDocumentId = config.get('adobeSign.nationalOrgDoc'); + + const response = await createAgreement(data); + + expect(response).to.eql({ id: 'an-id-goes-here' }); + expect(axiosPostStub).to.be.calledOnceWithExactly(...expectedAxiosCall); + }); + + it('returns an error if Adobe Sign rejects request', async () => { + axiosPostStub.rejects(Error('something went wrong')); + + try { + await createAgreement(data); + } catch (err) { + expect(err).to.be.an('Error'); + expect(err.message).to.equal('something went wrong'); + } + }); + }); + + describe('queryAgreementStatus', () => { + it('returns the current status of a given agreement', async () => { + axiosGetStub.resolves({ data: { status: 'SIGNED' } }); + + const response = await queryAgreementStatus('agreement-id'); + + expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + headers: { + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, + }, + }); + expect(response).to.include({ status: 'SIGNED' }); + }); + + it('returns the error from Abode Sign API if there was an issue querying agreement', async () => { + axiosGetStub.rejects(Error('something went wrong')); + + try { + await queryAgreementStatus('agreement-id'); + } catch (err) { + expect(err).to.be.an('Error'); + expect(err.message).to.equal('something went wrong'); + } + + expect(axiosGetStub).to.be.calledOnceWithExactly(`${adobeSignBaseUrl}/api/rest/v6/agreements/agreement-id`, { + headers: { + Authorization: `Bearer ${config.get('adobeSign.apiKey')}`, + }, + }); + }); + }); +}); From 763a0ec8a95f7f41185d7a64d4d6cfe8c7af8af0 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Fri, 1 Apr 2022 16:01:33 +0100 Subject: [PATCH 26/29] removes complete date from migrations and drops type on down migration --- ...20220329140351-createDevelopmentFundGrantsTable.js | 11 ++++------- server/models/developmentFundGrants.js | 5 ----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js index 9d0df1b548..6bfb4b9e6e 100644 --- a/migrations/20220329140351-createDevelopmentFundGrantsTable.js +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -60,11 +60,6 @@ module.exports = { allowNull: false, default: Sequelize.NOW, }, - DateCompleted: { - type: Sequelize.DATE, - allowNull: true, - default: null, - }, }, { schema: 'cqc', @@ -72,10 +67,12 @@ module.exports = { ); }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable({ + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable({ tableName: 'DevelopmentFundGrants', schema: 'cqc', }); + + return queryInterface.sequelize.query('DROP TYPE IF EXISTS cqc."enum_DevelopmentFundGrants_SignStatus";'); }, }; diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index 1a82a7bb8e..b64aeb7679 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -59,11 +59,6 @@ module.exports = function (sequelize, DataTypes) { allowNull: false, default: sequelize.NOW, }, - DateCompleted: { - type: DataTypes.DATE, - allowNull: true, - default: null, - }, }, { tableName: 'DevelopmentFundGrants', From 38b855138019deafa81403e0b74f0eec72d309e2 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Mon, 4 Apr 2022 10:32:55 +0100 Subject: [PATCH 27/29] changes date sent to date created due to adobe sign meta data available --- migrations/20220329140351-createDevelopmentFundGrantsTable.js | 2 +- server/models/developmentFundGrants.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/migrations/20220329140351-createDevelopmentFundGrantsTable.js b/migrations/20220329140351-createDevelopmentFundGrantsTable.js index 6bfb4b9e6e..8b0578620b 100644 --- a/migrations/20220329140351-createDevelopmentFundGrantsTable.js +++ b/migrations/20220329140351-createDevelopmentFundGrantsTable.js @@ -55,7 +55,7 @@ module.exports = { type: Sequelize.STRING(100), allowNull: false, }, - DateSent: { + DateCreated: { type: Sequelize.DATE, allowNull: false, default: Sequelize.NOW, diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index b64aeb7679..99e1ad98db 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -54,7 +54,7 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.STRING(100), allowNull: false, }, - DateSent: { + DateCreated: { type: DataTypes.DATE, allowNull: false, default: sequelize.NOW, @@ -75,7 +75,7 @@ module.exports = function (sequelize, DataTypes) { ReceiverEmail: data.email, ReceiverName: data.name, SignStatus: data.signStatus, - DateSent: data.createdDate, + DateCreated: data.createdDate, }); }; From 64bbe0e800f6be43b777b5044c81d50c626ca2e0 Mon Sep 17 00:00:00 2001 From: ZuhalAb Date: Mon, 4 Apr 2022 12:41:32 +0100 Subject: [PATCH 28/29] get the echoSign status and save it to the database after comparing with database value --- server/models/developmentFundGrants.js | 22 ++++++++++++++++ .../generateDevelopmentFundGrantLetter.js | 25 ++++++++++++------- .../establishments/wdfClaims/grantLetter.js | 7 +++++- .../unit/routes/wdf/grantLetter/index.spec.js | 11 +++----- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/server/models/developmentFundGrants.js b/server/models/developmentFundGrants.js index 1a82a7bb8e..e45128e635 100644 --- a/server/models/developmentFundGrants.js +++ b/server/models/developmentFundGrants.js @@ -84,5 +84,27 @@ module.exports = function (sequelize, DataTypes) { }); }; + DevelopmentFundGrants.getWDFClaimStatus = async function (establishmentId) { + return await this.findOne({ + attributes: ['AgreementID', 'SignStatus'], + where: { + EstablishmentID: establishmentId, + }, + }); + }; + + DevelopmentFundGrants.updateStatus = async function (establishmentId, status) { + return await this.update( + { + SignStatus: status, + }, + { + where: { + EstablishmentID: establishmentId, + }, + }, + ); + }; + return DevelopmentFundGrants; }; diff --git a/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js index 0b21beebbf..5a395f9554 100644 --- a/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js +++ b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js @@ -20,8 +20,6 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { postcode, isNationalOrg: IsNationalOrg, }); - // check sent date/time and signStatus - const data = await queryAgreementStatus(agreementId); // save to DB await models.DevelopmentFundGrants.saveWDFData({ agreementId, @@ -38,21 +36,30 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { } }; -const getDevelopmentFundGrantStatus = async (req, res) => { +const getDevelopmentFundGrantStatus = async (req, res, next) => { try { - // check sent date/time and signStatus - const data = await queryAgreementStatus(agreementId); + // get signStatus + const getWDFClaimStatus = await models.DevelopmentFundGrants.getWDFClaimStatus(req.body.establishmentId); + const data = await queryAgreementStatus(getWDFClaimStatus.AgreementID); - console.log(); + const signedStatus = getWDFClaimStatus.signStatus; + const echoSignStatus = data.status; + const returnStatus = signedStatus == 'SIGNED' ? signedStatus : echoSignStatus; - return res.status(200).json({}); + if (signedStatus != 'SIGNED') { + // update status to DB + if (signedStatus != echoSignStatus) { + await models.DevelopmentFundGrants.updateStatus(req.body.establishmentId, echoSignStatus); + } + } + return res.status(200).json({ Status: returnStatus }); } catch (err) { console.error(err); - return res.status(500).send(); + return next(Error('unable to get the status')); } }; -router.route('/:establishmentId').get(getDevelopmentFundGrantStatus); +router.route('/').get(getDevelopmentFundGrantStatus); router.route('/').post(generateDevelopmentFundGrantLetter); module.exports = router; diff --git a/server/routes/establishments/wdfClaims/grantLetter.js b/server/routes/establishments/wdfClaims/grantLetter.js index e9cf9091c5..1b03bc5acf 100644 --- a/server/routes/establishments/wdfClaims/grantLetter.js +++ b/server/routes/establishments/wdfClaims/grantLetter.js @@ -1,8 +1,13 @@ const router = require('express').Router(); const { hasPermission } = require('../../../utils/security/hasPermission'); -const { generateDevelopmentFundGrantLetter } = require('./generateDevelopmentFundGrantLetter'); +const { + generateDevelopmentFundGrantLetter, + getDevelopmentFundGrantStatus, +} = require('./generateDevelopmentFundGrantLetter'); router.route('/').post(generateDevelopmentFundGrantLetter); +router.route('/').get(getDevelopmentFundGrantStatus); module.exports = router; module.exports.generateDevelopmentFundGrantLetter = generateDevelopmentFundGrantLetter; +module.exports.getDevelopmentFundGrantStatus = getDevelopmentFundGrantStatus; diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 5b98c39f04..fbb7be5007 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -5,13 +5,10 @@ const httpMocks = require('node-mocks-http'); const config = require('../../../../../config/config'); const models = require('../../../../../models'); -const { - createAgreement, - queryAgreementStatus, -} = require('../../../../../../server/routes/wdf/developmentFundGrants/adobeSign'); +const { createAgreement, queryAgreementStatus } = require('../../../../../utils/adobeSign/index'); const { generateDevelopmentFundGrantLetter, -} = require('../../../../../../server/routes/wdf/developmentFundGrants/generateDevelopmentFundGrantLetter'); +} = require('../../../../../../server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter'); describe('GrantLetter', () => { const adobeSignBaseUrl = config.get('adobeSign.apiBaseUrl'); @@ -39,7 +36,7 @@ describe('GrantLetter', () => { }); }); - it('returns a 201 with an agreementId if agreement is successfully created', async () => { + it('returns a 200 with an agreementId if agreement is successfully created', async () => { axiosPostStub.resolves({ data: { id: 'someid' } }); axiosGetStub.resolves({ data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, @@ -55,7 +52,7 @@ describe('GrantLetter', () => { await generateDevelopmentFundGrantLetter(req, res, next); - expect(res.statusCode).to.equal(201); + expect(res.statusCode).to.equal(200); expect(res._getJSONData()).to.eql({ agreementId: 'someid' }); }); From 0e58b436e67bfc1032f03aa7431271bddcc3a664 Mon Sep 17 00:00:00 2001 From: Matthew Daley Date: Mon, 4 Apr 2022 16:18:33 +0100 Subject: [PATCH 29/29] fixes broken tests due to conflict resolution --- .../wdfClaims/generateDevelopmentFundGrantLetter.js | 2 ++ server/test/unit/routes/wdf/grantLetter/index.spec.js | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js index 5a395f9554..0b36a89bc3 100644 --- a/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js +++ b/server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter.js @@ -20,6 +20,8 @@ const generateDevelopmentFundGrantLetter = async (req, res, next) => { postcode, isNationalOrg: IsNationalOrg, }); + // check sent date/time and signStatus + const data = await queryAgreementStatus(agreementId); // save to DB await models.DevelopmentFundGrants.saveWDFData({ agreementId, diff --git a/server/test/unit/routes/wdf/grantLetter/index.spec.js b/server/test/unit/routes/wdf/grantLetter/index.spec.js index 8997fb37a7..dfb3eedcc5 100644 --- a/server/test/unit/routes/wdf/grantLetter/index.spec.js +++ b/server/test/unit/routes/wdf/grantLetter/index.spec.js @@ -4,7 +4,6 @@ const axios = require('axios'); const httpMocks = require('node-mocks-http'); const models = require('../../../../../models'); -const { createAgreement, queryAgreementStatus } = require('../../../../../utils/adobeSign/index'); const { generateDevelopmentFundGrantLetter, } = require('../../../../../../server/routes/establishments/wdfClaims/generateDevelopmentFundGrantLetter'); @@ -33,7 +32,7 @@ describe('GrantLetter', () => { }); }); - it('returns a 200 with an agreementId if agreement is successfully created', async () => { + it('returns a 201 with an agreementId if agreement is successfully created', async () => { axiosPostStub.resolves({ data: { id: 'someid' } }); axiosGetStub.resolves({ data: { status: 'OUT_FOR_SIGNATURE', createdDate: '2022-03-31T15:43:32Z' }, @@ -49,7 +48,7 @@ describe('GrantLetter', () => { await generateDevelopmentFundGrantLetter(req, res, next); - expect(res.statusCode).to.equal(200); + expect(res.statusCode).to.equal(201); expect(res._getJSONData()).to.eql({ agreementId: 'someid' }); });