diff --git a/src/apps/api/controllers/__test__/postcode-lookup.test.js b/src/apps/api/controllers/__test__/postcode-lookup.test.js index 63e56593806..c52045f0aab 100644 --- a/src/apps/api/controllers/__test__/postcode-lookup.test.js +++ b/src/apps/api/controllers/__test__/postcode-lookup.test.js @@ -15,6 +15,23 @@ describe('postcodeLookupHandler', () => { '../services': { lookupAddress: (postcode) => this.lookupAddressStub(postcode), }, + '../../../lib/metadata': { + countryOptions: [ + { + id: '1234', + name: 'United Kingdom', + }, + ], + }, + }) + }) + + context('when the external api service returns successful response', () => { + beforeEach(async () => { + await this.controller.postcodeLookupHandler(this.req, this.resMock) + }) + it('should return postcode response', () => { + expect(this.resMock.json).to.be.calledWith([{ country: '1234' }]) }) }) diff --git a/src/apps/api/controllers/postcode-lookup.js b/src/apps/api/controllers/postcode-lookup.js index c7e41196ba3..8f2b994cd2f 100644 --- a/src/apps/api/controllers/postcode-lookup.js +++ b/src/apps/api/controllers/postcode-lookup.js @@ -1,8 +1,20 @@ +const { assign } = require('lodash') + const { lookupAddress } = require('../services') +const metadata = require('../../../lib/metadata') async function postcodeLookupHandler(req, res) { try { - res.json(await lookupAddress(req.params.postcode)) + const postcode = req.params.postcode + const addresses = await lookupAddress(postcode) + const unitedKingdomCountryId = metadata.countryOptions.find( + (country) => country.name.toLowerCase() === 'united kingdom' + ).id + const augmentedAddresses = addresses.map((address) => { + return assign({}, address, { country: unitedKingdomCountryId }) + }) + + res.json(augmentedAddresses) } catch (error) { res.status(error.statusCode || 500).json({ message: error.message }) } diff --git a/src/apps/investments/middleware/__test__/shared.test.js b/src/apps/investments/middleware/__test__/shared.test.js index 809cb013ce8..703c9f09fd3 100644 --- a/src/apps/investments/middleware/__test__/shared.test.js +++ b/src/apps/investments/middleware/__test__/shared.test.js @@ -1,8 +1,9 @@ -const { merge } = require('lodash') +const { merge, cloneDeep } = require('lodash') const proxyquire = require('proxyquire') const paths = require('../../paths') const investmentData = require('../../../../../test/unit/data/investment/investment-data.json') +const investmentProjectStages = require('../../../../../test/unit/data/investment/investment-project-stages.json') const companyData = { id: '6c388e5b-a098-e211-a939-e4115bead28a', @@ -65,7 +66,12 @@ const getInvestmentData = (ukCompanyId, clientRelationshipManagerId) => { }) } -const createMiddleware = (investmentData, adviserData, companyData) => { +const createMiddleware = ( + investmentData, + adviserData, + companyData, + stages = investmentProjectStages +) => { return proxyquire('../shared', { '../repos': { getInvestment: sinon.stub().resolves(investmentData), @@ -76,6 +82,9 @@ const createMiddleware = (investmentData, adviserData, companyData) => { '../../companies/repos': { getDitCompany: sinon.stub().resolves(companyData), }, + '../../../lib/metadata': { + investmentProjectStage: stages, + }, }) } @@ -146,6 +155,15 @@ describe('Investment shared middleware', () => { expectedEquityCompany ) }) + it('should set investment project stages on locals', () => { + expect(resMock.locals.investmentProjectStages).to.deep.equal([ + 'Prospect', + 'Assign PM', + 'Active', + 'Verify win', + 'Won', + ]) + }) it('should set investment status on locals', () => { expect(resMock.locals.investmentStatus).to.deep.equal(investmentStatus) }) @@ -290,5 +308,42 @@ describe('Investment shared middleware', () => { expect(nextSpy).to.have.been.called }) }) + + context( + 'when the streamlined-investment-flow feature flag is set to true', + () => { + before(async () => { + const stages = cloneDeep(investmentProjectStages) + + // Exclude the Assign PM stage. + stages[1].exclude_from_investment_flow = true + + resMock = { + breadcrumb: sinon.stub().returnsThis(), + locals: { + features: { + 'streamlined-investment-flow': true, + }, + }, + } + const middleware = createMiddleware( + getInvestmentData(2, null), + adviserData, + companyData, + stages + ) + + await middleware.getInvestmentDetails(reqMock, resMock, nextSpy) + }) + it('should remove the Assign PM stage from the investmentProjectStages array on locals', () => { + expect(resMock.locals.investmentProjectStages).to.deep.equal([ + 'Prospect', + 'Active', + 'Verify win', + 'Won', + ]) + }) + } + ) }) }) diff --git a/src/apps/investments/middleware/shared.js b/src/apps/investments/middleware/shared.js index 1a779b14e00..54024ded9a7 100644 --- a/src/apps/investments/middleware/shared.js +++ b/src/apps/investments/middleware/shared.js @@ -1,5 +1,6 @@ const { get, upperFirst } = require('lodash') +const metadata = require('../../../lib/metadata') const { isValidGuid } = require('../../../lib/controller-utils') const { getDitCompany } = require('../../companies/repos') const { getAdviser } = require('../../adviser/repos') @@ -16,6 +17,15 @@ function getCompanyDetails(req, res, next) { .catch(next) } +function getInvestmentProjectStages(features) { + if (features && features['streamlined-investment-flow']) { + return metadata.investmentProjectStage.filter( + (stage) => stage.exclude_from_investment_flow !== true + ) + } + return metadata.investmentProjectStage +} + async function getInvestmentDetails(req, res, next) { const investmentId = req.params.investmentId @@ -34,6 +44,9 @@ async function getInvestmentDetails(req, res, next) { 'client_relationship_manager.id' ) const stageName = investment.stage.name + const investmentProjectStages = getInvestmentProjectStages( + res.locals.features + ) investment.investor_company = Object.assign( {}, @@ -60,6 +73,9 @@ async function getInvestmentDetails(req, res, next) { res.locals.investment = investment res.locals.equityCompany = investment.investor_company + res.locals.investmentProjectStages = investmentProjectStages.map( + (stage) => stage.name + ) res.locals.investmentStatus = { id: investment.id, diff --git a/src/client/components/Form/elements/FieldAddress/index.jsx b/src/client/components/Form/elements/FieldAddress/index.jsx index 5533dbae96b..62ee5442849 100644 --- a/src/client/components/Form/elements/FieldAddress/index.jsx +++ b/src/client/components/Form/elements/FieldAddress/index.jsx @@ -150,6 +150,8 @@ const FieldAddress = ({ setFieldValue('address2', address.address2) setFieldValue('city', address.city) setFieldValue('county', address.county) + setFieldValue('country', isCountrySelectable ? address.country : country.id) + setFieldValue('country', country.id) if (onSelectUKAddress) { onSelectUKAddress(address) diff --git a/src/lib/__test__/metadata.test.js b/src/lib/__test__/metadata.test.js new file mode 100644 index 00000000000..9950d287c4f --- /dev/null +++ b/src/lib/__test__/metadata.test.js @@ -0,0 +1,42 @@ +const rewire = require('rewire') + +const config = require('../../config') + +const modulePath = '../metadata' + +describe('metadata', () => { + let metadata + let hawkRequest + beforeEach(() => { + metadata = rewire(modulePath) + }) + afterEach(() => { + expect(hawkRequest).to.have.been.calledOnceWith( + `${config.apiRoot}/v4/metadata/fake` + ) + }) + describe('#getMetadata', () => { + beforeEach(() => { + metadata.__set__('redisClient', null) + }) + it('gets metadata from URL', async () => { + const getMetadata = metadata.__get__('getMetadata') + + hawkRequest = sinon.stub().resolves({ fake: 'metadata' }) + metadata.__set__('hawkRequest', hawkRequest) + await getMetadata('fake', 'fakeOptions') + const { fakeOptions } = metadata.__get__('exports') + expect(fakeOptions).to.deep.equal({ fake: 'metadata' }) + }) + + it('fails to get metadata if request cannot be made', async () => { + const getMetadata = metadata.__get__('getMetadata') + + hawkRequest = sinon.stub().rejects() + metadata.__set__('hawkRequest', hawkRequest) + await expect(getMetadata('fake', 'fakeOptions')).to.be.rejectedWith(Error) + const { fakeOptions } = metadata.__get__('exports') + expect(fakeOptions).to.deep.equal(undefined) + }) + }) +}) diff --git a/src/lib/metadata.js b/src/lib/metadata.js new file mode 100644 index 00000000000..b81fb0fcf0f --- /dev/null +++ b/src/lib/metadata.js @@ -0,0 +1,86 @@ +const config = require('../config') +const logger = require('../config/logger') +const hawkRequest = require('./hawk-request') + +let redisClient + +function getMetadata(path, key) { + const url = `${config.apiRoot}/v4/metadata/${path}` + + if (redisClient) { + return new Promise((resolve, reject) => { + const ttl = config.redis.metadataTtl + + redisClient.get(url, (err, data) => { + if (!err && data) { + data = JSON.parse(data) + module.exports[key] = data + resolve(data) + } else { + hawkRequest(url) + .then((responseData) => { + module.exports[key] = responseData + redisClient.setex(url, ttl, JSON.stringify(responseData)) + resolve(responseData) + }) + .catch((reponseError) => { + logger.error('Error fetching metadataRepository for url: %s', url) + reject(reponseError) + throw reponseError + }) + } + }) + }) + } + + return hawkRequest(url) + .then((responseData) => { + module.exports[key] = responseData + return responseData + }) + .catch((err) => { + logger.error('Error fetching metadataRepository for url: %s', url) + throw err + }) +} + +const metadataItems = [ + ['country', 'countryOptions'], + ['investment-project-stage', 'investmentProjectStage'], +] + +// TODO: Get rid of this +module.exports.fetchAll = (cb) => { + // todo + // refactor to create an array of jobs to do and then use promise all + // before returning back via a promise. + + let caughtErrors + let totalRequests = 0 + let completeRequests = 0 + + function checkResults() { + completeRequests += 1 + if (completeRequests === totalRequests) { + logger.info('All metadataRepository requests complete') + cb(caughtErrors) + } + } + + for (const item of metadataItems) { + totalRequests += 1 + getMetadata(item[0], item[1]) + .then((data) => { + if (item[2]) { + item[2](data) + } + + checkResults() + }) + .catch((err) => { + caughtErrors = caughtErrors || [] + caughtErrors.push(err) + checkResults() + }) + } +} diff --git a/src/server.js b/src/server.js index 52dd97e6cca..5e71d029793 100644 --- a/src/server.js +++ b/src/server.js @@ -21,6 +21,7 @@ const nunjucks = require('./config/nunjucks') const headers = require('./middleware/headers') const locals = require('./middleware/locals') const userLocals = require('./middleware/user-locals') +const metadata = require('./lib/metadata') const user = require('./middleware/user') const auth = require('./middleware/auth') const store = require('./middleware/store') @@ -158,6 +159,16 @@ app.use(errors.notFound) app.use(errors.badRequest) app.use(errors.catchAll) -app.listen(config.port, () => { - logger.info(`app listening on port ${config.port}`) +metadata.fetchAll((errors) => { + if (errors) { + logger.error('Unable to load all metadataRepository, cannot start app') + + for (const err of errors) { + throw err + } + } else { + app.listen(config.port, () => { + logger.info(`app listening on port ${config.port}`) + }) + } }) diff --git a/test/unit/data/investment/investment-project-stages.json b/test/unit/data/investment/investment-project-stages.json new file mode 100644 index 00000000000..439027cbbc7 --- /dev/null +++ b/test/unit/data/investment/investment-project-stages.json @@ -0,0 +1,32 @@ +[ + { + "id": "8a320cc9-ae2e-443e-9d26-2f36452c2ced", + "name": "Prospect", + "disabled_on": null, + "exclude_from_investment_flow": false + }, + { + "id": "c9864359-fb1a-4646-a4c1-97d10189fc03", + "name": "Assign PM", + "disabled_on": null, + "exclude_from_investment_flow": false + }, + { + "id": "7606cc19-20da-4b74-aba1-2cec0d753ad8", + "name": "Active", + "disabled_on": null, + "exclude_from_investment_flow": false + }, + { + "id": "49b8f6f3-0c50-4150-a965-2c974f3149e3", + "name": "Verify win", + "disabled_on": null, + "exclude_from_investment_flow": false + }, + { + "id": "945ea6d1-eee3-4f5b-9144-84a75b71b8e6", + "name": "Won", + "disabled_on": null, + "exclude_from_investment_flow": false + } +]