Skip to content

Commit 17de9a6

Browse files
committed
More coverage on PaymentField
1 parent 1ee302a commit 17de9a6

1 file changed

Lines changed: 247 additions & 2 deletions

File tree

src/server/plugins/engine/components/PaymentField.test.ts

Lines changed: 247 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
import { ComponentType, type PaymentFieldComponent } from '@defra/forms-model'
1+
import {
2+
ComponentType,
3+
type FormMetadata,
4+
type PaymentFieldComponent
5+
} from '@defra/forms-model'
26

37
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
8+
import { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'
49
import {
510
getAnswer,
611
type Field
712
} from '~/src/server/plugins/engine/components/helpers/components.js'
813
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
9-
import { type FormValue } from '~/src/server/plugins/engine/types.js'
14+
import {
15+
type FormContext,
16+
type FormValue
17+
} from '~/src/server/plugins/engine/types.js'
18+
import {
19+
type FormRequestPayload,
20+
type FormResponseToolkit
21+
} from '~/src/server/routes/types.js'
22+
import { get, post, postJson } from '~/src/server/services/httpService.js'
1023
import definition from '~/test/form/definitions/blank.js'
1124
import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
1225

26+
jest.mock('~/src/server/services/httpService.ts')
27+
1328
describe('PaymentField', () => {
1429
let model: FormModel
1530

@@ -218,4 +233,234 @@ describe('PaymentField', () => {
218233
})
219234
})
220235
})
236+
237+
describe('dispatcher and onSubmit', () => {
238+
const def = {
239+
title: 'Example payment field',
240+
name: 'myComponent',
241+
type: ComponentType.PaymentField,
242+
options: {
243+
amount: 100,
244+
description: 'Test payment description'
245+
}
246+
} satisfies PaymentFieldComponent
247+
248+
const collection = new ComponentCollection([def], { model })
249+
const paymentField = collection.fields[0] as PaymentField
250+
251+
describe('dispatcher', () => {
252+
it('should create payment and redirect to gov pay', async () => {
253+
const mockYarSet = jest.fn()
254+
const mockRequest = {
255+
server: {
256+
plugins: {
257+
// eslint-disable-next-line no-useless-computed-key
258+
['forms-engine-plugin']: {
259+
baseUrl: 'base-url'
260+
}
261+
}
262+
},
263+
yar: {
264+
set: mockYarSet
265+
}
266+
} as unknown as FormRequestPayload
267+
const mockH = {
268+
redirect: jest
269+
.fn()
270+
.mockReturnValueOnce({ code: jest.fn().mockReturnValueOnce('ok') })
271+
} as unknown as FormResponseToolkit
272+
const args = {
273+
controller: {
274+
model: {
275+
formId: 'form-id',
276+
basePath: 'base-path',
277+
name: 'PaymentModel'
278+
},
279+
getState: jest
280+
.fn()
281+
.mockResolvedValueOnce({ $$__referenceNumber: 'pay-ref-123' })
282+
},
283+
component: paymentField,
284+
sourceUrl: 'http://localhost:3009/test-payment',
285+
isLive: false,
286+
isPreview: true
287+
}
288+
// @ts-expect-error - partial mock
289+
jest.mocked(postJson).mockResolvedValueOnce({
290+
payload: {
291+
state: {
292+
status: 'created'
293+
},
294+
payment_id: 'new-payment-id',
295+
_links: {
296+
next_url: {
297+
href: '/next-url'
298+
}
299+
}
300+
}
301+
})
302+
303+
const res = await PaymentField.dispatcher(mockRequest, mockH, args)
304+
expect(res).toBe('ok')
305+
expect(mockYarSet).toHaveBeenCalledWith(expect.any(String), {
306+
amount: 100,
307+
componentName: 'myComponent',
308+
description: 'Test payment description',
309+
failureUrl: 'http://localhost:3009/test-payment',
310+
formId: 'form-id',
311+
isLivePayment: false,
312+
paymentId: 'new-payment-id',
313+
reference: 'pay-ref-123',
314+
returnUrl: 'base-url/base-path/summary',
315+
uuid: expect.any(String)
316+
})
317+
})
318+
})
319+
320+
describe('onSubmit', () => {
321+
it('should throw if missing state', async () => {
322+
const mockRequest = {} as unknown as FormRequestPayload
323+
324+
await expect(() =>
325+
paymentField.onSubmit(
326+
mockRequest,
327+
{} as FormMetadata,
328+
{ state: {} } as FormContext
329+
)
330+
).rejects.toThrow('Invalid component state for: myComponent')
331+
})
332+
333+
it('should ignore if payment already captured', async () => {
334+
const mockRequest = {} as unknown as FormRequestPayload
335+
336+
await paymentField.onSubmit(
337+
mockRequest,
338+
{} as FormMetadata,
339+
{
340+
state: {
341+
myComponent: {
342+
capture: {
343+
status: 'success'
344+
},
345+
paymentId: 'payment-id',
346+
amount: 123,
347+
description: 'Payment desc'
348+
}
349+
}
350+
} as unknown as FormContext
351+
)
352+
expect(get).not.toHaveBeenCalled()
353+
expect(post).not.toHaveBeenCalled()
354+
})
355+
356+
// TODO - understand the difference between this test and the previous
357+
it('should mark payment already captured', async () => {
358+
const mockRequest = {} as unknown as FormRequestPayload
359+
// @ts-expect-error - partial mock
360+
jest
361+
.mocked(get)
362+
.mockResolvedValueOnce({ payload: { state: { status: 'success' } } })
363+
await paymentField.onSubmit(
364+
mockRequest,
365+
{} as FormMetadata,
366+
{
367+
state: {
368+
myComponent: {
369+
paymentId: 'payment-id',
370+
amount: 123,
371+
description: 'Payment desc',
372+
isLivePayment: false,
373+
formId: 'form-id'
374+
}
375+
}
376+
} as unknown as FormContext
377+
)
378+
expect(get).toHaveBeenCalled()
379+
expect(post).not.toHaveBeenCalled()
380+
})
381+
382+
it('should throw if bad status', async () => {
383+
const mockRequest = {} as unknown as FormRequestPayload
384+
// @ts-expect-error - partial mock
385+
jest
386+
.mocked(get)
387+
.mockResolvedValueOnce({ payload: { state: { status: 'bad' } } })
388+
await expect(() =>
389+
paymentField.onSubmit(
390+
mockRequest,
391+
{} as FormMetadata,
392+
{
393+
state: {
394+
myComponent: {
395+
paymentId: 'payment-id',
396+
amount: 123,
397+
description: 'Payment desc',
398+
isLivePayment: false,
399+
formId: 'form-id'
400+
}
401+
}
402+
} as unknown as FormContext
403+
)
404+
).rejects.toThrow()
405+
})
406+
407+
it('should throw if error during capture', async () => {
408+
const mockRequest = {} as unknown as FormRequestPayload
409+
// @ts-expect-error - partial mock
410+
jest
411+
.mocked(get)
412+
.mockResolvedValueOnce({
413+
payload: { state: { status: 'capturable' } }
414+
})
415+
// @ts-expect-error - partial mock
416+
jest.mocked(post).mockResolvedValueOnce({ res: { statusCode: 400 } })
417+
await expect(() =>
418+
paymentField.onSubmit(
419+
mockRequest,
420+
{} as FormMetadata,
421+
{
422+
state: {
423+
myComponent: {
424+
paymentId: 'payment-id',
425+
amount: 123,
426+
description: 'Payment desc',
427+
isLivePayment: false,
428+
formId: 'form-id'
429+
}
430+
}
431+
} as unknown as FormContext
432+
)
433+
).rejects.toThrow()
434+
})
435+
436+
it('should capture payment if no errors', async () => {
437+
const mockRequest = {} as unknown as FormRequestPayload
438+
// @ts-expect-error - partial mock
439+
jest
440+
.mocked(get)
441+
.mockResolvedValueOnce({
442+
payload: { state: { status: 'capturable' } }
443+
})
444+
// @ts-expect-error - partial mock
445+
jest.mocked(post).mockResolvedValueOnce({ res: { statusCode: 200 } })
446+
await paymentField.onSubmit(
447+
mockRequest,
448+
{} as FormMetadata,
449+
{
450+
state: {
451+
myComponent: {
452+
paymentId: 'payment-id',
453+
amount: 123,
454+
description: 'Payment desc',
455+
isLivePayment: false,
456+
formId: 'form-id'
457+
}
458+
}
459+
} as unknown as FormContext
460+
)
461+
expect(get).toHaveBeenCalled()
462+
expect(post).toHaveBeenCalled()
463+
})
464+
})
465+
})
221466
})

0 commit comments

Comments
 (0)