@defra-fish/recurring-payments-job
Version:
Rod Licensing Recurring Payments Job
192 lines (166 loc) • 6.59 kB
JavaScript
import { getPaymentStatus, sendPayment, isGovPayUp } from '../govuk-pay-service.js'
import { govUkPayApi } from '@defra-fish/connectors-lib'
import db from 'debug'
jest.mock('@defra-fish/connectors-lib')
jest.mock('debug', () => jest.fn(() => jest.fn()))
const mockDebug = db.mock.results[0].value
describe('govuk-pay-service', () => {
it('initialises logger', () => {
expect(db).toHaveBeenCalledWith('recurring-payments:gov.uk-pay-service')
})
describe('sendPayment', () => {
const preparedPayment = { id: '1234' }
it('sendPayment should return response from createPayment in json format', async () => {
const mockPreparedPayment = { id: 'test-payment-id' }
const mockResponse = { state: { status: 'created' }, payment_id: 'abcde12345' }
const mockFetchResponse = {
status: 200,
ok: true,
json: jest.fn().mockResolvedValue(mockResponse)
}
govUkPayApi.createPayment.mockResolvedValue(mockFetchResponse)
const result = await sendPayment(mockPreparedPayment)
expect(result).toEqual(mockResponse)
})
it('should send provided payload data to Gov.UK Pay', async () => {
govUkPayApi.createPayment.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue({ success: true, paymentId: 'abc123' })
})
const unique = Symbol('payload')
const payload = {
amount: '100',
description: 'The recurring card payment for your rod fishing licence',
reference: unique
}
sendPayment(payload)
expect(govUkPayApi.createPayment).toHaveBeenCalledWith(payload, true)
})
it('should throw an error when the GOV.UK Pay connector raises an error', async () => {
govUkPayApi.createPayment.mockImplementationOnce(() => {
throw new Error('Oops!')
})
try {
await sendPayment(preparedPayment)
} catch (e) {
expect(e.message).toBe('Oops!')
}
})
it('should throw an error when response is not ok', async () => {
const mockFetchResponse = {
ok: false,
status: 400,
json: jest.fn().mockResolvedValue({
code: 'P0102',
field: 'agreement_id',
description: 'Invalid attribute value: agreement_id. Agreement does not exist'
})
}
govUkPayApi.createPayment.mockResolvedValueOnce(mockFetchResponse)
await expect(
sendPayment({
amount: 100,
description: 'The recurring card payment for your rod fishing licence',
id: 'a50f0d51-295f-42b3-98f8-97c0641ede5a',
authorisation_mode: 'agreement',
agreement_id: 'does_not_exist'
})
).rejects.toThrow('Unexpected response from GOV.UK Pay API')
})
})
describe('getPaymentStatus', () => {
it('should call fetchPaymentStatus with payment id and true for recurring payments', async () => {
govUkPayApi.fetchPaymentStatus.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue({ code: 'P1234', description: 'Success' })
})
const paymentId = Symbol('transactionId')
await getPaymentStatus(paymentId)
expect(govUkPayApi.fetchPaymentStatus).toHaveBeenCalledWith(paymentId, true)
})
it('should return the payment status on successful response', async () => {
const mockPaymentStatus = { amount: 37.5, state: { status: 'success', finished: 'true' } }
govUkPayApi.fetchPaymentStatus.mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockPaymentStatus)
})
const paymentId = 'valid-payment-id'
const result = await getPaymentStatus(paymentId)
expect(result).toEqual(mockPaymentStatus)
})
it('should throw an error when payment ID is not provided', async () => {
await expect(getPaymentStatus(null)).rejects.toThrow('Invalid payment ID')
})
it('should throw an error when response is not ok', async () => {
const mockFetchResponse = {
ok: false,
status: 404,
json: jest.fn().mockResolvedValue({
code: 'P0200',
field: 'payment_id',
description: 'No payment matched the payment id you provided'
})
}
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockFetchResponse)
await expect(getPaymentStatus('invalid-payment-id')).rejects.toThrow('Unexpected response from GOV.UK Pay API')
})
it('should log details when response is not ok', async () => {
const serviceResponseBody = {
code: 'P0200',
field: 'payment_id',
description: 'No payment matched the payment id you provided'
}
const mockFetchResponse = {
ok: false,
status: 404,
json: jest.fn().mockResolvedValue(serviceResponseBody)
}
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockFetchResponse)
jest.spyOn(console, 'error')
const paymentId = 'invalid-payment-id'
try {
await getPaymentStatus(paymentId)
} catch {}
expect(console.error).toHaveBeenCalledWith(
expect.objectContaining({
method: 'GET',
status: mockFetchResponse.status,
response: serviceResponseBody,
paymentId
})
)
})
it('should throw an error when fetchPaymentStatus fails', async () => {
const mockError = new Error('Network error')
govUkPayApi.fetchPaymentStatus.mockRejectedValue(mockError)
await expect(getPaymentStatus('test-payment-id')).rejects.toThrow('Network error')
})
})
describe('isGovPayUp', () => {
it.each([
[true, 'true', 'true'],
[false, 'true', 'false'],
[false, 'false', 'true'],
[false, 'false', 'false']
])('resolves to %p if healthy is %s and deadlocks is %s', async (expectedResult, pingHealthy, deadlocksHealthy) => {
govUkPayApi.isGovPayUp.mockResolvedValueOnce({
ok: true,
text: async () => `{"ping":{"healthy":${pingHealthy}},"deadlocks":{"healthy":${deadlocksHealthy}}}`
})
expect(await isGovPayUp()).toBe(expectedResult)
})
it("resolves to false if we don't receive a 2xx response", async () => {
govUkPayApi.isGovPayUp.mockResolvedValueOnce({
ok: false
})
expect(await isGovPayUp()).toBe(false)
})
it("logs if we don't receive a 2xx response", async () => {
govUkPayApi.isGovPayUp.mockResolvedValueOnce({
ok: false
})
await isGovPayUp()
expect(mockDebug).toHaveBeenCalledWith('Health endpoint unavailable')
})
})
})