Skip to content

Commit 075d2ce

Browse files
authored
Merge pull request #58 from DEFRA/bugfix/dev-bugs
00697349-net carbon saving field should accept negative numbers
2 parents 5271bd5 + 15a839e commit 075d2ce

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

src/common/schemas/project/carbon.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const MAX_COST_DIGITS = 18
77
const MAX_HEXDIGEST_LENGTH = 255
88
const DECIMAL_REGEX = /^\d+(\.(\d{1,2}))?$/
99
const INTEGER_REGEX = /^\d+$/
10+
const SIGNED_INTEGER_REGEX = /^-?\d+$/
11+
12+
// Joi error type constants
13+
const ERROR_STRING_PATTERN_BASE = 'string.pattern.base'
14+
const ERROR_STRING_MAX = 'string.max'
15+
const ERROR_STRING_WHOLE_NUMBER_MAX = 'string.whole_number_max'
1016

1117
/**
1218
* Validates a carbon decimal field value (up to 2 decimal places).
@@ -16,17 +22,17 @@ const INTEGER_REGEX = /^\d+$/
1622
*/
1723
const validateCarbonDecimalString = (value, helpers) => {
1824
if (!DECIMAL_REGEX.test(value)) {
19-
return helpers.error('string.pattern.base')
25+
return helpers.error(ERROR_STRING_PATTERN_BASE)
2026
}
2127
const [intPart, decPart] = value.split('.')
2228
if (decPart === undefined) {
2329
// Whole number: max 18 digits
2430
if (intPart.length > MAX_WHOLE_NUMBER_DIGITS) {
25-
return helpers.error('string.whole_number_max')
31+
return helpers.error(ERROR_STRING_WHOLE_NUMBER_MAX)
2632
}
2733
} else if (intPart.length > MAX_EMISSION_DIGITS) {
2834
// Decimal: max 16 digits before decimal point
29-
return helpers.error('string.max')
35+
return helpers.error(ERROR_STRING_MAX)
3036
} else {
3137
// no error
3238
}
@@ -39,10 +45,27 @@ const validateCarbonDecimalString = (value, helpers) => {
3945
*/
4046
const validateCarbonIntegerString = (value, helpers) => {
4147
if (!INTEGER_REGEX.test(value)) {
42-
return helpers.error('string.pattern.base')
48+
return helpers.error(ERROR_STRING_PATTERN_BASE)
4349
}
4450
if (value.length > MAX_COST_DIGITS) {
45-
return helpers.error('string.max')
51+
return helpers.error(ERROR_STRING_MAX)
52+
}
53+
return value
54+
}
55+
56+
/**
57+
* Validates a carbon signed integer field value (allows negative numbers).
58+
* Used for £ fields that can be negative: net economic benefit.
59+
* Max digits excludes the minus sign.
60+
*/
61+
const validateCarbonSignedIntegerString = (value, helpers) => {
62+
if (!SIGNED_INTEGER_REGEX.test(value)) {
63+
return helpers.error(ERROR_STRING_PATTERN_BASE)
64+
}
65+
// Count digits excluding minus sign
66+
const digits = value.replace(/^-/, '')
67+
if (digits.length > MAX_COST_DIGITS) {
68+
return helpers.error(ERROR_STRING_MAX)
4669
}
4770
return value
4871
}
@@ -90,6 +113,24 @@ const createOptionalCarbonIntegerSchema = (label) =>
90113
'string.max': PROJECT_VALIDATION_MESSAGES.CARBON_COST_INVALID
91114
})
92115

116+
const createOptionalCarbonSignedIntegerSchema = (label) =>
117+
Joi.string()
118+
.trim()
119+
.allow(null, '')
120+
.optional()
121+
.custom((value, helpers) => {
122+
if (value === null || value === undefined || value === '') {
123+
return value
124+
}
125+
return validateCarbonSignedIntegerString(value, helpers)
126+
})
127+
.label(label)
128+
.messages({
129+
'string.base': PROJECT_VALIDATION_MESSAGES.CARBON_COST_INVALID,
130+
'string.pattern.base': PROJECT_VALIDATION_MESSAGES.CARBON_COST_INVALID,
131+
'string.max': PROJECT_VALIDATION_MESSAGES.CARBON_COST_INVALID
132+
})
133+
93134
const createRequiredCarbonOperationalCostForecastSchema = (label) =>
94135
Joi.string()
95136
.trim()
@@ -117,7 +158,7 @@ export const carbonCostAvoidedOptionalSchema =
117158

118159
// £ integer fields
119160
export const carbonSavingsNetEconomicBenefitOptionalSchema =
120-
createOptionalCarbonIntegerSchema('carbonSavingsNetEconomicBenefit')
161+
createOptionalCarbonSignedIntegerSchema('carbonSavingsNetEconomicBenefit')
121162
export const carbonOperationalCostForecastRequiredSchema =
122163
createRequiredCarbonOperationalCostForecastSchema(
123164
'carbonOperationalCostForecast'

src/common/schemas/project/carbon.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,36 @@ describe('Carbon Impact Schemas', () => {
142142
expect(error).toBeUndefined()
143143
expect(value).toBe('500')
144144
})
145+
146+
it('should accept negative integers', () => {
147+
const { error, value } =
148+
carbonSavingsNetEconomicBenefitOptionalSchema.validate('-150000')
149+
expect(error).toBeUndefined()
150+
expect(value).toBe('-150000')
151+
})
152+
153+
it('should accept negative single digit', () => {
154+
const { error, value } =
155+
carbonSavingsNetEconomicBenefitOptionalSchema.validate('-5')
156+
expect(error).toBeUndefined()
157+
expect(value).toBe('-5')
158+
})
159+
160+
it('should reject negative values exceeding max digits (excluding minus)', () => {
161+
const oversized = '-1234567890123456789' // 19 digits
162+
const { error } =
163+
carbonSavingsNetEconomicBenefitOptionalSchema.validate(oversized)
164+
expect(error).toBeDefined()
165+
expect(error.details[0].message).toBe('CARBON_COST_INVALID')
166+
})
167+
168+
it('should accept negative values at max digits boundary', () => {
169+
const maxDigits = '-123456789012345678' // 18 digits
170+
const { error, value } =
171+
carbonSavingsNetEconomicBenefitOptionalSchema.validate(maxDigits)
172+
expect(error).toBeUndefined()
173+
expect(value).toBe(maxDigits)
174+
})
145175
})
146176

147177
describe('carbonOperationalCostForecastRequiredSchema', () => {

0 commit comments

Comments
 (0)