Skip to content

Commit

Permalink
Merge pull request #145 from companieshouse/feature/roe-144-add-web-v…
Browse files Browse the repository at this point in the history
…alidation-to-managing-officers

Feature/roe 144 add web validation to managing officers
  • Loading branch information
mouhajer-ch authored May 24, 2022
2 parents dffa746 + 6f5ba19 commit 4a26e0d
Show file tree
Hide file tree
Showing 22 changed files with 328 additions and 78 deletions.
5 changes: 3 additions & 2 deletions src/controllers/beneficial.owner.statements.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextFunction, Request, Response } from "express";
import { logger } from "../utils/logger";

import * as config from "../config";
import { logger } from "../utils/logger";
import { ApplicationData } from "../model";
import { getApplicationData, setExtraData } from "../utils/application.data";
import { BeneficialOwnerStatementKey } from "../model/beneficial.owner.statement.model";
Expand All @@ -13,7 +14,7 @@ export const get = (req: Request, res: Response, next: NextFunction) => {

return res.render(config.BENEFICIAL_OWNER_STATEMENTS_PAGE, {
backLinkUrl: config.ENTITY_URL,
beneficial_owners_statement: appData.beneficial_owners_statement
[BeneficialOwnerStatementKey]: appData[BeneficialOwnerStatementKey]
});
} catch (error) {
logger.errorRequest(req, error);
Expand Down
13 changes: 11 additions & 2 deletions src/middleware/validation.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { NextFunction, Request, Response } from "express";
import { validationResult, ValidationError } from "express-validator";

import { getApplicationData } from "../utils/application.data";
import { getApplicationData, prepareData } from "../utils/application.data";
import { NAVIGATION } from "../utils/navigation";
import { DateOfBirthKey, StartDateKey, DateOfBirthKeys, StartDateKeys } from "../model/date.model";

export function checkValidations(req: Request, res: Response, next: NextFunction) {
const errorList = validationResult(req);

if (!errorList.isEmpty()) {
const errors = formatValidationError(errorList.array());

// Bypass the direct use of variables with dashes that
// govukDateInput adds for day, month and year field
const dates = {
[DateOfBirthKey]: prepareData(req.body, DateOfBirthKeys),
[StartDateKey]: prepareData(req.body, StartDateKeys)
};

return res.render(NAVIGATION[req.path].currentPage, {
backLinkUrl: NAVIGATION[req.path].previousPage,
...getApplicationData(req.session),
...req.body,
errors,
...dates,
errors
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ router.get(config.BENEFICIAL_OWNER_OTHER_URL, authentication, beneficialOwnerOth
router.post(config.BENEFICIAL_OWNER_OTHER_URL, authentication, beneficialOwnerOther.post);

router.get(config.MANAGING_OFFICER_URL, authentication, managingOfficerIndividual.get);
router.post(config.MANAGING_OFFICER_URL, authentication, managingOfficerIndividual.post);
router.post(config.MANAGING_OFFICER_URL, authentication, ...validator.managingOfficerIndividual, checkValidations, managingOfficerIndividual.post);

router.get(config.MANAGING_OFFICER_CORPORATE_URL, authentication, managingOfficerCorporate.get);
router.post(config.MANAGING_OFFICER_CORPORATE_URL, authentication, managingOfficerCorporate.post);
router.post(config.MANAGING_OFFICER_CORPORATE_URL, authentication, ...validator.managingOfficerCorporate, checkValidations, managingOfficerCorporate.post);

router.get(config.BENEFICIAL_OWNER_INDIVIDUAL_URL, authentication, beneficialOwnerIndividual.get);
router.post(config.BENEFICIAL_OWNER_INDIVIDUAL_URL, authentication, beneficialOwnerIndividual.post);
Expand Down
5 changes: 5 additions & 0 deletions src/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,10 @@ export const NAVIGATION: Navigation = {
currentPage: config.MANAGING_OFFICER_PAGE,
previousPage: config.BENEFICIAL_OWNER_TYPE_URL,
nextPage: config.BENEFICIAL_OWNER_TYPE_URL
},
[config.MANAGING_OFFICER_CORPORATE_URL]: {
currentPage: config.MANAGING_OFFICER_CORPORATE_PAGE,
previousPage: config.BENEFICIAL_OWNER_TYPE_URL,
nextPage: config.BENEFICIAL_OWNER_TYPE_URL
}
};
15 changes: 15 additions & 0 deletions src/validation/custom.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Custom validation utils - For now checking is not empty

export const checkFieldIfRadioButtonSelected = (selected: boolean, errMsg: string, value: string = "") => {
if ( selected && !value.trim() ) {
throw new Error(errMsg);
}
return true;
};

export const checkDate = (errMsg: string, day: string = "", month: string = "", year: string = "") => {
if ( !day.trim() || !month.trim() || !year.trim() ) {
throw new Error(errMsg);
}
return true;
};
20 changes: 7 additions & 13 deletions src/validation/entity.validation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { body } from "express-validator";

import { checkFieldIfRadioButtonSelected } from "./custom.validation";
import { ErrorMessages } from "./error.messages";

export const entity = [
Expand All @@ -10,21 +11,14 @@ export const entity = [
body("principal_address_town").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.CITY_OR_TOWN),
body("principal_address_country").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.COUNTRY),
body("is_service_address_same_as_principal_address").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_SERVICE_ADDRESS_SAME_AS_PRINCIPAL_ADDRESS),
body("service_address_property_name_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', value, ErrorMessages.PROPERTY_NAME_OR_NUMBER) ),
body("service_address_line_1").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', value, ErrorMessages.ADDRESS_LINE1) ),
body("service_address_town").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', value, ErrorMessages.CITY_OR_TOWN) ),
body("service_address_country").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', value, ErrorMessages.COUNTRY) ),
body("service_address_property_name_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.PROPERTY_NAME_OR_NUMBER, value) ),
body("service_address_line_1").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.ADDRESS_LINE1, value) ),
body("service_address_town").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.CITY_OR_TOWN, value) ),
body("service_address_country").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.COUNTRY, value) ),
body('email').not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.EMAIL),
body("legal_form").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.LEGAL_FORM),
body("law_governed").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.LAW_GOVERNED),
body("is_on_register_in_country_formed_in").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_REGISTER_IN_COUNTRY_FORMED_IN),
body("public_register_name").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', value, ErrorMessages.PUBLIC_REGISTER_NAME) ),
body("registration_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', value, ErrorMessages.PUBLIC_REGISTER_NUMBER) )
body("public_register_name").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', ErrorMessages.PUBLIC_REGISTER_NAME, value) ),
body("registration_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', ErrorMessages.PUBLIC_REGISTER_NUMBER, value) )
];

const checkFieldIfRadioButtonSelected = (selected: boolean, value: string, errMsg: string) => {
if ( selected && ( !value || !value.trim() ) ) {
throw new Error(errMsg);
}
return true;
};
15 changes: 10 additions & 5 deletions src/validation/error.messages.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
export enum ErrorMessages {
ENTITY_NAME = "Enter the name of the overseas entity",
MANAGING_OFFICER_CORPORATE_NAME = "Enter the corporate managing officer's name",
MANAGING_OFFICER_CORPORATE_NAME = "Enter the corporate managing officers name",
EMAIL = "Enter an email address",
LEGAL_FORM = "Enter the legal form",
LAW_GOVERNED = "Enter the governing law",
FULL_NAME = "Enter a full name",
FIRST_NAME = "Enter the individual person's first name",
LAST_NAME = "Enter the individual person's last name",
ROLE = "Enter a description of the individual person's role and responsibilities",
NATIONALITY = "Enter the individual person's nationality",
FIRST_NAME = "Enter the individual person’s first name",
LAST_NAME = "Enter the individual person’s last name",
FORMER_NAME = "Enter the individual person’s former name or names",
ROLE = "Enter a description of the individual person’s role and responsibilities",
NATIONALITY = "Enter the individual person’s nationality",
// Public Register
PUBLIC_REGISTER_NAME = "Enter the name of the register",
PUBLIC_REGISTER_NUMBER = "Enter the registration number",
Expand All @@ -21,9 +22,13 @@ export enum ErrorMessages {
DAY = "Date must include a day ",
MONTH = "Date must include a month",
YEAR = "Date must include a year",
DATE_OF_BIRTH = "Enter the individual person’s date of birth",
MANAGING_OFFICER_START_DATE = "Enter when did it become a managing officer for the overseas entity",
// No radio selected
SELECT_IF_INDIVIDUAL_PERSON_HAS_FORMER_NAME = "Select yes if the individual person has any former names",
SELECT_IF_REGISTER_IN_COUNTRY_FORMED_IN = "Select yes if the overseas entity is already on a public register in the country it was formed in",
SELECT_IF_SERVICE_ADDRESS_SAME_AS_PRINCIPAL_ADDRESS = "Select yes if the correspondence address the same as the principal or registered office address",
SELECT_IF_SERVICE_ADDRESS_SAME_AS_USER_RESIDENTIAL_ADDRESS = "Select yes if the individual person’s correspondence address is the same as their home address",
SELECT_IF_ANY_BENEFICIAL_OWNERS_BEEN_IDENTIFIED = "Select if any beneficial owners have been identified",
SELECT_THE_TYPE_OF_BENEFICIAL_OWNER_OR_MANAGING_OFFICER_YOU_WANT_TO_ADD = "Select the type of beneficial owner or managing officer you want to add",
}
6 changes: 5 additions & 1 deletion src/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { beneficialOwnersStatement } from "./beneficial.owner.statements.validation";
import { beneficialOwnersType } from "./beneficial.owner.type.validation";
import { entity } from "./entity.validation";
import { managingOfficerCorporate } from "./managing.officer.corporate.validation";
import { managingOfficerIndividual } from "./managing.officer.validation";
import { presenter } from "./presenter.validation";

export const validator = {
entity,
presenter,
beneficialOwnersStatement,
beneficialOwnersType
beneficialOwnersType,
managingOfficerIndividual,
managingOfficerCorporate
};
26 changes: 26 additions & 0 deletions src/validation/managing.officer.corporate.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { body } from "express-validator";

import { checkDate, checkFieldIfRadioButtonSelected } from "./custom.validation";
import { ErrorMessages } from "./error.messages";

export const managingOfficerCorporate = [
body("name").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.MANAGING_OFFICER_CORPORATE_NAME),
body("principal_address_property_name_number").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.PROPERTY_NAME_OR_NUMBER),
body("principal_address_line_1").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.ADDRESS_LINE1),
body("principal_address_town").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.CITY_OR_TOWN),
body("principal_address_country").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.COUNTRY),
body("is_service_address_same_as_principal_address").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_SERVICE_ADDRESS_SAME_AS_PRINCIPAL_ADDRESS),
body("service_address_property_name_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.PROPERTY_NAME_OR_NUMBER, value) ),
body("service_address_line_1").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.ADDRESS_LINE1, value) ),
body("service_address_town").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.CITY_OR_TOWN, value) ),
body("service_address_country").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_principal_address === '0', ErrorMessages.COUNTRY, value) ),
body("legal_form").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.LEGAL_FORM),
body("law_governed").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.LAW_GOVERNED),
body("is_on_register_in_country_formed_in").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_REGISTER_IN_COUNTRY_FORMED_IN),
body("public_register_name").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', ErrorMessages.PUBLIC_REGISTER_NAME, value) ),
body("registration_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_on_register_in_country_formed_in === '1', ErrorMessages.PUBLIC_REGISTER_NUMBER, value) ),
body("start_date").custom((value, { req }) => checkDate(ErrorMessages.MANAGING_OFFICER_START_DATE, req.body["start_date-day"], req.body["start_date-month"], req.body["start_date-year"]) ),
body("start_date-day").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.DAY),
body("start_date-month").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.MONTH),
body("start_date-year").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.YEAR)
];
26 changes: 26 additions & 0 deletions src/validation/managing.officer.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { body } from "express-validator";

import { checkDate, checkFieldIfRadioButtonSelected } from "./custom.validation";
import { ErrorMessages } from "./error.messages";

export const managingOfficerIndividual = [
body("first_name").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.FIRST_NAME),
body("last_name").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.LAST_NAME),
body("has_former_names").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_INDIVIDUAL_PERSON_HAS_FORMER_NAME),
body("former_names").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.has_former_names === '1', ErrorMessages.FORMER_NAME, value) ),
body("date_of_birth").custom((value, { req }) => checkDate(ErrorMessages.DATE_OF_BIRTH, req.body["date_of_birth-day"], req.body["date_of_birth-month"], req.body["date_of_birth-year"]) ),
body("date_of_birth-day").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.DAY),
body("date_of_birth-month").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.MONTH),
body("date_of_birth-year").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.YEAR),
body("nationality").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.NATIONALITY),
body("usual_residential_address_property_name_number").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.PROPERTY_NAME_OR_NUMBER),
body("usual_residential_address_line_1").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.ADDRESS_LINE1),
body("usual_residential_address_town").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.CITY_OR_TOWN),
body("usual_residential_address_country").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.COUNTRY),
body("is_service_address_same_as_usual_residential_address").not().isEmpty().withMessage(ErrorMessages.SELECT_IF_SERVICE_ADDRESS_SAME_AS_USER_RESIDENTIAL_ADDRESS),
body("service_address_property_name_number").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_usual_residential_address === '0', ErrorMessages.PROPERTY_NAME_OR_NUMBER, value) ),
body("service_address_line_1").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_usual_residential_address === '0', ErrorMessages.ADDRESS_LINE1, value) ),
body("service_address_town").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_usual_residential_address === '0', ErrorMessages.CITY_OR_TOWN, value) ),
body("service_address_country").custom((value, { req }) => checkFieldIfRadioButtonSelected(req.body.is_service_address_same_as_usual_residential_address === '0', ErrorMessages.COUNTRY, value) ),
body("role_and_responsibilities").not().isEmpty({ ignore_whitespace: true }).withMessage(ErrorMessages.ROLE)
];
53 changes: 53 additions & 0 deletions test/__mocks__/session.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export function getSessionRequestWithExtraData(): Session {
return session;
}

const date_of_birth = { 'date_of_birth-day': "1", "date_of_birth-month": "1", "date_of_birth-year": "2000" };
const start_date = { 'start_date-day': "1", 'start_date-month': "1", 'start_date-year': "2022" };

export const ADDRESS = {
property_name_number: "1",
line_1: "addressLine1",
Expand Down Expand Up @@ -124,6 +127,16 @@ export const PRINCIPAL_ADDRESS_MOCK = {
principal_address_postcode: "BY 2"
};

export const RESIDENTIAL_ADDRESS_MOCK = {
usual_residential_address_property_name_number: "residential address 1",
usual_residential_address_line_1: "residential address addressLine1",
usual_residential_address_line_2: "residential address addressLine2",
usual_residential_address_town: "residential address town",
usual_residential_address_county: "residential address county",
usual_residential_address_country: "residential address country",
usual_residential_address_postcode: "residential address BY 2"
};

export const ENTITY_OBJECT_MOCK: entityType.Entity = {
name: "overseasEntityName",
incorporation_country: "incorporationCountry",
Expand Down Expand Up @@ -309,6 +322,20 @@ export const REQ_BODY_MANAGING_OFFICER_OBJECT_EMPTY = {
role_and_responsibilities: ""
};

export const REQ_BODY_MANAGING_OFFICER_MOCK_WITH_ADDRESS = {
first_name: "some first name",
last_name: "some last name",
has_former_names: "0",
former_names: "",
nationality: "some nationality",
is_service_address_same_as_usual_residential_address: "0",
occupation: "some occupation",
role_and_responsibilities: "some role and responsibilities",
...RESIDENTIAL_ADDRESS_MOCK,
...SERVICE_ADDRESS_MOCK,
...date_of_birth
};

export const MANAGING_OFFICER_CORPORATE_OBJECT_MOCK: managingOfficerCorporateType.ManagingOfficerCorporate = {
name: "Joe Bloggs Ltd",
principal_address: ADDRESS,
Expand All @@ -322,6 +349,32 @@ export const MANAGING_OFFICER_CORPORATE_OBJECT_MOCK: managingOfficerCorporateTyp
start_date: { day: "1", month: "1", year: "2011" }
};

export const REQ_BODY_MANAGING_OFFICER_CORPORATE_OBJECT_EMPTY = {
name: "",
is_service_address_same_as_principal_address: "",
legal_form: "",
law_governed: "",
is_on_register_in_country_formed_in: "",
public_register_name: "",
registration_number: "",
start_date: { 'start_date-day': "", 'start_date-month': "", 'start_date-year': "" },
usual_residential_address: {},
service_address: {}
};

export const REQ_BODY_MANAGING_OFFICER_CORPORATE_MOCK_WITH_ADDRESS = {
name: "Joe Bloggs Ltd",
is_service_address_same_as_principal_address: "0",
legal_form: "legalForm",
law_governed: "LegAuth",
is_on_register_in_country_formed_in: "1",
public_register_name: "register",
registration_number: "123456789",
...PRINCIPAL_ADDRESS_MOCK,
...SERVICE_ADDRESS_MOCK,
...start_date
};

export const PRESENTER_OBJECT_MOCK: presenterType.Presenter = {
full_name: "fullName",
email: "user@domain.roe"
Expand Down
Loading

0 comments on commit 4a26e0d

Please sign in to comment.