Skip to content

Commit

Permalink
unit tests for joi validation schema
Browse files Browse the repository at this point in the history
  • Loading branch information
sanjaytkbabu committed May 16, 2024
1 parent 9788827 commit aa8498a
Show file tree
Hide file tree
Showing 13 changed files with 521 additions and 73 deletions.
10 changes: 8 additions & 2 deletions app/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const NOTE_TYPE_LIST = Object.freeze({
* @see {@link https://emailregex.com/}
*/
export const EMAIL_REGEX = '^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]{2,})+$';

export const PHONE_NUMBER_REGEX = '^(\\+\\d{1,2}\\s?)?\\(?\\d{3}\\)?[\\s.-]?\\d{3}[\\s.-]?\\d{4}$';
/**
* Basic
*/
Expand Down Expand Up @@ -107,7 +107,13 @@ export const NUM_RESIDENTIAL_UNITS = Object.freeze({
});

/**
* Mac character length for input text boxes
* Max character length for input text boxes
*/

export const TEXT_MAX_LENGTH = 255;

/**
* Max character length for phone numbers
*/

export const PHONE_NUMBER_MAX_LENGTH = 10;
13 changes: 7 additions & 6 deletions app/src/validators/applicant.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Joi from 'joi';
import { emailJoi, phoneNumberJoi, stringRequiredMaxLengthTrim } from './common';

export const applicantSchema = Joi.object({
contactPreference: Joi.string().max(255).required(),
email: Joi.string().email().required(),
firstName: Joi.string().max(255).required(),
lastName: Joi.string().max(255).required(),
phoneNumber: Joi.string().max(255).required(),
relationshipToProject: Joi.string().max(255).required()
contactPreference: stringRequiredMaxLengthTrim,
email: emailJoi.required(),
firstName: stringRequiredMaxLengthTrim,
lastName: stringRequiredMaxLengthTrim,
phoneNumber: phoneNumberJoi.required(),
relationshipToProject: stringRequiredMaxLengthTrim
});
2 changes: 1 addition & 1 deletion app/src/validators/appliedPermits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export const appliedPermitsSchema = Joi.object({
status: Joi.string()
.valid(...Object.values(PERMIT_STATUS))
.allow(null),
statusLastVerified: Joi.date().allow(null),
statusLastVerified: Joi.date().max('now').allow(null),
trackingId: Joi.string().allow(null)
});
13 changes: 5 additions & 8 deletions app/src/validators/basic.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import Joi from 'joi';

import { stringRequiredMaxLengthTrim, stringRequiredYesNo } from './common';
import { YES_NO } from '../components/constants';

export const basicSchema = Joi.object({
isDevelopedByCompanyOrOrg: Joi.string()
.valid(...Object.values(YES_NO))
.required(),
isDevelopedInBC: Joi.string()
.valid(...Object.values(YES_NO))
.required(),
registeredName: Joi.string().when('isDevelopedInBC', {
isDevelopedByCompanyOrOrg: stringRequiredYesNo,
isDevelopedInBC: stringRequiredYesNo,
registeredName: Joi.when('isDevelopedInBC', {
is: YES_NO.YES,
then: Joi.string().max(255).required(),
then: stringRequiredMaxLengthTrim,
otherwise: Joi.forbidden()
})
});
23 changes: 21 additions & 2 deletions app/src/validators/common.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import Joi from 'joi';

import { EMAIL_REGEX } from '../components/constants';
import {
EMAIL_REGEX,
PHONE_NUMBER_REGEX,
PHONE_NUMBER_MAX_LENGTH,
TEXT_MAX_LENGTH,
YES_NO,
YES_NO_UNSURE
} from '../components/constants';

export const activityId = Joi.string().min(8).max(8).required();

export const emailJoi = Joi.string().pattern(new RegExp(EMAIL_REGEX)).max(255);
export const emailJoi = Joi.string().pattern(new RegExp(EMAIL_REGEX)).max(TEXT_MAX_LENGTH);

export const uuidv4 = Joi.string().guid({ version: 'uuidv4' });

export const phoneNumberJoi = Joi.string().regex(new RegExp(PHONE_NUMBER_REGEX)).max(PHONE_NUMBER_MAX_LENGTH);

export const stringRequiredMaxLengthTrim = Joi.string().max(TEXT_MAX_LENGTH).required().trim();

export const stringRequiredYesNo = Joi.string()
.valid(...Object.values(YES_NO))
.required();

export const stringRequiredYesNoUnsure = Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required();
75 changes: 31 additions & 44 deletions app/src/validators/housing.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,61 @@
import Joi from 'joi';

import { NUM_RESIDENTIAL_UNITS, TEXT_MAX_LENGTH, YES_NO, YES_NO_UNSURE } from '../components/constants';
import { NUM_RESIDENTIAL_UNITS, TEXT_MAX_LENGTH, YES_NO } from '../components/constants';
import { stringRequiredMaxLengthTrim, stringRequiredYesNoUnsure } from './common';

const stringRequiredNumResidentialUnits = Joi.string()
.valid(...Object.values(NUM_RESIDENTIAL_UNITS))
.required();

export const housingSchema = Joi.object({
financiallySupportedBC: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required(),
financiallySupportedIndigenous: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required(),
financiallySupportedNonProfit: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required(),
financiallySupportedHousingCoop: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required(),
hasRentalUnits: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required(),
housingCoopDescription: Joi.string().when('financiallySupportedHousingCoop', {
financiallySupportedBC: stringRequiredYesNoUnsure,
financiallySupportedIndigenous: stringRequiredYesNoUnsure,
financiallySupportedNonProfit: stringRequiredYesNoUnsure,
financiallySupportedHousingCoop: stringRequiredYesNoUnsure,
hasRentalUnits: stringRequiredYesNoUnsure,
housingCoopDescription: Joi.when('financiallySupportedHousingCoop', {
is: YES_NO.YES,
then: Joi.string().max(TEXT_MAX_LENGTH).required(),
then: stringRequiredMaxLengthTrim,
otherwise: Joi.forbidden()
}),
indigenousDescription: Joi.string().when('financiallySupportedIndigenous', {
indigenousDescription: Joi.when('financiallySupportedIndigenous', {
is: YES_NO.YES,
then: Joi.string().max(TEXT_MAX_LENGTH).required(),
then: stringRequiredMaxLengthTrim,
otherwise: Joi.forbidden()
}),
multiFamilySelected: Joi.boolean().allow(null),
multiFamilyUnits: Joi.string().when('multiFamilySelected', {
is: Joi.exist(),
then: Joi.string()
.valid(...Object.values(NUM_RESIDENTIAL_UNITS))
.required(),
multiFamilyUnits: Joi.when('multiFamilySelected', {
is: true,
then: stringRequiredNumResidentialUnits,
otherwise: Joi.forbidden()
}),
nonProfitDescription: Joi.string().when('financiallySupportedNonProfit', {
nonProfitDescription: Joi.when('financiallySupportedNonProfit', {
is: YES_NO.YES,
then: Joi.string().max(TEXT_MAX_LENGTH).required(),
then: stringRequiredMaxLengthTrim,
otherwise: Joi.forbidden()
}),
otherSelected: Joi.boolean().allow(null),
otherUnits: Joi.string().when('otherSelected', {
is: Joi.exist(),
then: Joi.string()
.valid(...Object.values(NUM_RESIDENTIAL_UNITS))
.required(),
otherUnits: Joi.when('otherSelected', {
is: true,
then: stringRequiredNumResidentialUnits,
otherwise: Joi.forbidden()
}),
otherUnitsDescription: Joi.when('otherSelected', {
is: Joi.exist(),
then: Joi.string().trim().max(TEXT_MAX_LENGTH).required(),
then: stringRequiredMaxLengthTrim,
otherwise: Joi.forbidden()
}),
projectName: Joi.string().max(TEXT_MAX_LENGTH).required(),
projectName: stringRequiredMaxLengthTrim,
projectDescription: Joi.string().max(TEXT_MAX_LENGTH).allow(null),
rentalUnits: Joi.string().when('hasRentalUnits', {
rentalUnits: Joi.when('hasRentalUnits', {
is: YES_NO.YES,
then: Joi.string()
.valid(...Object.values(NUM_RESIDENTIAL_UNITS))
.required(),
then: stringRequiredNumResidentialUnits,
otherwise: Joi.forbidden()
}),
singleFamilySelected: Joi.boolean().allow(null),
singleFamilyUnits: Joi.string().when('singleFamilySelected', {
is: Joi.exist(),
then: Joi.string()
.valid(...Object.values(NUM_RESIDENTIAL_UNITS))
.required(),
singleFamilyUnits: Joi.when('singleFamilySelected', {
is: true,
then: stringRequiredNumResidentialUnits,
otherwise: Joi.forbidden()
})
}).xor('singleFamilySelected', 'multiFamilySelected', 'otherSelected');
}).or('singleFamilySelected', 'multiFamilySelected', 'otherSelected');
15 changes: 5 additions & 10 deletions app/src/validators/permits.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import Joi from 'joi';

import { YES_NO, YES_NO_UNSURE } from '../components/constants';
import { YES_NO_UNSURE } from '../components/constants';
import { stringRequiredYesNo, stringRequiredYesNoUnsure } from './common';

export const permitsSchema = Joi.object({
checkProvincialPermits: Joi.when('hasAppliedProvincialPermits', {
switch: [
{
is: YES_NO_UNSURE.YES,
then: Joi.string()
.valid(...Object.values(YES_NO))
.required()
then: stringRequiredYesNo
},
{
is: YES_NO_UNSURE.UNSURE,
then: Joi.string()
.valid(...Object.values(YES_NO))
.required()
then: stringRequiredYesNo
}
],
otherwise: Joi.forbidden()
}),
hasAppliedProvincialPermits: Joi.string()
.valid(...Object.values(YES_NO_UNSURE))
.required()
hasAppliedProvincialPermits: stringRequiredYesNoUnsure
});
3 changes: 3 additions & 0 deletions app/src/validators/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const schema = {
appliedPermits: Joi.array().items(appliedPermitsSchema).allow(null),
basic: basicSchema,
housing: housingSchema,
investigatePermits: Joi.array()
.items(Joi.object({ permitTypeId: Joi.number().allow(null) }))
.allow(null),
permits: permitsSchema
})
},
Expand Down
87 changes: 87 additions & 0 deletions app/tests/unit/validators/applicant.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { applicantSchema } from '../../../src/validators/applicant';

describe('applicantSchema', () => {
it('should only accept string values for each field', () => {
const applicant = {
contactPreference: 123,
email: 123,
firstName: 123,
lastName: 123,
phoneNumber: 123,
relationshipToProject: 123
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});

it('should not exceed the 255 character limit for any fields', () => {
const applicant = {
contactPreference: 'a'.repeat(256),
email: 'a'.repeat(256),
firstName: 'a'.repeat(256),
lastName: 'a'.repeat(256),
phoneNumber: 'a'.repeat(256),
relationshipToProject: 'a'.repeat(256)
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});

it('should not be empty', () => {
const applicant = {};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});

it('should be a valid schema', () => {
const applicant = {
contactPreference: 'email',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
phoneNumber: '1234567890',
relationshipToProject: 'test'
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeUndefined();
});

it('should not accept invalid email', () => {
const applicant = {
contactPreference: 'email',
email: 'not-an-email',
firstName: 'John',
lastName: 'Doe',
phoneNumber: '1234567890',
relationshipToProject: 'test'
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});

it('should not accept invalid phone number', () => {
const applicant = {
contactPreference: 'email',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
phoneNumber: '+1234567890',
relationshipToProject: 'test'
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});

it('should not accept invalid phone number', () => {
const applicant = {
contactPreference: 'email',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
phoneNumber: '12345678901',
relationshipToProject: 'test'
};
const result = applicantSchema.validate(applicant);
expect(result.error).toBeDefined();
});
});
Loading

0 comments on commit aa8498a

Please sign in to comment.