Skip to content

Commit 9707655

Browse files
authored
DCC2: complete eligibility fixtures (#273)
* feat(dcc2): complete eligibility fixtures * feat(dcc2): clean code
1 parent 6d230ce commit 9707655

6 files changed

Lines changed: 182 additions & 29 deletions

File tree

src/Widgets/EligibilityModal/__tests__/Schedule.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ it('should be displayed', async () => {
1616
eligible: false,
1717
installments_count: 6,
1818
purchase_amount: 0,
19+
transaction_country: 'FR',
1920
}}
2021
/>,
2122
)

src/hooks/useFetchEligibility.test.ts

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,32 @@ import { ApiMode } from '@/consts'
44
import { statusResponse } from '@/types'
55
import useFetchEligibility from 'hooks/useFetchEligibility'
66
import { useSessionStorage } from 'hooks/useSessionStorage'
7-
import { mockPlansAllEligible } from 'test/fixtures'
7+
import { configPlans, mockPlansAllEligible, withCountry } from 'test/fixtures'
88
import { fetchFromApi } from 'utils/fetch'
99
import filterEligibility from 'utils/filterEligibility'
1010

1111
jest.mock('utils/fetch')
1212
jest.mock('hooks/useSessionStorage')
1313

14+
// Every test needs the hook's caching layer mocked; only `getCache` varies from one test to another.
15+
const mockSessionStorage = (getCache: jest.Mock = jest.fn()) => {
16+
const mocked = {
17+
getCache,
18+
setCache: jest.fn(),
19+
createKey: jest.fn().mockReturnValue('mocked_key'),
20+
clearCache: jest.fn(),
21+
}
22+
;(useSessionStorage as jest.Mock).mockReturnValue(mocked)
23+
return mocked
24+
}
25+
1426
describe('useFetchEligibility', () => {
1527
beforeEach(() => {
1628
jest.clearAllMocks()
1729
})
1830

1931
it('should fetch eligibility data and update state', async () => {
20-
const mockedSessionStorage = {
21-
getCache: jest.fn(),
22-
setCache: jest.fn(),
23-
createKey: jest.fn().mockReturnValue('mocked_key'),
24-
clearCache: jest.fn(),
25-
}
26-
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
32+
const mockedSessionStorage = mockSessionStorage()
2733
;(fetchFromApi as jest.Mock).mockImplementation(async () => mockPlansAllEligible)
2834

2935
const { result } = renderHook(() =>
@@ -68,13 +74,7 @@ describe('useFetchEligibility', () => {
6874
it('should use cached data if available to avoid calling fetch again', async () => {
6975
// Not using directly mockPlansAllEligible to make sure we gather the results stored in the cache
7076
const newMockedResult = [mockPlansAllEligible[0]]
71-
const mockedSessionStorage = {
72-
getCache: jest.fn().mockReturnValue({ key: 'mocked_key', value: newMockedResult }),
73-
setCache: jest.fn(),
74-
createKey: jest.fn().mockReturnValue('mocked_key'),
75-
clearCache: jest.fn(),
76-
}
77-
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
77+
mockSessionStorage(jest.fn().mockReturnValue({ key: 'mocked_key', value: newMockedResult }))
7878

7979
const { result } = renderHook(() =>
8080
useFetchEligibility(
@@ -97,13 +97,7 @@ describe('useFetchEligibility', () => {
9797
})
9898

9999
it('should returns a FAILED status if the API response contains `error_code`', async () => {
100-
const mockedSessionStorage = {
101-
getCache: jest.fn(),
102-
setCache: jest.fn(),
103-
createKey: jest.fn().mockReturnValue('mocked_key'),
104-
clearCache: jest.fn(),
105-
}
106-
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
100+
const mockedSessionStorage = mockSessionStorage()
107101
;(fetchFromApi as jest.Mock).mockImplementation(async () => ({
108102
message: 'some error',
109103
error_code: '403',
@@ -129,13 +123,7 @@ describe('useFetchEligibility', () => {
129123
expect(result.current[0]).toEqual([])
130124
})
131125
it('should returns a FAILED status if the API response contains `errors`', async () => {
132-
const mockedSessionStorage = {
133-
getCache: jest.fn(),
134-
setCache: jest.fn(),
135-
createKey: jest.fn().mockReturnValue('mocked_key'),
136-
clearCache: jest.fn(),
137-
}
138-
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
126+
const mockedSessionStorage = mockSessionStorage()
139127
;(fetchFromApi as jest.Mock).mockImplementation(async () => ({
140128
errors: 'some error',
141129
}))
@@ -159,4 +147,30 @@ describe('useFetchEligibility', () => {
159147
// The hook response should be empty
160148
expect(result.current[0]).toEqual([])
161149
})
150+
it('should expose the transaction_country returned by the API, not the requested country', async () => {
151+
mockSessionStorage()
152+
// The API answers with an Italian transaction while the customer addresses are French, so a
153+
// plan carrying `IT` can only come from the response itself. Which plan it is does not matter.
154+
const [anyEligiblePlan] = mockPlansAllEligible
155+
const planInItaly = withCountry(anyEligiblePlan, 'IT')
156+
;(fetchFromApi as jest.Mock).mockImplementation(async () => [planInItaly])
157+
158+
const { result } = renderHook(() =>
159+
// `configPlans` is required: it is the branch of filterEligibility that rebuilds each plan
160+
// object, and therefore the only place where the new field could get dropped.
161+
useFetchEligibility(
162+
45000,
163+
{ domain: ApiMode.TEST, merchantId: 'test_id' },
164+
configPlans,
165+
'FR',
166+
'FR',
167+
),
168+
)
169+
170+
await waitFor(() => {
171+
expect(result.current[1]).toBe(statusResponse.SUCCESS)
172+
})
173+
expect(result.current[0]).toHaveLength(1)
174+
expect(result.current[0][0].transaction_country).toBe('IT')
175+
})
162176
})

src/test/fixtures.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { mockDeferredP1XPlan, withCountry } from 'test/fixtures'
2+
3+
describe('withCountry', () => {
4+
it('should return a copy of the plan with transaction_country overridden', () => {
5+
expect(withCountry(mockDeferredP1XPlan, 'IT')).toEqual({
6+
...mockDeferredP1XPlan,
7+
transaction_country: 'IT',
8+
})
9+
})
10+
11+
it('should not mutate the given plan', () => {
12+
const plan = { ...mockDeferredP1XPlan }
13+
14+
withCountry(plan, 'ES')
15+
16+
expect(plan).toEqual(mockDeferredP1XPlan)
17+
})
18+
})

src/test/fixtures.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
1818
},
1919
],
2020
purchase_amount: 45000,
21+
transaction_country: 'FR',
2122
},
2223
{
2324
customer_total_cost_amount: 0,
@@ -36,6 +37,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
3637
},
3738
],
3839
purchase_amount: 45000,
40+
transaction_country: 'FR',
3941
},
4042
{
4143
customer_total_cost_amount: 0,
@@ -61,6 +63,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
6163
},
6264
],
6365
purchase_amount: 45000,
66+
transaction_country: 'FR',
6467
},
6568
{
6669
customer_total_cost_amount: 135,
@@ -93,6 +96,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
9396
},
9497
],
9598
purchase_amount: 45000,
99+
transaction_country: 'FR',
96100
},
97101
{
98102
customer_total_cost_amount: 1062,
@@ -132,6 +136,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
132136
},
133137
],
134138
purchase_amount: 45000,
139+
transaction_country: 'FR',
135140
},
136141
{
137142
annual_interest_rate: 1720,
@@ -224,6 +229,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
224229
},
225230
],
226231
purchase_amount: 45000,
232+
transaction_country: 'FR',
227233
},
228234
]
229235

@@ -245,6 +251,7 @@ export const mockButtonPlans = [
245251
},
246252
],
247253
purchase_amount: 45000,
254+
transaction_country: 'FR',
248255
},
249256
{
250257
customer_total_cost_amount: 0,
@@ -263,6 +270,7 @@ export const mockButtonPlans = [
263270
},
264271
],
265272
purchase_amount: 45000,
273+
transaction_country: 'FR',
266274
},
267275
{
268276
customer_total_cost_amount: 0,
@@ -288,6 +296,7 @@ export const mockButtonPlans = [
288296
},
289297
],
290298
purchase_amount: 45000,
299+
transaction_country: 'FR',
291300
},
292301
{
293302
customer_total_cost_amount: 135,
@@ -320,6 +329,7 @@ export const mockButtonPlans = [
320329
},
321330
],
322331
purchase_amount: 45000,
332+
transaction_country: 'FR',
323333
},
324334
{
325335
customer_total_cost_amount: 1202,
@@ -359,6 +369,7 @@ export const mockButtonPlans = [
359369
},
360370
],
361371
purchase_amount: 45000,
372+
transaction_country: 'FR',
362373
},
363374
{
364375
annual_interest_rate: 1719,
@@ -451,6 +462,7 @@ export const mockButtonPlans = [
451462
},
452463
],
453464
purchase_amount: 45000,
465+
transaction_country: 'FR',
454466
},
455467
]
456468

@@ -474,6 +486,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
474486
},
475487
],
476488
purchase_amount: 45000,
489+
transaction_country: 'FR',
477490
},
478491
{
479492
customer_fee: 0,
@@ -501,6 +514,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
501514
},
502515
],
503516
purchase_amount: 45000,
517+
transaction_country: 'FR',
504518
},
505519
{
506520
constraints: { purchase_amount: { maximum: 20000, minimum: 9000 } },
@@ -509,6 +523,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
509523
eligible: false,
510524
installments_count: 4,
511525
purchase_amount: 45000,
526+
transaction_country: 'FR',
512527
reasons: { purchase_amount: 'invalid_value' },
513528
},
514529
{
@@ -518,6 +533,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
518533
eligible: false,
519534
installments_count: 10,
520535
purchase_amount: 45000,
536+
transaction_country: 'FR',
521537
reasons: { purchase_amount: 'invalid_value' },
522538
},
523539
]
@@ -533,6 +549,7 @@ export const mockEligibilityWithHiddenPlan = [
533549
eligible: false,
534550
installments_count: 4,
535551
purchase_amount: 45000,
552+
transaction_country: 'FR',
536553
reasons: { installments_count: 'not_allowed' },
537554
// No constraints.purchase_amount → plan is hidden, not grayed out
538555
},
@@ -549,6 +566,7 @@ export const mockEligibilityWithGrayedOutPlan = [
549566
eligible: false,
550567
installments_count: 4,
551568
purchase_amount: 45000,
569+
transaction_country: 'FR',
552570
reasons: { purchase_amount: 'invalid_value' },
553571
// constraints.purchase_amount present → plan is grayed out, not hidden
554572
},
@@ -569,3 +587,103 @@ export const configPlans: ConfigPlan[] = mockPlansAllEligible.map((plan) => ({
569587
minAmount: 90_00,
570588
maxAmount: 3350_00,
571589
}))
590+
591+
// Deferred (30 days) 3x plan, without customer fees.
592+
export const mockDeferredMultiInstallmentPlanWithoutFees: EligibilityPlan = {
593+
customer_total_cost_amount: 0,
594+
customer_total_cost_bps: 0,
595+
deferred_days: 30,
596+
deferred_months: 0,
597+
eligible: true,
598+
installments_count: 3,
599+
payment_plan: [
600+
{
601+
customer_fee: 0,
602+
customer_interest: 0,
603+
due_date: 1640942762,
604+
purchase_amount: 15000,
605+
total_amount: 15000,
606+
},
607+
{
608+
customer_fee: 0,
609+
customer_interest: 0,
610+
due_date: 1643621162,
611+
purchase_amount: 15000,
612+
total_amount: 15000,
613+
},
614+
{
615+
customer_fee: 0,
616+
customer_interest: 0,
617+
due_date: 1646299562,
618+
purchase_amount: 15000,
619+
total_amount: 15000,
620+
},
621+
],
622+
purchase_amount: 45000,
623+
transaction_country: 'FR',
624+
}
625+
626+
// Same plan as above, with customer fees charged on the first installment.
627+
export const mockDeferredMultiInstallmentPlanWithFees: EligibilityPlan = {
628+
customer_total_cost_amount: 135,
629+
customer_total_cost_bps: 30,
630+
deferred_days: 30,
631+
deferred_months: 0,
632+
eligible: true,
633+
installments_count: 3,
634+
payment_plan: [
635+
{
636+
customer_fee: 135,
637+
customer_interest: 0,
638+
due_date: 1640942762,
639+
purchase_amount: 15000,
640+
total_amount: 15135,
641+
},
642+
{
643+
customer_fee: 0,
644+
customer_interest: 0,
645+
due_date: 1643621162,
646+
purchase_amount: 15000,
647+
total_amount: 15000,
648+
},
649+
{
650+
customer_fee: 0,
651+
customer_interest: 0,
652+
due_date: 1646299562,
653+
purchase_amount: 15000,
654+
total_amount: 15000,
655+
},
656+
],
657+
purchase_amount: 45000,
658+
transaction_country: 'FR',
659+
}
660+
661+
// Deferred P1X: a single installment paid 30 days later (pay later).
662+
export const mockDeferredP1XPlan: EligibilityPlan = {
663+
customer_total_cost_amount: 0,
664+
customer_total_cost_bps: 0,
665+
deferred_days: 30,
666+
deferred_months: 0,
667+
eligible: true,
668+
installments_count: 1,
669+
payment_plan: [
670+
{
671+
customer_fee: 0,
672+
customer_interest: 0,
673+
due_date: 1640942762,
674+
purchase_amount: 45000,
675+
total_amount: 45000,
676+
},
677+
],
678+
purchase_amount: 45000,
679+
transaction_country: 'FR',
680+
}
681+
682+
/**
683+
* Returns a copy of `plan` with its `transaction_country` overridden.
684+
* The input plan is never mutated, so shared fixtures stay reusable across tests.
685+
*/
686+
export const withCountry = (plan: EligibilityPlan, countryCode: string): EligibilityPlan => ({
687+
...plan,
688+
transaction_country: countryCode,
689+
})

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type EligibilityPlan = {
4444
constraints?: {
4545
purchase_amount?: { minimum: number; maximum: number }
4646
}
47+
transaction_country: string
4748
}
4849

4950
export type ErrorResponse = {

0 commit comments

Comments
 (0)