-
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.
chore: Move fee breakdown logic (#578)
- Loading branch information
1 parent
3e48c64
commit 427b5cc
Showing
5 changed files
with
395 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,21 @@ | ||
export interface FeeBreakdown { | ||
amount: { | ||
calculated: number; | ||
payable: number; | ||
reduction: number; | ||
vat: number; | ||
}; | ||
reductions: string[]; | ||
exemptions: string[]; | ||
} | ||
|
||
export interface PassportFeeFields { | ||
"application.fee.calculated": number; | ||
"application.fee.payable": number; | ||
"application.fee.payable.vat": number; | ||
"application.fee.reduction.alternative": boolean; | ||
"application.fee.reduction.parishCouncil": boolean; | ||
"application.fee.reduction.sports": boolean; | ||
"application.fee.exemption.disability": boolean; | ||
"application.fee.exemption.resubmission": boolean; | ||
} |
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
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,285 @@ | ||
import { PassportFeeFields } from "../types"; | ||
import { | ||
calculateReduction, | ||
getFeeBreakdown, | ||
toFeeBreakdown, | ||
toNumber, | ||
} from "./feeBreakdown"; | ||
|
||
describe("toNumber() helper function", () => { | ||
it("outputs a number when passed a number", () => { | ||
const input = 12; | ||
const output = toNumber(input); | ||
|
||
expect(output).toEqual(input); | ||
}); | ||
|
||
it("outputs a number when passed a number tuple", () => { | ||
const input: [number] = [12]; | ||
const output = toNumber(input); | ||
|
||
expect(output).toEqual(12); | ||
}); | ||
}); | ||
|
||
describe("calculateReduction() helper function", () => { | ||
it("correctly outputs the reduction when a calculated value is provided", () => { | ||
const input: PassportFeeFields = { | ||
"application.fee.calculated": 100, | ||
"application.fee.payable": 50, | ||
"application.fee.payable.vat": 0, | ||
"application.fee.reduction.alternative": false, | ||
"application.fee.reduction.parishCouncil": false, | ||
"application.fee.reduction.sports": false, | ||
"application.fee.exemption.disability": false, | ||
"application.fee.exemption.resubmission": false, | ||
}; | ||
const reduction = calculateReduction(input); | ||
|
||
expect(reduction).toEqual(50); | ||
}); | ||
|
||
it("defaults to 0 when calculated is 0", () => { | ||
const input: PassportFeeFields = { | ||
"application.fee.calculated": 0, | ||
"application.fee.payable": 100, | ||
"application.fee.payable.vat": 0, | ||
"application.fee.reduction.alternative": false, | ||
"application.fee.reduction.parishCouncil": false, | ||
"application.fee.reduction.sports": false, | ||
"application.fee.exemption.disability": false, | ||
"application.fee.exemption.resubmission": false, | ||
}; | ||
const reduction = calculateReduction(input); | ||
|
||
expect(reduction).toEqual(0); | ||
}); | ||
}); | ||
|
||
describe("toFeeBreakdown() helper function", () => { | ||
it("correctly maps fields", () => { | ||
const input: PassportFeeFields = { | ||
"application.fee.calculated": 100, | ||
"application.fee.payable": 50, | ||
"application.fee.payable.vat": 10, | ||
"application.fee.reduction.alternative": false, | ||
"application.fee.reduction.parishCouncil": false, | ||
"application.fee.reduction.sports": false, | ||
"application.fee.exemption.disability": false, | ||
"application.fee.exemption.resubmission": false, | ||
}; | ||
|
||
const { amount } = toFeeBreakdown(input); | ||
|
||
expect(amount.calculated).toEqual(input["application.fee.calculated"]); | ||
expect(amount.payable).toEqual(input["application.fee.payable"]); | ||
expect(amount.vat).toEqual(input["application.fee.payable.vat"]); | ||
expect(amount.reduction).toEqual(50); | ||
}); | ||
|
||
it("sets calculated to payable amount if no calculated value is provided", () => { | ||
const input: PassportFeeFields = { | ||
"application.fee.calculated": 0, | ||
"application.fee.payable.vat": 10, | ||
"application.fee.payable": 50, | ||
"application.fee.reduction.alternative": false, | ||
"application.fee.reduction.parishCouncil": false, | ||
"application.fee.reduction.sports": false, | ||
"application.fee.exemption.disability": false, | ||
"application.fee.exemption.resubmission": false, | ||
}; | ||
|
||
const { amount } = toFeeBreakdown(input); | ||
|
||
expect(amount.calculated).toEqual(input["application.fee.payable"]); | ||
}); | ||
}); | ||
|
||
describe("getFeeBreakdown() function", () => { | ||
describe("valid data", () => { | ||
it("returns a fee breakdown for number inputs", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result).toEqual({ | ||
amount: { | ||
calculated: 1000, | ||
payable: 800, | ||
reduction: 200, | ||
vat: 160, | ||
}, | ||
exemptions: [], | ||
reductions: [], | ||
}); | ||
}); | ||
|
||
it("returns a fee breakdown for number tuple inputs", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": [1000], | ||
"application.fee.payable": [800], | ||
"application.fee.payable.vat": [160], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result).toEqual({ | ||
amount: { | ||
calculated: 1000, | ||
payable: 800, | ||
reduction: 200, | ||
vat: 160, | ||
}, | ||
exemptions: [], | ||
reductions: [], | ||
}); | ||
}); | ||
|
||
it("parses 'true' reduction values to a list of keys", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.reduction.alternative": ["true"], | ||
"application.fee.reduction.parishCouncil": ["true"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.reductions).toHaveLength(2); | ||
expect(result?.reductions).toEqual( | ||
expect.arrayContaining(["alternative", "parishCouncil"]), | ||
); | ||
}); | ||
|
||
it("does not parse 'false' reduction values to a list of keys", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.reduction.alternative": ["false"], | ||
"application.fee.reduction.parishCouncil": ["false"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.reductions).toHaveLength(0); | ||
}); | ||
|
||
it("does not parse non-schema reduction values", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.reduction.alternative": ["true"], | ||
"application.fee.reduction.parishCouncil": ["false"], | ||
"application.fee.reduction.someReason": ["true"], | ||
"application.fee.reduction.someOtherReason": ["false"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.reductions).toEqual( | ||
expect.not.arrayContaining(["someReason"]), | ||
); | ||
expect(result?.reductions).toEqual( | ||
expect.not.arrayContaining(["someOtherReason"]), | ||
); | ||
}); | ||
|
||
it("parses 'true' exemption values to a list of keys", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.exemption.disability": ["true"], | ||
"application.fee.exemption.resubmission": ["true"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.exemptions).toHaveLength(2); | ||
expect(result?.exemptions).toEqual( | ||
expect.arrayContaining(["disability", "resubmission"]), | ||
); | ||
}); | ||
|
||
it("does not parse 'false' exemption values to a list of keys", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.exemption.disability": ["false"], | ||
"application.fee.exemption.resubmission": ["false"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.exemptions).toHaveLength(0); | ||
}); | ||
|
||
it("does not parse non-schema exemption values", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": 1000, | ||
"application.fee.payable": 800, | ||
"application.fee.payable.vat": 160, | ||
"application.fee.exemption.disability": ["false"], | ||
"application.fee.exemption.resubmission": ["false"], | ||
"application.fee.exemption.someReason": ["true"], | ||
"application.fee.exemption.someOtherReason": ["false"], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
const result = getFeeBreakdown(mockPassportData); | ||
|
||
expect(result?.exemptions).toEqual( | ||
expect.not.arrayContaining(["someReason"]), | ||
); | ||
expect(result?.exemptions).toEqual( | ||
expect.not.arrayContaining(["someOtherReason"]), | ||
); | ||
}); | ||
}); | ||
|
||
describe("invalid inputs", () => { | ||
it("returns undefined for missing data", () => { | ||
const mockPassportData = { | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
expect(() => getFeeBreakdown(mockPassportData)).toThrow(); | ||
}); | ||
|
||
it("returns undefined for partial data", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": [1000], | ||
"application.fee.payable.vat": [160], | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
expect(() => getFeeBreakdown(mockPassportData)).toThrow(); | ||
}); | ||
|
||
it("returns undefined for incorrect data", () => { | ||
const mockPassportData = { | ||
"application.fee.calculated": "some string", | ||
"application.fee.payable": [800, 700], | ||
"application.fee.payable.vat": false, | ||
"some.other.fields": ["abc", "xyz"], | ||
}; | ||
|
||
expect(() => getFeeBreakdown(mockPassportData)).toThrow(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.