-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new Charging Module SendBillRunRequest (#831)
https://eaflood.atlassian.net/browse/WATER-4390 > Part of a series of changes to migrate the legacy [send a bill run](https://defra.github.io/sroc-charging-module-api-docs/#/bill-run/SendBillRun) to this project The [Charging Module API](https://github.com/DEFRA/sroc-charging-module.api) generates transaction references for every bill in a bill run and creates the transaction files when it receives a `/send` request. For that to happen though we need to send the 'send a bill' request. However, something that is a button click on our side is 3 stages in the CHA. - a bill run has to be 'approved' before it can be sent - the `/send` request is then made - wait for the CHA to complete the 'send' process and mark the bill run as either `billed` or `billing_not_required` When we make the request on our side, we don't care about the interstitial stages. So, we are adding a `SendBillRunRequest` which manages all this for us.
- Loading branch information
1 parent
b2b0974
commit 1d457d9
Showing
2 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
'use strict' | ||
|
||
/** | ||
* Connects with the Charging Module to send a bill run | ||
* @module ChargingModuleSendBillRunRequest | ||
*/ | ||
|
||
const ChargingModuleRequest = require('../charging-module.request.js') | ||
const ExpandedError = require('../../errors/expanded.error.js') | ||
const WaitForStatusRequest = require('./wait-for-status.request.js') | ||
|
||
/** | ||
* Approve then send a bill run in the Charging Module API | ||
* | ||
* Our service does in one step what the CHA does in two; approve the bill run then 'send' it. Approving a CHA bill run | ||
* doesn't actually do anything. It just changes the state to `approved` which the bill run has to be in before the bill | ||
* run can be sent. | ||
* | ||
* Sending a bill run is the final step. Once sent a bill run cannot be changed or deleted. Sending involves the CHA | ||
* generating a transaction reference for every bill in the bill run. It then generates a transaction reference for the | ||
* bill run itself. This reference is used to name the transaction import file which the CHA also generates at this | ||
* time. This is the file that will make it's way to SOP and be used to generate the invoice and credit notes that | ||
* customers receive. | ||
* | ||
* For small bill runs the process is near instantaneous. Larger bill runs however it can take a number of seconds. | ||
* Because of this when the request is first made the CHA switches the bill run's status to `pending`. Only when the | ||
* process is complete does the status get set to `billed` or `billing_not_required`. | ||
* | ||
* See {@link https://defra.github.io/sroc-charging-module-api-docs/#/bill-run/SendBillRun | CHA API docs} for more | ||
* details | ||
* | ||
* @param {string} billRunId - UUID of the charging module API bill run to send | ||
* | ||
* @returns {Promise<Object>} the promise returned is not intended to resolve to any particular value | ||
*/ | ||
async function send (billRunId) { | ||
await _approve(billRunId) | ||
await _send(billRunId) | ||
|
||
return _waitForSent(billRunId) | ||
} | ||
|
||
async function _approve (billRunId) { | ||
const path = `v3/wrls/bill-runs/${billRunId}/approve` | ||
const result = await ChargingModuleRequest.patch(path) | ||
|
||
if (!result.succeeded) { | ||
const error = new ExpandedError( | ||
'Charging Module approve request failed', | ||
{ billRunExternalId: billRunId, responseBody: result.response.body } | ||
) | ||
|
||
throw error | ||
} | ||
} | ||
|
||
async function _send (billRunId) { | ||
const path = `v3/wrls/bill-runs/${billRunId}/send` | ||
const result = await ChargingModuleRequest.patch(path) | ||
|
||
if (!result.succeeded) { | ||
const error = new ExpandedError( | ||
'Charging Module send request failed', | ||
{ billRunExternalId: billRunId, responseBody: result.response.body } | ||
) | ||
|
||
throw error | ||
} | ||
} | ||
|
||
async function _waitForSent (billRunId) { | ||
const result = await WaitForStatusRequest.send(billRunId, ['billed', 'billing_not_required']) | ||
|
||
if (!result.succeeded) { | ||
const error = new ExpandedError( | ||
'Charging Module wait request failed', | ||
{ billRunExternalId: billRunId, attempts: result.attempts, responseBody: result.response.body } | ||
) | ||
|
||
throw error | ||
} | ||
|
||
return result | ||
} | ||
|
||
module.exports = { | ||
send | ||
} |
129 changes: 129 additions & 0 deletions
129
test/requests/charging-module/send-bill-run-request.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
'use strict' | ||
|
||
// Test framework dependencies | ||
const Lab = require('@hapi/lab') | ||
const Code = require('@hapi/code') | ||
const Sinon = require('sinon') | ||
|
||
const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() | ||
const { expect } = Code | ||
|
||
// Test helpers | ||
const ExpandedError = require('../../../app/errors/expanded.error.js') | ||
|
||
// Things we need to stub | ||
const ChargingModuleRequest = require('../../../app/requests/charging-module.request.js') | ||
const WaitForStatusRequest = require('../../../app/requests/charging-module/wait-for-status.request.js') | ||
|
||
// Thing under test | ||
const SendBillRunRequest = require('../../../app/requests/charging-module/send-bill-run.request.js') | ||
|
||
describe('Charging Module Send Bill Run request', () => { | ||
const billRunId = '2bbbe459-966e-4026-b5d2-2f10867bdddd' | ||
|
||
let chargingModuleRequestStub | ||
|
||
beforeEach(() => { | ||
chargingModuleRequestStub = Sinon.stub(ChargingModuleRequest, 'patch') | ||
}) | ||
|
||
afterEach(() => { | ||
Sinon.restore() | ||
}) | ||
|
||
describe('when the request can send a bill run', () => { | ||
beforeEach(async () => { | ||
chargingModuleRequestStub.resolves({ | ||
succeeded: true, | ||
response: { | ||
info: { | ||
gitCommit: '273604040a47e0977b0579a0fef0f09726d95e39', | ||
dockerTag: 'ghcr.io/defra/sroc-charging-module-api:v0.19.0' | ||
}, | ||
statusCode: 204, | ||
body: null | ||
} | ||
}) | ||
Sinon.stub(WaitForStatusRequest, 'send').resolves({ succeeded: true, status: 'billed', attempts: 1 }) | ||
}) | ||
|
||
it('returns a `true` success status', async () => { | ||
const result = await SendBillRunRequest.send(billRunId) | ||
|
||
expect(result.succeeded).to.be.true() | ||
}) | ||
|
||
it('returns the last status received', async () => { | ||
const result = await SendBillRunRequest.send(billRunId) | ||
|
||
expect(result.status).to.equal('billed') | ||
}) | ||
|
||
it('returns the number of attempts', async () => { | ||
const result = await SendBillRunRequest.send(billRunId) | ||
|
||
expect(result.attempts).to.equal(1) | ||
}) | ||
}) | ||
|
||
describe('when the request cannot send a bill run', () => { | ||
describe('because the approve request fails', () => { | ||
beforeEach(async () => { | ||
chargingModuleRequestStub.onFirstCall().resolves({ | ||
succeeded: false, | ||
response: { body: 'Boom' } | ||
}) | ||
}) | ||
|
||
it('throws an error', async () => { | ||
const error = await expect(SendBillRunRequest.send(billRunId)).to.reject() | ||
|
||
expect(error).to.be.instanceOf(ExpandedError) | ||
expect(error.message).to.equal('Charging Module approve request failed') | ||
expect(error.billRunExternalId).to.equal(billRunId) | ||
expect(error.responseBody).to.equal('Boom') | ||
}) | ||
}) | ||
|
||
describe('because the send request fails', () => { | ||
beforeEach(async () => { | ||
chargingModuleRequestStub.onFirstCall().resolves({ succeeded: true }) | ||
chargingModuleRequestStub.onSecondCall().resolves({ | ||
succeeded: false, | ||
response: { body: 'Boom' } | ||
}) | ||
}) | ||
|
||
it('throws an error', async () => { | ||
const error = await expect(SendBillRunRequest.send(billRunId)).to.reject() | ||
|
||
expect(error).to.be.instanceOf(ExpandedError) | ||
expect(error.message).to.equal('Charging Module send request failed') | ||
expect(error.billRunExternalId).to.equal(billRunId) | ||
expect(error.responseBody).to.equal('Boom') | ||
}) | ||
}) | ||
|
||
describe('because the wait request fails', () => { | ||
beforeEach(async () => { | ||
chargingModuleRequestStub.onFirstCall().resolves({ succeeded: true }) | ||
chargingModuleRequestStub.onSecondCall().resolves({ succeeded: true }) | ||
Sinon.stub(WaitForStatusRequest, 'send').resolves({ | ||
succeeded: false, | ||
attempts: 100, | ||
response: { body: 'Boom' } | ||
}) | ||
}) | ||
|
||
it('throws an error', async () => { | ||
const error = await expect(SendBillRunRequest.send(billRunId)).to.reject() | ||
|
||
expect(error).to.be.instanceOf(ExpandedError) | ||
expect(error.message).to.equal('Charging Module wait request failed') | ||
expect(error.billRunExternalId).to.equal(billRunId) | ||
expect(error.attempts).to.equal(100) | ||
expect(error.responseBody).to.equal('Boom') | ||
}) | ||
}) | ||
}) | ||
}) |