Skip to content

Commit

Permalink
Add new billing account change address validator (#383)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4092

We use validators to 'validate' incoming payloads. This new validator will be used to check the content of a request to our new `/billing-accounts/{id}/change-address` endpoint.

Though the journey in the UI starts with clicking a **Change address** button during the process you can also change the company and contact linked to the billing account as well.

This is why we need to cater for all 3 possibly being sent in the payload.

The [Joi](https://joi.dev/) schema we've compiled comes from what we found is currently being used in the legacy code (`water-abstraction-service/src/modules/invoice-accounts/routes.js`).
  • Loading branch information
Cruikshanks authored Aug 29, 2023
1 parent 35d116d commit 9f7e07c
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 1 deletion.
28 changes: 27 additions & 1 deletion app/lib/static-lookups.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ const billRunTypes = [
'two_part_tariff'
]

const companyTypes = [
'person',
'organisation'
]

const contactTypes = [
'person',
'department'
]

const organisationTypes = [
'individual',
'limitedCompany',
'limitedLiabilityPartnership',
'publicLimitedCompany'
]

const sources = [
'nald',
'wrls'
]

module.exports = {
billRunTypes
billRunTypes,
companyTypes,
contactTypes,
organisationTypes,
sources
}
75 changes: 75 additions & 0 deletions app/validators/change-address.validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use strict'

/**
* @module ChangeAddressValidator
*/

const Joi = require('joi')

const StaticLookupsLib = require('../lib/static-lookups.lib.js')

/**
* Checks that the payload of a `/billing-accounts/{invoiceAccountId}/address` request is valid
*
* The JOI schema was interpreted from what we found in water-abstraction-service/src/modules/invoice-accounts/routes.js
*
* @param {Object} data The data to be validated (assumed to be the request payload)
*
* @returns {Object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If
* any errors are found the `error:` property will also exist detailing what the issues were
*/
function go (data) {
const schema = Joi.object({
address: _addressSchema(),
agentCompany: _agentCompanySchema(),
contact: _contactSchema()
})

return schema.validate(data)
}

function _addressSchema () {
return Joi.object({
id: Joi.string().guid().optional(),
addressLine1: Joi.string().optional(),
addressLine2: Joi.string().optional(),
addressLine3: Joi.string().optional(),
addressLine4: Joi.string().optional(),
town: Joi.string().optional(),
county: Joi.string().optional(),
country: Joi.string().optional(),
postcode: Joi.string().optional(),
uprn: Joi.number().optional(),
source: Joi.string().optional()
}).required()
}

function _agentCompanySchema () {
return Joi.object({
id: Joi.string().guid().optional(),
type: Joi.string().valid(...StaticLookupsLib.companyTypes).optional(),
organisationType: Joi.string().valid(...StaticLookupsLib.organisationTypes).optional(),
name: Joi.string().optional(),
companyNumber: Joi.string().optional()
}).optional()
}

function _contactSchema () {
return Joi.object({
id: Joi.string().guid().optional(),
type: Joi.string().valid(...StaticLookupsLib.contactTypes).optional(),
salutation: Joi.string().optional(),
firstName: Joi.string().optional(),
initials: Joi.string().optional(),
middleInitials: Joi.string().optional(),
lastName: Joi.string().optional(),
suffix: Joi.string().optional(),
department: Joi.string().optional(),
source: Joi.string().valid(...StaticLookupsLib.sources).optional(),
isTest: Joi.boolean().default(false).optional()
}).optional()
}

module.exports = {
go
}
218 changes: 218 additions & 0 deletions test/validators/change-address.validator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
'use strict'

// Test framework dependencies
const Lab = require('@hapi/lab')
const Code = require('@hapi/code')

const { describe, it, beforeEach } = exports.lab = Lab.script()
const { expect } = Code

// Thing under test
const ChangeAddressValidator = require('../../app/validators/change-address.validator.js')

describe('Create Bill Run validator', () => {
const completeAddress = {
id: '752446c0-a649-4383-967c-4e4fb6e13dde',
addressLine1: 'Building 12',
addressLine2: 'SCP Headquarters',
addressLine3: 'East campus',
addressLine4: '227 Roundly Rd',
town: 'Leeds',
county: 'West Yorkshire',
country: 'United Kingdom',
postcode: 'LS8 4HS',
uprn: 97564,
source: 'ea-address-facade'
}
const completeAgentCompany = {
id: 'aa0dd66a-6f38-4ea6-b2e1-9639cb51c792',
type: 'organisation',
organisationType: 'limitedCompany',
name: 'SCP',
companyNumber: 'OE008471'
}
const completeContact = {
id: '9969a9e6-c440-4065-ba2c-16566cb59575',
type: 'person',
salutation: 'Mrs',
firstName: 'Margherita',
middleInitials: 'OE008471',
lastName: 'Villar',
suffix: 'MBE',
department: 'Humanoid Risk Assessment',
source: 'wrls',
isTest: false
}

let data

describe('when valid data is provided', () => {
describe('and it is the minimalist valid data allowed', () => {
beforeEach(() => {
data = {
address: {}
}
})

it('confirms the data is valid', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).not.to.exist()
})
})

describe('which includes only a valid address', () => {
beforeEach(() => {
data = {
address: {
...completeAddress
}
}
})

it('confirms the data is valid', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).not.to.exist()
expect(result.value.address.id).to.equal(data.address.id)
})
})

describe('which includes a minimal address and a valid agent company', () => {
beforeEach(() => {
data = {
agentCompany: {
...completeAgentCompany
},
address: {}
}
})

it('confirms the data is valid', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).not.to.exist()
expect(result.value.agentCompany.id).to.equal(data.agentCompany.id)
})
})

describe('which includes a minimal address and a valid contact', () => {
beforeEach(() => {
data = {
contact: {
...completeContact
},
address: {}
}
})

it('confirms the data is valid', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).not.to.exist()
expect(result.value.contact.id).to.equal(data.contact.id)
})
})
})

describe('when invalid data is provided', () => {
describe("because no 'address' is provided", () => {
beforeEach(() => {
data = {
agentCompany: {},
contact: {}
}
})

it('returns an error', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).to.exist()
expect(result.error.details[0].message).to.equal('"address" is required')
})
})

describe('and the issue is in the agent company', () => {
beforeEach(() => {
data = {
agentCompany: {
...completeAgentCompany
},
address: {}
}
})

describe("because 'type' is unrecognised", () => {
beforeEach(() => {
data.agentCompany.type = 'INVALID'
})

it('returns an error', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).to.exist()
expect(result.error.details[0].message).startsWith('"agentCompany.type" must be one of')
})
})

describe("because 'organisationType' is unrecognised", () => {
beforeEach(() => {
data.agentCompany.organisationType = 'INVALID'
})

it('returns an error', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).to.exist()
expect(result.error.details[0].message).startsWith('"agentCompany.organisationType" must be one of')
})
})
})

describe('and it is an issue in the contact', () => {
beforeEach(() => {
data = {
contact: {
...completeContact
},
address: {}
}
})

describe("because 'type' is unrecognised", () => {
beforeEach(() => {
data.contact.type = 'INVALID'
})

it('returns an error', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).to.exist()
expect(result.error.details[0].message).startsWith('"contact.type" must be one of')
})
})

describe("because 'source' is unrecognised", () => {
beforeEach(() => {
data.contact.source = 'INVALID'
})

it('returns an error', () => {
const result = ChangeAddressValidator.go(data)

expect(result.value).to.exist()
expect(result.error).to.exist()
expect(result.error.details[0].message).startsWith('"contact.source" must be one of')
})
})
})
})
})

0 comments on commit 9f7e07c

Please sign in to comment.