Skip to content

Commit

Permalink
Merge pull request #666 from trade-tariff/HOTT-3766-support-type-vali…
Browse files Browse the repository at this point in the history
…dation

HOTT-3766: Adds comprehensive validations to all measurement unit types
  • Loading branch information
willfish committed Aug 3, 2023
2 parents 882a9d5 + d3e5e31 commit 662b192
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 49 deletions.
25 changes: 24 additions & 1 deletion app/models/steps/measure_amount.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ module Steps
class MeasureAmount
STEPS_TO_REMOVE_FROM_SESSION = %w[additional_code document_code excise].freeze

DEFAULT_MEASUREMENT_UNIT_TYPE = 'number'.freeze
DEFAULT_HINT = 'Enter the correct value'.freeze

# By default, all measurement types are validated as positive numbers
MEASUREMENT_TYPE_VALIDATIONS = Hash.new do |validations, type|
validations[type] = { 'only_positive' => true }
end
MEASUREMENT_TYPE_VALIDATIONS['percentage'] = { 'max' => 100, 'min' => 0 }
MEASUREMENT_TYPE_VALIDATIONS['percentage_abv'] = { 'max' => 100, 'min' => 0 }
MEASUREMENT_TYPE_VALIDATIONS['money'] = { 'min' => 0.001 }
MEASUREMENT_TYPE_VALIDATIONS['discount'] = { 'min' => 0 }

include ActiveModel::Model
include CommodityHelper
include Rails.application.routes.url_helpers
Expand All @@ -12,14 +24,25 @@ class MeasureAmount
validation_messages = I18n.t('activemodel.errors.models.steps/measure_amount.attributes.answers')

record.applicable_measure_units.each do |key, values|
type = values['measurement_unit_type'].presence || DEFAULT_MEASUREMENT_UNIT_TYPE
key = key.downcase
value = record.public_send(key.downcase)
hint = values['unit_hint'] || DEFAULT_HINT

record.errors.add(key, "#{validation_messages[:blank]} #{values['unit_hint']}") if value.blank?

value = Float(value)

record.errors.add(key, "#{validation_messages[:greater_than]} #{values['unit_hint']}") unless value.positive?
MEASUREMENT_TYPE_VALIDATIONS[type].each do |validation, validation_value|
case validation
when 'only_positive'
record.errors.add(key, "#{validation_messages[:greater_than]} #{hint}") unless value.positive?
when 'max'
record.errors.add(key, "#{validation_messages[:max]} #{validation_value}. #{hint}") if value > validation_value
when 'min'
record.errors.add(key, "#{validation_messages[:min]} #{validation_value}. #{hint}") if value < validation_value
end
end
rescue ArgumentError, TypeError
record.errors.add(key, "#{validation_messages[:not_a_number]} #{values['unit_hint']}")
end
Expand Down
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ en:
blank: Enter a valid import quantity.
not_a_number: Enter a numeric import quantity.
greater_than: Enter an import quantity value greater than zero.
max: Enter an import quantity value less than or equal too
min: Enter an import quantity value more than or equal too
steps/vat:
attributes:
vat:
Expand Down
43 changes: 43 additions & 0 deletions spec/factories/api/commodity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -477,5 +477,48 @@
]
end
end

trait :with_all_variations_of_measurement_unit do
applicable_measure_units do
{
'ASV' => {
'measurement_unit_code' => 'ASV',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'Enter the alcohol by volume (ABV) percentage',
'measurement_unit_type' => 'percentage_abv',
},
'BRX' => {
'measurement_unit_code' => 'BRX',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'If you do not know the percentage sucrose content (Brix value), check the footnotes for the commodity code to identify how to calculate it.',
'measurement_unit_type' => 'percentage',
},
'CEN' => {
'measurement_unit_code' => 'CEN',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'Enter the number of items',
'measurement_unit_type' => 'number',
},
'HLT' => {
'measurement_unit_code' => 'HLT',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'Enter the value in litres',
'measurement_unit_type' => 'volume',
},
'KGM' => {
'measurement_unit_code' => 'KGM',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'Enter the value in kilograms',
'measurement_unit_type' => 'weight',
},
'SPR' => {
'measurement_unit_code' => 'SPR',
'measurement_unit_qualifier_code' => nil,
'unit_hint' => 'Enter the SPR discount against the full rate, not the chargeable SPR rate. For example, if the full rate, before application of SPR is £10.00 / litre of pure alcohol, and you are entitled to pay £7.00, enter 3.00 as your SPR discount.',
'measurement_unit_type' => 'discount',
},
}
end
end
end
end
132 changes: 84 additions & 48 deletions spec/models/steps/measure_amount_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,73 +26,109 @@
end

describe '#validations' do
context 'when all answers are present, positive floats' do
let(:measure_amount) { { 'dtn' => 500.42, 'hlt' => 204.64 } }
subject(:step) do
build(
:measure_amount,
user_session:,
measure_amount:,
applicable_measure_units: commodity.applicable_measure_units,
)
end

it 'is a valid object' do
expect(step).to be_valid
end
include_context 'with a fake commodity'

it 'has no validation errors' do
step.valid?
let(:commodity) { build(:commodity, :with_all_variations_of_measurement_unit) }

expect(step.errors.messages).to be_empty
shared_examples_for 'a valid step' do |measure_amount|
let(:measure_amount) do
{
'asv' => 0,
'brx' => 0,
'cen' => 1,
'hlt' => 1,
'kgm' => 1,
'spr' => 0,
}.merge(measure_amount)
end

it { expect(step).to be_valid }
end

context 'when one of the answers is blank' do
let(:measure_amount) { { 'dtn' => 500.42 } }
shared_examples_for 'an invalid step' do |measure_amount, error_message|
let(:measure_amount) { measure_amount }

it 'is not a valid object' do
expect(step).not_to be_valid
end
before { step.valid? }

it { expect(step).not_to be_valid }
it { expect(step.errors.messages[measure_amount.keys.first.to_sym]).to include(error_message) }
end

it 'adds the correct validation error message' do
step.valid?
context 'when the answer unit is a percentage abv type' do
hint = 'Enter the alcohol by volume (ABV) percentage'

expect(step.errors.messages[:hlt]).to eq(
[
'Enter a valid import quantity. Enter the value in hectolitres (100 litres)',
'Enter a numeric import quantity. Enter the value in hectolitres (100 litres)',
],
)
end
it_behaves_like 'a valid step', { 'asv' => 0 }

it_behaves_like 'an invalid step', { 'asv' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'asv' => -1 }, "Enter an import quantity value more than or equal too 0. #{hint}"
it_behaves_like 'an invalid step', { 'asv' => 101 }, "Enter an import quantity value less than or equal too 100. #{hint}"
it_behaves_like 'an invalid step', { 'asv' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'asv' => nil }, "Enter a numeric import quantity. #{hint}"
end

context 'when one of the answers is negative' do
let(:measure_amount) { { 'dtn' => 500.42, 'hlt' => -1.5 } }
context 'when the answer unit is a percentage type' do
hint = 'If you do not know the percentage sucrose content (Brix value), check the footnotes for the commodity code to identify how to calculate it.'

it 'is not a valid object' do
expect(step).not_to be_valid
end
it_behaves_like 'a valid step', { 'brx' => 0 }

it 'adds the correct validation error message' do
step.valid?
it_behaves_like 'an invalid step', { 'brx' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'brx' => -1 }, "Enter an import quantity value more than or equal too 0. #{hint}"
it_behaves_like 'an invalid step', { 'brx' => 101 }, "Enter an import quantity value less than or equal too 100. #{hint}"
it_behaves_like 'an invalid step', { 'brx' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'brx' => nil }, "Enter a numeric import quantity. #{hint}"
end

expect(step.errors.messages[:hlt]).to eq(
[
'Enter an import quantity value greater than zero. Enter the value in hectolitres (100 litres)',
],
)
end
context 'when the answer unit is a number type' do
hint = 'Enter the number of items'

it_behaves_like 'a valid step', { 'cen' => 1 }

it_behaves_like 'an invalid step', { 'cen' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'cen' => 0 }, "Enter an import quantity value greater than zero. #{hint}"
it_behaves_like 'an invalid step', { 'cen' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'cen' => nil }, "Enter a numeric import quantity. #{hint}"
end

context 'when one of the answers is not a numeric' do
let(:measure_amount) { { 'dtn' => 500.42, 'hlt' => 'foo' } }
context 'when the anser unit is a volume type' do
hint = 'Enter the value in litres'

it 'is not a valid object' do
expect(step).not_to be_valid
end
it_behaves_like 'a valid step', { 'hlt' => 1 }

it 'adds the correct validation error message' do
step.valid?
it_behaves_like 'an invalid step', { 'hlt' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'hlt' => 0 }, "Enter an import quantity value greater than zero. #{hint}"
it_behaves_like 'an invalid step', { 'hlt' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'hlt' => nil }, "Enter a numeric import quantity. #{hint}"
end

expect(step.errors.messages[:hlt]).to eq(
[
'Enter a numeric import quantity. Enter the value in hectolitres (100 litres)',
],
)
end
context 'when the answer unit is a weight type' do
hint = 'Enter the value in kilograms'

it_behaves_like 'a valid step', { 'kgm' => 0.00001 }

it_behaves_like 'an invalid step', { 'kgm' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'kgm' => 0 }, "Enter an import quantity value greater than zero. #{hint}"
it_behaves_like 'an invalid step', { 'kgm' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'kgm' => nil }, "Enter a numeric import quantity. #{hint}"
end

context 'when the answer unit is a discount type' do
hint = 'Enter the SPR discount against the full rate, not the chargeable SPR rate. For example, if the full rate, before application of SPR is £10.00 / litre of pure alcohol, and you are entitled to pay £7.00, enter 3.00 as your SPR discount.'

it_behaves_like 'a valid step', { 'spr' => 0 }

it_behaves_like 'an invalid step', { 'spr' => 'foo' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'spr' => -1 }, "Enter an import quantity value more than or equal too 0. #{hint}"
it_behaves_like 'an invalid step', { 'spr' => '' }, "Enter a numeric import quantity. #{hint}"
it_behaves_like 'an invalid step', { 'spr' => nil }, "Enter a numeric import quantity. #{hint}"
end
end

Expand Down

0 comments on commit 662b192

Please sign in to comment.