Skip to content

Commit

Permalink
refactor: Use discrete list of passport variables only
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr committed Dec 5, 2024
1 parent 41eb904 commit dbd4569
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export interface FeeBreakdown {
}

export interface PassportFeeFields {
amount: {
"application.fee.calculated": number;
"application.fee.payable": number;
"application.fee.payable.vat": number;
},
reductions?: Record<string, boolean>
exemptions?: Record<string, boolean>
"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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ describe("useFeeBreakdown() hook", () => {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.reduction.reasonOne": ["true"],
"application.fee.reduction.reasonTwo": ["true"],
"application.fee.reduction.alternative": ["true"],
"application.fee.reduction.parishCouncil": ["true"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -80,7 +80,7 @@ describe("useFeeBreakdown() hook", () => {

expect(result?.reductions).toHaveLength(2);
expect(result?.reductions).toEqual(
expect.arrayContaining(["reasonOne", "reasonTwo"])
expect.arrayContaining(["alternative", "parishCouncil"])
);
});

Expand All @@ -89,8 +89,8 @@ describe("useFeeBreakdown() hook", () => {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.reduction.reasonOne": ["false"],
"application.fee.reduction.reasonTwo": ["false"],
"application.fee.reduction.alternative": ["false"],
"application.fee.reduction.parishCouncil": ["false"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -101,13 +101,36 @@ describe("useFeeBreakdown() hook", () => {
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"],
};

vi.mocked(useStore).mockReturnValue([
mockPassportData,
"test-session",
]);

const result = useFeeBreakdown();

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.reasonOne": ["true"],
"application.fee.exemption.reasonTwo": ["true"],
"application.fee.exemption.disability": ["true"],
"application.fee.exemption.resubmission": ["true"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -117,7 +140,7 @@ describe("useFeeBreakdown() hook", () => {

expect(result?.exemptions).toHaveLength(2);
expect(result?.exemptions).toEqual(
expect.arrayContaining(["reasonOne", "reasonTwo"])
expect.arrayContaining(["disability", "resubmission"])
);
});

Expand All @@ -126,8 +149,8 @@ describe("useFeeBreakdown() hook", () => {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.exemption.reasonOne": ["false"],
"application.fee.exemption.reasonTwo": ["false"],
"application.fee.exemption.disability": ["false"],
"application.fee.exemption.resubmission": ["false"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -137,6 +160,33 @@ describe("useFeeBreakdown() hook", () => {

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"],
};

vi.mocked(useStore).mockReturnValue([
mockPassportData,
"test-session",
]);

const result = useFeeBreakdown();

expect(result?.exemption).toEqual(
expect.not.arrayContaining(["someReason"])
);
expect(result?.exemption).toEqual(
expect.not.arrayContaining(["someOtherReason"])
);
});
});

describe("invalid inputs", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { logger } from "airbrake";
import { useStore } from "pages/FlowEditor/lib/store";

import { FeeBreakdown } from "./types";
import { createPassportSchema, preProcessPassport } from "./utils";
import { createPassportSchema } from "./utils";

/**
* Parses the users's Passport for data variables associated with their fee
Expand All @@ -19,8 +19,7 @@ export const useFeeBreakdown = (): FeeBreakdown | undefined => {
if (!passportData) return

const schema = createPassportSchema();
const processedPassport = preProcessPassport(passportData);
const result = schema.safeParse(processedPassport);
const result = schema.safeParse(passportData);

if (!result.success) {
logger.notify(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ describe("toNumber() helper function", () => {
describe("calculateReduction() helper function", () => {
it("correctly outputs the reduction when a calculated value is provided", () => {
const input: PassportFeeFields = {
amount: {
"application.fee.calculated": 100,
"application.fee.payable": 50,
"application.fee.payable.vat": 0,
}
"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);

Expand All @@ -33,11 +36,14 @@ describe("calculateReduction() helper function", () => {

it("defaults to 0 when calculated is 0", () => {
const input: PassportFeeFields = {
amount: {
"application.fee.calculated": 0,
"application.fee.payable": 100,
"application.fee.payable.vat": 0,
}
"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);

Expand All @@ -48,32 +54,38 @@ describe("calculateReduction() helper function", () => {
describe("toFeeBreakdown() helper function", () => {
it("correctly maps fields", () => {
const input: PassportFeeFields = {
amount: {
"application.fee.calculated": 100,
"application.fee.payable": 50,
"application.fee.payable.vat": 10,
}
"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.applicationFee).toEqual(input.amount["application.fee.calculated"]);
expect(amount.total).toEqual(input.amount["application.fee.payable"]);
expect(amount.vat).toEqual(input.amount["application.fee.payable.vat"]);
expect(amount.applicationFee).toEqual(input["application.fee.calculated"]);
expect(amount.total).toEqual(input["application.fee.payable"]);
expect(amount.vat).toEqual(input["application.fee.payable.vat"]);
expect(amount.reduction).toEqual(50);
});

it("sets applicationFee to payable amount if no calculated value is provided", () => {
const input: PassportFeeFields = {
amount : {
"application.fee.calculated": 0,
"application.fee.payable.vat": 10,
"application.fee.payable": 50,
}
"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.applicationFee).toEqual(input.amount["application.fee.payable"]);
expect(amount.applicationFee).toEqual(input["application.fee.payable"]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@ export const toNumber = (input: number | [number]) =>
*/
const toBoolean = (val: ["true" | "false"]) => val[0] === "true";

const filterByKeyPrefix = (data: Record<string, unknown>, prefix: string) =>
Object.fromEntries(
Object.entries(data).filter(([k, _v]) => k.startsWith(prefix))
);

/**
* Iterate over exemptions or reductions to find matches, returning the granular keys
*/
const getGranularKeys = (
data: Record<string, boolean> | undefined = {},
prefix: "application.fee.reduction" | "application.fee.exemption"
) => {
const keys = Object.keys(data).filter((key) => data[key]);
const granularKeys = keys.map((key) => key.replace(prefix + ".", ""));
return granularKeys;
};
data: PassportFeeFields,
prefix: "application.fee.reduction" | "application.fee.exemption"
) => {
const keys = Object.keys(data) as (keyof PassportFeeFields)[];
const intersectingKeys = keys.filter(
(key) => key.startsWith(prefix) && Boolean(data[key])
);
const granularKeys = intersectingKeys.map((key) =>
key.replace(prefix + ".", "")
);

return granularKeys;
};

/**
* A "reduction" is the sum of the difference between calculated and payable
*/
export const calculateReduction = ({ amount }: PassportFeeFields) =>
amount["application.fee.calculated"]
? amount["application.fee.calculated"] - amount["application.fee.payable"]
export const calculateReduction = (data: PassportFeeFields) =>
data["application.fee.calculated"]
? data["application.fee.calculated"] - data["application.fee.payable"]
: 0;

/**
Expand All @@ -41,20 +43,14 @@ export const calculateReduction = ({ amount }: PassportFeeFields) =>
export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({
amount: {
applicationFee:
data.amount["application.fee.calculated"] ||
data.amount["application.fee.payable"],
total: data.amount["application.fee.payable"],
vat: data.amount["application.fee.payable.vat"],
data["application.fee.calculated"] ||
data["application.fee.payable"],
total: data["application.fee.payable"],
vat: data["application.fee.payable.vat"],
reduction: calculateReduction(data),
},
reductions: getGranularKeys(data.reductions, "application.fee.reduction"),
exemptions: getGranularKeys(data.exemptions, "application.fee.exemption"),
});

export const preProcessPassport = (data: Record<string, unknown>) => ({
amount: filterByKeyPrefix(data, "application.fee"),
reductions: filterByKeyPrefix(data, "application.fee.reduction"),
exemptions: filterByKeyPrefix(data, "application.fee.exemption"),
reductions: getGranularKeys(data, "application.fee.reduction"),
exemptions: getGranularKeys(data, "application.fee.exemption"),
});

export const createPassportSchema = () => {
Expand All @@ -64,31 +60,22 @@ export const createPassportSchema = () => {
.union([questionSchema, setValueSchema])
.transform(toNumber);

const amountsSchema = z.object({
"application.fee.calculated": feeSchema.optional().default(0),
"application.fee.payable": feeSchema,
"application.fee.payable.vat": feeSchema.optional().default(0),
});

const reductionsSchema = z
.record(
z.string(),
z.tuple([z.enum(["true", "false"])]).transform(toBoolean)
)
.optional();

const exemptionsSchema = z
.record(
z.string(),
z.tuple([z.enum(["true", "false"])]).transform(toBoolean)
)
.optional();
/** Describes how boolean values are set via PlanX components */
const booleanSchema = z
.tuple([z.enum(["true", "false"])])
.default(["false"])
.transform(toBoolean)

const schema = z
.object({
amount: amountsSchema,
reductions: reductionsSchema,
exemptions: exemptionsSchema,
"application.fee.calculated": feeSchema.optional().default(0),
"application.fee.payable": feeSchema,
"application.fee.payable.vat": feeSchema.optional().default(0),
"application.fee.reduction.alternative": booleanSchema,
"application.fee.reduction.parishCouncil": booleanSchema,
"application.fee.reduction.sports": booleanSchema,
"application.fee.exemption.disability": booleanSchema,
"application.fee.exemption.resubmission": booleanSchema,
})
.transform(toFeeBreakdown);

Expand Down

0 comments on commit dbd4569

Please sign in to comment.