From cd8d0e60fa898c0b2d100890143230552cb7e7dc Mon Sep 17 00:00:00 2001 From: Marc Templeton <56440327+marctemp@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:20:35 +0100 Subject: [PATCH] Get Settlement info (#14) --- ...get-settled-settlement-by-settlement-id.js | 19 +++ app/processing/settlement/get-settlement.js | 9 ++ app/processing/settlement/index.js | 3 + app/processing/settlement/schema.js | 7 + .../settlement/validate-settlement.js | 16 ++ package-lock.json | 4 +- package.json | 2 +- .../settlement/get-settlement.test.js | 113 ++++++++++++++ .../settlement/validate-settlement.test.js | 113 ++++++++++++++ .../settlement/get-settlement.test.js | 145 ++++++++++++++++++ .../settlement/validate-settlement.test.js | 89 +++++++++++ 11 files changed, 517 insertions(+), 3 deletions(-) create mode 100644 app/processing/settlement/get-settled-settlement-by-settlement-id.js create mode 100644 app/processing/settlement/get-settlement.js create mode 100644 app/processing/settlement/index.js create mode 100644 app/processing/settlement/schema.js create mode 100644 app/processing/settlement/validate-settlement.js create mode 100644 test/integration/processing/settlement/get-settlement.test.js create mode 100644 test/integration/processing/settlement/validate-settlement.test.js create mode 100644 test/unit/processing/settlement/get-settlement.test.js create mode 100644 test/unit/processing/settlement/validate-settlement.test.js diff --git a/app/processing/settlement/get-settled-settlement-by-settlement-id.js b/app/processing/settlement/get-settled-settlement-by-settlement-id.js new file mode 100644 index 00000000..599f27cc --- /dev/null +++ b/app/processing/settlement/get-settled-settlement-by-settlement-id.js @@ -0,0 +1,19 @@ +const db = require('../../data') + +const getSettledSettlementBySettlementId = async (settlementId, transaction) => { + return db.settlement.findOne({ + transaction, + attributes: [ + 'paymentRequestId', + 'reference', + 'settled' + ], + where: { + settlementId, + settled: true + }, + raw: true + }) +} + +module.exports = getSettledSettlementBySettlementId diff --git a/app/processing/settlement/get-settlement.js b/app/processing/settlement/get-settlement.js new file mode 100644 index 00000000..86d758fe --- /dev/null +++ b/app/processing/settlement/get-settlement.js @@ -0,0 +1,9 @@ +const getSettledSettlementBySettlementId = require('./get-settled-settlement-by-settlement-id') +const validateSettlement = require('./validate-settlement') + +const getSettlement = async (settlementId) => { + const settledSettlement = await getSettledSettlementBySettlementId(settlementId) + return validateSettlement(settledSettlement) +} + +module.exports = getSettlement diff --git a/app/processing/settlement/index.js b/app/processing/settlement/index.js new file mode 100644 index 00000000..c9badb36 --- /dev/null +++ b/app/processing/settlement/index.js @@ -0,0 +1,3 @@ +const getSettlement = require('./get-settlement') + +module.exports = getSettlement diff --git a/app/processing/settlement/schema.js b/app/processing/settlement/schema.js new file mode 100644 index 00000000..911fe963 --- /dev/null +++ b/app/processing/settlement/schema.js @@ -0,0 +1,7 @@ +const Joi = require('joi') + +module.exports = Joi.object({ + paymentRequestId: Joi.number().integer().required(), + reference: Joi.string().required(), + settled: Joi.boolean().required() +}).required() diff --git a/app/processing/settlement/validate-settlement.js b/app/processing/settlement/validate-settlement.js new file mode 100644 index 00000000..374b7834 --- /dev/null +++ b/app/processing/settlement/validate-settlement.js @@ -0,0 +1,16 @@ +const util = require('util') +const schema = require('./schema') + +const validateSettlement = (settlement) => { + const result = schema.validate(settlement, { + abortEarly: false + }) + + if (result.error) { + throw new Error(`Settlement: ${util.inspect(settlement, false, null, true)} does not have the required data: ${result.error.message}`) + } + + return result.value +} + +module.exports = validateSettlement diff --git a/package-lock.json b/package-lock.json index b5c4aa21..1ac989e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ffc-pay-statement-constructor", - "version": "0.7.1", + "version": "0.7.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ffc-pay-statement-constructor", - "version": "0.7.1", + "version": "0.7.3", "license": "OGL-UK-3.0", "dependencies": { "@azure/identity": "2.0.5", diff --git a/package.json b/package.json index fbb2932d..0feb0d09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ffc-pay-statement-constructor", - "version": "0.7.2", + "version": "0.7.3", "description": "Data construction for statement generation", "homepage": "https://github.com/DEFRA/ffc-pay-statement-constructor", "main": "app/index.js", diff --git a/test/integration/processing/settlement/get-settlement.test.js b/test/integration/processing/settlement/get-settlement.test.js new file mode 100644 index 00000000..5e6137a2 --- /dev/null +++ b/test/integration/processing/settlement/get-settlement.test.js @@ -0,0 +1,113 @@ +const db = require('../../../../app/data') + +const getSettlement = require('../../../../app/processing/settlement') + +const schemes = require('../../../../app/constants/schemes') +const paymentRequest = JSON.parse(JSON.stringify(require('../../../mock-payment-request').submitPaymentRequest)) + +const SETTLEMENT_ID_NOT_SETTLED = 1 +const SETTLEMENT_ID_SETTLED = 2 + +let settlement +let mappedSettlement + +describe('process settlement', () => { + beforeAll(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + beforeEach(async () => { + await db.scheme.bulkCreate(schemes) + await db.invoiceNumber.create({ + invoiceNumber: paymentRequest.invoiceNumber, + originalInvoiceNumber: paymentRequest.invoiceNumber.slice(0, 5) + }) + + await db.paymentRequest.create(paymentRequest) + + settlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) + await db.settlement.create({ ...settlement, paymentRequestId: 1, settled: false }) + + mappedSettlement = { + paymentRequestId: 1, + reference: settlement.reference, + settled: settlement.settled + } + }) + + afterEach(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + afterAll(async () => { + await db.sequelize.close() + }) + + test('should return mapped settled object when existing settled settlement with required information exists', async () => { + try { await db.settlement.create({ ...settlement, paymentRequestId: 1 }) } catch { } + const result = await getSettlement(SETTLEMENT_ID_SETTLED) + expect(result).toStrictEqual(mappedSettlement) + }) + + test('should throw when no existing settlement exists', async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + + const wrapper = async () => { await getSettlement(1) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw when no existing settlement exists', async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + + const wrapper = async () => { await getSettlement(undefined) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw when no existing settlement exists', async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + + const wrapper = async () => { await getSettlement(null) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw when existing unsettled settlement with required information exists', async () => { + const wrapper = async () => { await getSettlement(SETTLEMENT_ID_NOT_SETTLED) } + expect(wrapper).rejects.toThrow() + }) + + test('should throw when existing settled settlement with missing required reference does not exist', async () => { + delete settlement.reference + await db.settlement.create({ ...settlement, paymentRequestId: 1 }) + + const wrapper = async () => { await getSettlement(SETTLEMENT_ID_SETTLED) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw when existing settled settlement with missing required settled does not exist', async () => { + delete settlement.settled + await db.settlement.create({ ...settlement, paymentRequestId: 1 }) + + const wrapper = async () => { await getSettlement(SETTLEMENT_ID_SETTLED) } + + expect(wrapper).rejects.toThrow() + }) +}) diff --git a/test/integration/processing/settlement/validate-settlement.test.js b/test/integration/processing/settlement/validate-settlement.test.js new file mode 100644 index 00000000..e143a377 --- /dev/null +++ b/test/integration/processing/settlement/validate-settlement.test.js @@ -0,0 +1,113 @@ +const validateSettlement = require('../../../../app/processing/settlement/validate-settlement') + +let retreivedSettlement + +describe('validate settlement', () => { + beforeEach(() => { + const settlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) + retreivedSettlement = { + paymentRequestId: 1, + reference: settlement.reference, + settled: settlement.settled + } + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should return retreivedSettlement', async () => { + const result = validateSettlement(retreivedSettlement) + expect(result).toStrictEqual(retreivedSettlement) + }) + + test('should throw when retreivedSettlement is missing required paymentRequestId', async () => { + delete retreivedSettlement.paymentRequestId + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when retreivedSettlement is missing required paymentRequestId', async () => { + delete retreivedSettlement.paymentRequestId + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which ends "does not have the required data: "paymentRequestId" is required" when retreivedSettlement is missing required paymentRequestId', async () => { + delete retreivedSettlement.paymentRequestId + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(/does not have the required data: "paymentRequestId" is required/) + }) + + test('should throw when retreivedSettlement is missing required reference', async () => { + delete retreivedSettlement.reference + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when retreivedSettlement is missing required reference', async () => { + delete retreivedSettlement.reference + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which ends "does not have the required data: "reference" is required" when retreivedSettlement is missing required reference', async () => { + delete retreivedSettlement.reference + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(/does not have the required data: "reference" is required/) + }) + + test('should throw when retreivedSettlement is missing required settled', async () => { + delete retreivedSettlement.settled + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when retreivedSettlement is missing required settled', async () => { + delete retreivedSettlement.settled + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which ends "does not have the required data: "settled" is required" when retreivedSettlement is missing required settled', async () => { + delete retreivedSettlement.settled + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(/does not have the required data: "settled" is required/) + }) +}) diff --git a/test/unit/processing/settlement/get-settlement.test.js b/test/unit/processing/settlement/get-settlement.test.js new file mode 100644 index 00000000..9d7637fe --- /dev/null +++ b/test/unit/processing/settlement/get-settlement.test.js @@ -0,0 +1,145 @@ +jest.mock('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') +const getSettledSettlementBySettlementId = require('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') + +jest.mock('../../../../app/processing/settlement/validate-settlement') +const validateSettlement = require('../../../../app/processing/settlement/validate-settlement') + +const getSettlement = require('../../../../app/processing/settlement/get-settlement') + +let retreivedSettlement + +describe('get required settlement information for building a statement object', () => { + beforeEach(() => { + const settlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) + + retreivedSettlement = { + paymentRequestId: 1, + reference: settlement.reference, + settled: settlement.settled + } + + validateSettlement.mockReturnValue(retreivedSettlement) + getSettledSettlementBySettlementId.mockResolvedValue(retreivedSettlement) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should call getSettledSettlementBySettlementId when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(getSettledSettlementBySettlementId).toHaveBeenCalled() + }) + + test('should call getSettledSettlementBySettlementId once when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(getSettledSettlementBySettlementId).toHaveBeenCalledTimes(1) + }) + + test('should call getSettledSettlementBySettlementId with settlementId when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(getSettledSettlementBySettlementId).toHaveBeenCalledWith(settlementId) + }) + + test('should call validateSettlement when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(validateSettlement).toHaveBeenCalled() + }) + + test('should call validateSettlement once when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(validateSettlement).toHaveBeenCalledTimes(1) + }) + + test('should call validateSettlement with retreivedSettlement when a settlementId is given', async () => { + const settlementId = 1 + await getSettlement(settlementId) + expect(validateSettlement).toHaveBeenCalledWith(retreivedSettlement) + }) + + test('should return retreivedSettlement when a settlementId is given', async () => { + const settlementId = 1 + const result = await getSettlement(settlementId) + expect(result).toStrictEqual(retreivedSettlement) + }) + + test('should throw when getSettledSettlementBySettlementId throws', async () => { + const settlementId = 1 + getSettledSettlementBySettlementId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when getSettledSettlementBySettlementId throws Error', async () => { + const settlementId = 1 + getSettledSettlementBySettlementId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error with "Database retrieval issue" when getSettledSettlementBySettlementId throws error with "Database retrieval issue"', async () => { + const settlementId = 1 + getSettledSettlementBySettlementId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow(/^Database retrieval issue$/) + }) + + test('should not call validateSettlement when getSettledSettlementBySettlementId throws', async () => { + const settlementId = 1 + getSettledSettlementBySettlementId.mockRejectedValue(new Error('Database retrieval issue')) + + try { await getSettlement(settlementId) } catch {} + + expect(validateSettlement).not.toHaveBeenCalled() + }) + + test('should throw when validateSettlement throws', async () => { + const settlementId = 1 + validateSettlement.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when validateSettlement throws Error', async () => { + const settlementId = 1 + validateSettlement.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error with "Joi validation issue" when validateSettlement throws error with "Joi validation issue"', async () => { + const settlementId = 1 + validateSettlement.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + await getSettlement(settlementId) + } + + expect(wrapper).rejects.toThrow(/^Joi validation issue$/) + }) +}) diff --git a/test/unit/processing/settlement/validate-settlement.test.js b/test/unit/processing/settlement/validate-settlement.test.js new file mode 100644 index 00000000..af655522 --- /dev/null +++ b/test/unit/processing/settlement/validate-settlement.test.js @@ -0,0 +1,89 @@ +jest.mock('../../../../app/processing/settlement/schema') +const schema = require('../../../../app/processing/settlement/schema') + +const validateSettlement = require('../../../../app/processing/settlement/validate-settlement') + +let retreivedSettlement + +describe('validate settlement', () => { + beforeEach(() => { + const settlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) + + retreivedSettlement = { + paymentRequestId: 1, + reference: settlement.reference, + settled: settlement.settled + } + + schema.validate.mockReturnValue({ value: retreivedSettlement }) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should return retreivedSettlement', async () => { + const result = validateSettlement(retreivedSettlement) + expect(result).toBe(retreivedSettlement) + }) + + test('should throw when schema.validate throws', async () => { + schema.validate.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when schema.validate throws Error', async () => { + schema.validate.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error "Joi validation issue" when schema.validate throws with "Joi validation issue"', async () => { + schema.validate.mockImplementation(() => { throw new Error('Joi validation issue') }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(/^Joi validation issue$/) + }) + + test('should throw when schema.validate returns with error key', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when schema.validate returns with error key', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which has in it "does not have the required data" when schema.validate returns with error key of "Not a valid object"', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + validateSettlement(retreivedSettlement) + } + + expect(wrapper).rejects.toThrow(/does not have the required data/) + }) +})