Skip to content

Commit

Permalink
Improve unexpected field handling (#105)
Browse files Browse the repository at this point in the history
* add IncomeHelper class, allows for using the correct income based on marital status
* remove forbidden field check and related tests
  • Loading branch information
JeremyKennedy authored Jan 19, 2022
1 parent 6ba8a55 commit ff3234c
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 93 deletions.
55 changes: 0 additions & 55 deletions __tests__/pages/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,61 +210,6 @@ describe('sanity checks', () => {
})
expect(res.status).toEqual(200)
})
it('fails when not partnered and "partnerBenefitStatus" FULL_OAS_GIS', async () => {
let res = await mockGetRequestError({
age: 65,
maritalStatus: MaritalStatus.SINGLE,
partnerBenefitStatus: PartnerBenefitStatus.FULL_OAS_GIS,
})
expect(res.body.error).toEqual(ResultKey.INVALID)
expect(res.status).toEqual(400)
})
it('fails when not partnered and "partnerBenefitStatus" NONE', async () => {
let res = await mockGetRequestError({
age: 65,
maritalStatus: MaritalStatus.SINGLE,
partnerBenefitStatus: PartnerBenefitStatus.NONE,
})
expect(res.body.error).toEqual(ResultKey.INVALID)
expect(res.status).toEqual(400)
})
it('accepts when partnered and "partnerReceivingOas" present', async () => {
const res = await mockPartialGetRequest({
age: 65,
maritalStatus: MaritalStatus.MARRIED,
partnerBenefitStatus: PartnerBenefitStatus.FULL_OAS_GIS,
})
expect(res.status).toEqual(200)
})
it('fails when not partnered and "partnerIncome" provided', async () => {
let res = await mockGetRequestError({
income: 10000,
age: 65,
maritalStatus: MaritalStatus.SINGLE,
partnerIncome: 10000,
})
expect(res.status).toEqual(400)
expect(res.body.error).toEqual(ResultKey.INVALID)
})
it('accepts not partnered and "partnerIncome" provided', async () => {
let res = await mockGetRequestError({
income: 10000,
age: 65,
maritalStatus: MaritalStatus.MARRIED,
partnerIncome: 10000,
})
expect(res.status).toEqual(200)
})
it('fails when lifeCanada=true and "yearsInCanadaSince18" provided', async () => {
let res = await mockGetRequestError({
income: 10000,
age: 65,
canadaWholeLife: true,
yearsInCanadaSince18: 20,
})
expect(res.status).toEqual(400)
expect(res.body.error).toEqual(ResultKey.INVALID)
})
})

describe('field requirement analysis', () => {
Expand Down
4 changes: 2 additions & 2 deletions utils/api/benefits/checkAfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function checkAfs(input: ProcessedInput): BenefitResult {
const meetsReqAge = 60 <= input.age && input.age <= 64
const overAgeReq = 65 <= input.age
const underAgeReq = input.age < 60
const meetsReqIncome = input.income < MAX_AFS_INCOME
const meetsReqIncome = input.income.relevant < MAX_AFS_INCOME
const requiredYearsInCanada = 10
const meetsReqYears = input.yearsInCanadaSince18 >= requiredYearsInCanada
const meetsReqLegal = input.legalStatus.canadian
Expand All @@ -19,7 +19,7 @@ export default function checkAfs(input: ProcessedInput): BenefitResult {
if (meetsReqLegal && meetsReqYears && meetsReqMarital && meetsReqIncome) {
if (meetsReqAge) {
const entitlementResult = new AfsEntitlement(
input.income
input.income.relevant
).getEntitlement()
return {
eligibilityResult: ResultKey.ELIGIBLE,
Expand Down
4 changes: 2 additions & 2 deletions utils/api/benefits/checkAllowance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function checkAllowance(input: ProcessedInput): BenefitResult {
const meetsReqAge = 60 <= input.age && input.age <= 64
const overAgeReq = 65 <= input.age
const underAgeReq = input.age < 60
const meetsReqIncome = input.income < MAX_ALLOWANCE_INCOME
const meetsReqIncome = input.income.relevant < MAX_ALLOWANCE_INCOME
const requiredYearsInCanada = 10
const meetsReqYears = input.yearsInCanadaSince18 >= requiredYearsInCanada
const meetsReqLegal = input.legalStatus.canadian
Expand All @@ -27,7 +27,7 @@ export default function checkAllowance(input: ProcessedInput): BenefitResult {
) {
if (meetsReqAge) {
const entitlementResult = new AllowanceEntitlement(
input.income
input.income.relevant
).getEntitlement()
return {
eligibilityResult: ResultKey.ELIGIBLE,
Expand Down
4 changes: 2 additions & 2 deletions utils/api/benefits/checkGis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function checkGis(input: ProcessedInput): BenefitResult {
? MAX_GIS_INCOME_PARTNER_OAS
: MAX_GIS_INCOME_PARTNER_NO_OAS_NO_ALLOWANCE
: MAX_GIS_INCOME_SINGLE
const meetsReqIncome = input.income < maxIncome
const meetsReqIncome = input.income.relevant < maxIncome

// main checks
if (meetsReqIncome && meetsReqLiving && meetsReqOas && meetsReqLegal) {
Expand All @@ -49,7 +49,7 @@ export default function checkGis(input: ProcessedInput): BenefitResult {
}
} else {
const entitlementResult = new GisEntitlement(
input.income,
input.income.relevant,
oasResultIsPartial,
input.maritalStatus,
input.partnerBenefitStatus
Expand Down
2 changes: 1 addition & 1 deletion utils/api/benefits/checkOas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { BenefitResult, ProcessedInput } from '../definitions/types'
export default function checkOas(input: ProcessedInput): BenefitResult {
// helpers
const meetsReqAge = input.age >= 65
const meetsReqIncome = input.income < MAX_OAS_INCOME
const meetsReqIncome = input.income.relevant < MAX_OAS_INCOME
const requiredYearsInCanada = input.livingCountry.canada ? 10 : 20
const meetsReqYears = input.yearsInCanadaSince18 >= requiredYearsInCanada
const meetsReqLegal = input.legalStatus.canadian
Expand Down
27 changes: 6 additions & 21 deletions utils/api/definitions/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,17 @@ export const RequestSchema = Joi.object({
maritalStatus: Joi.string().valid(...Object.values(MaritalStatus)),
livingCountry: Joi.string().valid(...Object.values(ALL_COUNTRY_CODES)),
legalStatus: Joi.string().valid(...Object.values(LegalStatus)),
legalStatusOther: Joi.string().when('legalStatus', {
not: Joi.exist().valid(LegalStatus.OTHER),
then: Joi.forbidden(),
}),
legalStatusOther: Joi.string(),
canadaWholeLife: Joi.boolean(),
yearsInCanadaSince18: Joi.number()
.integer()
.ruleset.max(Joi.ref('age', { adjust: (age) => age - 18 }))
.message('Years in Canada should be no more than age minus 18') // todo i18n
.when('canadaWholeLife', {
is: Joi.boolean().valid(true),
then: Joi.forbidden(),
}),
.message('Years in Canada should be no more than age minus 18'), // todo i18n
everLivedSocialCountry: Joi.boolean(),
partnerBenefitStatus: Joi.string()
.valid(...Object.values(PartnerBenefitStatus))
.when('maritalStatus', {
not: Joi.exist().valid(MaritalStatus.MARRIED, MaritalStatus.COMMON_LAW),
then: Joi.forbidden(),
}),
partnerIncome: Joi.number()
.integer()
.when('maritalStatus', {
not: Joi.exist().valid(MaritalStatus.MARRIED, MaritalStatus.COMMON_LAW),
then: Joi.forbidden(),
}),
partnerBenefitStatus: Joi.string().valid(
...Object.values(PartnerBenefitStatus)
),
partnerIncome: Joi.number().integer(),
_language: Joi.string()
.valid(...Object.values(Language))
.default(Language.EN),
Expand Down
3 changes: 2 additions & 1 deletion utils/api/definitions/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Language, Translations } from '../../../i18n/api'
import {
IncomeHelper,
LegalStatusHelper,
LivingCountryHelper,
MaritalStatusHelper,
Expand Down Expand Up @@ -37,7 +38,7 @@ export interface RequestInput {
* After Joi validation and additional pre-processing, this is the object passed around to provide app logic.
*/
export interface ProcessedInput {
income?: number // sum of personal and partner
income: IncomeHelper
age?: number
maritalStatus: MaritalStatusHelper
livingCountry: LivingCountryHelper
Expand Down
31 changes: 31 additions & 0 deletions utils/api/helpers/fieldClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,37 @@ export class FieldHelper {
}
}

export class IncomeHelper extends FieldHelper {
constructor(
public client: number,
public partner: number,
public maritalStatus: MaritalStatusHelper
) {
super(client === undefined ? undefined : -1) // send either undefined or -1, as we should never use this property
}

/**
* Returns the relevant income, depending on marital status.
* Returns the client's income when single, or the sum of client+partner when partnered.
*/
get relevant(): number {
if (
this.maritalStatus.provided &&
this.maritalStatus.partnered &&
this.partner !== undefined
) {
return this.sum
}
return this.client
}

get sum(): number {
const a = this.client ? this.client : 0
const b = this.partner ? this.partner : 0
return a + b
}
}

export class LivingCountryHelper extends FieldHelper {
normalized: LivingCountry
canada: boolean
Expand Down
19 changes: 12 additions & 7 deletions utils/api/helpers/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '../definitions/types'
import {
FieldHelper,
IncomeHelper,
LegalStatusHelper,
LivingCountryHelper,
MaritalStatusHelper,
Expand Down Expand Up @@ -69,19 +70,23 @@ export class RequestHandler {
static processSanitizedInput(sanitizedInput: RequestInput): ProcessedInput {
const translations = getTranslations(sanitizedInput._language)

const maritalStatusHelper = new MaritalStatusHelper(
sanitizedInput.maritalStatus
)
return {
...sanitizedInput,
// adds client and partner income into a single combined income
income: sanitizedInput.partnerIncome
? sanitizedInput.income + sanitizedInput.partnerIncome
: sanitizedInput.income,
income: new IncomeHelper(
sanitizedInput.income,
sanitizedInput.partnerIncome,
maritalStatusHelper
),
// if canadaWholeLife, assume yearsInCanadaSince18 is 40
yearsInCanadaSince18: sanitizedInput.canadaWholeLife
? 40
: sanitizedInput.yearsInCanadaSince18,
livingCountry: new LivingCountryHelper(sanitizedInput.livingCountry),
legalStatus: new LegalStatusHelper(sanitizedInput.legalStatus),
maritalStatus: new MaritalStatusHelper(sanitizedInput.maritalStatus),
maritalStatus: maritalStatusHelper,
partnerBenefitStatus: new PartnerBenefitStatusHelper(
sanitizedInput.partnerBenefitStatus
),
Expand All @@ -94,10 +99,10 @@ export class RequestHandler {
*/
static getRequiredFields(input: ProcessedInput): FieldKey[] {
const requiredFields = [FieldKey.INCOME]
if (input.income >= MAX_OAS_INCOME) {
if (input.income.client >= MAX_OAS_INCOME) {
// over highest income, therefore don't need anything else
return requiredFields
} else if (input.income < MAX_OAS_INCOME) {
} else if (input.income.client < MAX_OAS_INCOME) {
// meets max income req, open up main form
requiredFields.push(
FieldKey.AGE,
Expand Down
4 changes: 2 additions & 2 deletions utils/api/helpers/summaryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export class SummaryBuilder {
if (this.results.afs?.eligibilityResult === ResultKey.ELIGIBLE)
links.push(this.translations.links.afsEntitlement)
if (
this.input.income > OAS_RECOVERY_TAX_CUTOFF &&
this.input.income < MAX_OAS_INCOME
this.input.income.relevant > OAS_RECOVERY_TAX_CUTOFF &&
this.input.income.relevant < MAX_OAS_INCOME
)
links.push(this.translations.links.oasRecoveryTax)
if (this.results.oas?.eligibilityResult === ResultKey.ELIGIBLE)
Expand Down

0 comments on commit ff3234c

Please sign in to comment.