Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Widgets/EligibilityModal/__tests__/Schedule.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ it('should be displayed', async () => {
eligible: false,
installments_count: 6,
purchase_amount: 0,
transaction_country: 'FR',
}}
/>,
)
Expand Down
72 changes: 43 additions & 29 deletions src/hooks/useFetchEligibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,32 @@ import { ApiMode } from '@/consts'
import { statusResponse } from '@/types'
import useFetchEligibility from 'hooks/useFetchEligibility'
import { useSessionStorage } from 'hooks/useSessionStorage'
import { mockPlansAllEligible } from 'test/fixtures'
import { configPlans, mockPlansAllEligible, withCountry } from 'test/fixtures'
import { fetchFromApi } from 'utils/fetch'
import filterEligibility from 'utils/filterEligibility'

jest.mock('utils/fetch')
jest.mock('hooks/useSessionStorage')

// Every test needs the hook's caching layer mocked; only `getCache` varies from one test to another.
const mockSessionStorage = (getCache: jest.Mock = jest.fn()) => {
const mocked = {
getCache,
setCache: jest.fn(),
createKey: jest.fn().mockReturnValue('mocked_key'),
clearCache: jest.fn(),
}
;(useSessionStorage as jest.Mock).mockReturnValue(mocked)
return mocked
}

describe('useFetchEligibility', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should fetch eligibility data and update state', async () => {
const mockedSessionStorage = {
getCache: jest.fn(),
setCache: jest.fn(),
createKey: jest.fn().mockReturnValue('mocked_key'),
clearCache: jest.fn(),
}
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
const mockedSessionStorage = mockSessionStorage()
;(fetchFromApi as jest.Mock).mockImplementation(async () => mockPlansAllEligible)

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

const { result } = renderHook(() =>
useFetchEligibility(
Expand All @@ -97,13 +97,7 @@ describe('useFetchEligibility', () => {
})

it('should returns a FAILED status if the API response contains `error_code`', async () => {
const mockedSessionStorage = {
getCache: jest.fn(),
setCache: jest.fn(),
createKey: jest.fn().mockReturnValue('mocked_key'),
clearCache: jest.fn(),
}
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
const mockedSessionStorage = mockSessionStorage()
;(fetchFromApi as jest.Mock).mockImplementation(async () => ({
message: 'some error',
error_code: '403',
Expand All @@ -129,13 +123,7 @@ describe('useFetchEligibility', () => {
expect(result.current[0]).toEqual([])
})
it('should returns a FAILED status if the API response contains `errors`', async () => {
const mockedSessionStorage = {
getCache: jest.fn(),
setCache: jest.fn(),
createKey: jest.fn().mockReturnValue('mocked_key'),
clearCache: jest.fn(),
}
;(useSessionStorage as jest.Mock).mockReturnValue(mockedSessionStorage)
const mockedSessionStorage = mockSessionStorage()
;(fetchFromApi as jest.Mock).mockImplementation(async () => ({
errors: 'some error',
}))
Expand All @@ -159,4 +147,30 @@ describe('useFetchEligibility', () => {
// The hook response should be empty
expect(result.current[0]).toEqual([])
})
it('should expose the transaction_country returned by the API, not the requested country', async () => {
mockSessionStorage()
// The API answers with an Italian transaction while the customer addresses are French, so a
// plan carrying `IT` can only come from the response itself. Which plan it is does not matter.
const [anyEligiblePlan] = mockPlansAllEligible
const planInItaly = withCountry(anyEligiblePlan, 'IT')
;(fetchFromApi as jest.Mock).mockImplementation(async () => [planInItaly])

const { result } = renderHook(() =>
// `configPlans` is required: it is the branch of filterEligibility that rebuilds each plan
// object, and therefore the only place where the new field could get dropped.
useFetchEligibility(
45000,
{ domain: ApiMode.TEST, merchantId: 'test_id' },
configPlans,
'FR',
'FR',
),
)

await waitFor(() => {
expect(result.current[1]).toBe(statusResponse.SUCCESS)
})
Comment thread
greg-olivier marked this conversation as resolved.
expect(result.current[0]).toHaveLength(1)
expect(result.current[0][0].transaction_country).toBe('IT')
})
})
18 changes: 18 additions & 0 deletions src/test/fixtures.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mockDeferredP1XPlan, withCountry } from 'test/fixtures'

describe('withCountry', () => {
it('should return a copy of the plan with transaction_country overridden', () => {
expect(withCountry(mockDeferredP1XPlan, 'IT')).toEqual({
...mockDeferredP1XPlan,
transaction_country: 'IT',
})
})

it('should not mutate the given plan', () => {
const plan = { ...mockDeferredP1XPlan }

withCountry(plan, 'ES')

expect(plan).toEqual(mockDeferredP1XPlan)
})
})
118 changes: 118 additions & 0 deletions src/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 0,
Expand All @@ -36,6 +37,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 0,
Expand All @@ -61,6 +63,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 135,
Expand Down Expand Up @@ -93,6 +96,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 1062,
Expand Down Expand Up @@ -132,6 +136,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
annual_interest_rate: 1720,
Expand Down Expand Up @@ -224,6 +229,7 @@ export const mockPlansAllEligible: EligibilityPlan[] = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
]

Expand All @@ -245,6 +251,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 0,
Expand All @@ -263,6 +270,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 0,
Expand All @@ -288,6 +296,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 135,
Expand Down Expand Up @@ -320,6 +329,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_total_cost_amount: 1202,
Expand Down Expand Up @@ -359,6 +369,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
annual_interest_rate: 1719,
Expand Down Expand Up @@ -451,6 +462,7 @@ export const mockButtonPlans = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
]

Expand All @@ -474,6 +486,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
customer_fee: 0,
Expand Down Expand Up @@ -501,6 +514,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
},
],
purchase_amount: 45000,
transaction_country: 'FR',
},
{
constraints: { purchase_amount: { maximum: 20000, minimum: 9000 } },
Expand All @@ -509,6 +523,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
eligible: false,
installments_count: 4,
purchase_amount: 45000,
transaction_country: 'FR',
reasons: { purchase_amount: 'invalid_value' },
},
{
Expand All @@ -518,6 +533,7 @@ export const mockEligibilityPaymentPlanWithIneligiblePlan = [
eligible: false,
installments_count: 10,
purchase_amount: 45000,
transaction_country: 'FR',
reasons: { purchase_amount: 'invalid_value' },
},
]
Expand All @@ -533,6 +549,7 @@ export const mockEligibilityWithHiddenPlan = [
eligible: false,
installments_count: 4,
purchase_amount: 45000,
transaction_country: 'FR',
reasons: { installments_count: 'not_allowed' },
// No constraints.purchase_amount → plan is hidden, not grayed out
},
Expand All @@ -549,6 +566,7 @@ export const mockEligibilityWithGrayedOutPlan = [
eligible: false,
installments_count: 4,
purchase_amount: 45000,
transaction_country: 'FR',
reasons: { purchase_amount: 'invalid_value' },
// constraints.purchase_amount present → plan is grayed out, not hidden
},
Expand All @@ -569,3 +587,103 @@ export const configPlans: ConfigPlan[] = mockPlansAllEligible.map((plan) => ({
minAmount: 90_00,
maxAmount: 3350_00,
}))

// Deferred (30 days) 3x plan, without customer fees.
export const mockDeferredMultiInstallmentPlanWithoutFees: EligibilityPlan = {
customer_total_cost_amount: 0,
customer_total_cost_bps: 0,
deferred_days: 30,
deferred_months: 0,
eligible: true,
installments_count: 3,
payment_plan: [
{
customer_fee: 0,
customer_interest: 0,
due_date: 1640942762,
purchase_amount: 15000,
total_amount: 15000,
},
{
customer_fee: 0,
customer_interest: 0,
due_date: 1643621162,
purchase_amount: 15000,
total_amount: 15000,
},
{
customer_fee: 0,
customer_interest: 0,
due_date: 1646299562,
purchase_amount: 15000,
total_amount: 15000,
},
],
purchase_amount: 45000,
transaction_country: 'FR',
}

// Same plan as above, with customer fees charged on the first installment.
export const mockDeferredMultiInstallmentPlanWithFees: EligibilityPlan = {
customer_total_cost_amount: 135,
customer_total_cost_bps: 30,
deferred_days: 30,
deferred_months: 0,
eligible: true,
installments_count: 3,
payment_plan: [
{
customer_fee: 135,
customer_interest: 0,
due_date: 1640942762,
purchase_amount: 15000,
total_amount: 15135,
},
{
customer_fee: 0,
customer_interest: 0,
due_date: 1643621162,
purchase_amount: 15000,
total_amount: 15000,
},
{
customer_fee: 0,
customer_interest: 0,
due_date: 1646299562,
purchase_amount: 15000,
total_amount: 15000,
},
],
purchase_amount: 45000,
transaction_country: 'FR',
}

// Deferred P1X: a single installment paid 30 days later (pay later).
export const mockDeferredP1XPlan: EligibilityPlan = {
customer_total_cost_amount: 0,
customer_total_cost_bps: 0,
deferred_days: 30,
deferred_months: 0,
eligible: true,
installments_count: 1,
payment_plan: [
{
customer_fee: 0,
customer_interest: 0,
due_date: 1640942762,
purchase_amount: 45000,
total_amount: 45000,
},
],
purchase_amount: 45000,
transaction_country: 'FR',
}

/**
* Returns a copy of `plan` with its `transaction_country` overridden.
* The input plan is never mutated, so shared fixtures stay reusable across tests.
*/
export const withCountry = (plan: EligibilityPlan, countryCode: string): EligibilityPlan => ({
...plan,
transaction_country: countryCode,
})
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type EligibilityPlan = {
constraints?: {
purchase_amount?: { minimum: number; maximum: number }
}
transaction_country: string
}

export type ErrorResponse = {
Expand Down
Loading