Skip to content

Commit

Permalink
Lighthouse Benefits Intake Metadata (#16333)
Browse files Browse the repository at this point in the history
  • Loading branch information
wayne-weibel authored Apr 16, 2024
1 parent 9f5164e commit c762d12
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 0 deletions.
132 changes: 132 additions & 0 deletions lib/lighthouse/benefits_intake/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

module BenefitsIntake
##
# Validate the required metadata which must accompany an upload:
#
# {
# 'veteranFirstName': String,
# 'veteranLastName': String,
# 'fileNumber': String, # 8-9 digits
# 'zipCode': String, # 5 or 9 digits
# 'source': String,
# 'docType': String,
# 'businessLine': String, # optional; enum in BUSINESS_LINE
# }
#
# https://developer.va.gov/explore/api/benefits-intake/docs
#
class Metadata
BUSINESS_LINE = {
CMP: 'Compensation requests such as those related to disability, unemployment, and pandemic claims',
PMC: 'Pension requests including survivor’s pension',
INS: 'Insurance such as life insurance, disability insurance, and other health insurance',
EDU: 'Education benefits, programs, and affiliations',
VRE: 'Veteran Readiness & Employment such as employment questionnaires, ' \
'employment discrimination, employment verification',
BVA: 'Board of Veteran Appeals',
FID: 'Fiduciary / financial appointee, including family member benefits',
NCA: 'National Cemetery Administration',
OTH: 'Other (this value if used, will be treated as CMP)'
}.freeze

# rubocop:disable Metrics/ParameterLists
def self.generate(first_name, last_name, file_number, zip_code, source, doc_type, business_line = nil)
validate({
'veteranFirstName' => first_name,
'veteranLastName' => last_name,
'fileNumber' => file_number,
'zipCode' => zip_code,
'source' => source,
'docType' => doc_type,
'businessLine' => business_line
})
end
# rubocop:enable Metrics/ParameterLists

def self.validate(metadata)
validate_first_name(metadata)
.then { |m| validate_last_name(m) }
.then { |m| validate_file_number(m) }
.then { |m| validate_zip_code(m) }
.then { |m| validate_source(m) }
.then { |m| validate_doc_type(m) }
.then { |m| validate_business_line(m) }
end

def self.validate_first_name(metadata)
validate_presence_and_stringiness(metadata['veteranFirstName'], 'veteran first name')

first_name = I18n.transliterate(metadata['veteranFirstName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50)
validate_nonblank(first_name, 'veteran first name')

metadata['veteranFirstName'] = first_name
metadata
end

def self.validate_last_name(metadata)
validate_presence_and_stringiness(metadata['veteranLastName'], 'veteran last name')

last_name = I18n.transliterate(metadata['veteranLastName']).gsub(%r{[^a-zA-Z\-\/\s]}, '').strip.first(50)
validate_nonblank(last_name, 'veteran last name')

metadata['veteranLastName'] = last_name
metadata
end

def self.validate_file_number(metadata)
validate_presence_and_stringiness(metadata['fileNumber'], 'file number')
unless metadata['fileNumber'].match?(/^\d{8,9}$/)
raise ArgumentError, 'file number is invalid. It must be 8 or 9 digits'
end

metadata
end

def self.validate_zip_code(metadata)
validate_presence_and_stringiness(metadata['zipCode'], 'zip code')

zip_code = metadata['zipCode'].dup.gsub(/[^0-9]/, '')
zip_code.insert(5, '-') if zip_code.match?(/\A[0-9]{9}\z/)
zip_code = '00000' unless zip_code.match?(/\A[0-9]{5}(-[0-9]{4})?\z/)

metadata['zipCode'] = zip_code

metadata
end

def self.validate_source(metadata)
validate_presence_and_stringiness(metadata['source'], 'source')

metadata
end

def self.validate_doc_type(metadata)
validate_presence_and_stringiness(metadata['docType'], 'doc type')

metadata
end

def self.validate_business_line(metadata)
bl = metadata['businessLine']
if bl
bl = bl.dup.to_s.upcase.to_sym
bl = :OTH unless BUSINESS_LINE.key?(bl)
metadata['businessLine'] = bl.to_s
else
metadata.delete('businessLine')
end

metadata
end

def self.validate_presence_and_stringiness(value, error_label)
raise ArgumentError, "#{error_label} is missing" unless value
raise ArgumentError, "#{error_label} is not a string" if value.class != String
end

def self.validate_nonblank(value, error_label)
raise ArgumentError, "#{error_label} is blank" if value.blank?
end
end
end
139 changes: 139 additions & 0 deletions spec/lib/lighthouse/benefits_intake/metadata_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# frozen_string_literal: true

require 'rails_helper'
require 'lighthouse/benefits_intake/metadata'

RSpec.describe BenefitsIntake::Metadata do
let(:meta) { described_class }

context 'with valid parameters' do
let(:valid) do
{
'veteranFirstName' => 'firstname',
'veteranLastName' => 'lastname',
'fileNumber' => '123456789',
'zipCode' => '12345-5555',
'source' => 'source',
'docType' => 'doc_type',
'businessLine' => 'BVA'
}
end

it 'returns unmodified metadata' do
data = meta.generate('firstname', 'lastname', '123456789', '12345-5555', 'source', 'doc_type', 'BVA')
expect(data).to eq(valid)
end

it 'returns corrected metadata' do
data = meta.generate('first_name', 'last_name', '123456789', '123455555', 'source', 'doc_type', :bva)
expect(data).to eq(valid)
end
end

context 'malformed data' do
it 'truncates names' do
charset = Array('a'..'z') + Array('A'..'Z') + ['-', ' ', '/']
firstname = Array.new(rand(50..100)) { charset.sample }.join
lastname = Array.new(rand(50..100)) { charset.sample }.join

first50 = meta.validate_first_name({ 'veteranFirstName' => firstname })
expect(first50).to eq({ 'veteranFirstName' => firstname.strip[0..49] })

last50 = meta.validate_last_name({ 'veteranLastName' => lastname })
expect(last50).to eq({ 'veteranLastName' => lastname.strip[0..49] })
end

it 'errors on substituted blank names' do
expect do
meta.validate_first_name({ 'veteranFirstName' => '23&_$!42' })
end.to raise_error(ArgumentError, 'veteran first name is blank')

expect do
meta.validate_last_name({ 'veteranLastName' => '23&_$!42' })
end.to raise_error(ArgumentError, 'veteran last name is blank')
end

it 'corrects malformed zipcode' do
zip = meta.validate_zip_code({ 'zipCode' => '12345TEST' })
expect(zip).to eq({ 'zipCode' => '12345' })

zip = meta.validate_zip_code({ 'zipCode' => '12345TEST6789' })
expect(zip).to eq({ 'zipCode' => '12345-6789' })

zip = meta.validate_zip_code({ 'zipCode' => '123456789123456789' })
expect(zip).to eq({ 'zipCode' => '00000' })
end

it 'corrects malformed business_line' do
zip = meta.validate_business_line({ 'businessLine' => :BVA })
expect(zip).to eq({ 'businessLine' => 'BVA' })

zip = meta.validate_business_line({ 'businessLine' => :pmc })
expect(zip).to eq({ 'businessLine' => 'PMC' })

zip = meta.validate_business_line({ 'businessLine' => 'pmc' })
expect(zip).to eq({ 'businessLine' => 'PMC' })

zip = meta.validate_business_line({ 'businessLine' => :TEST })
expect(zip).to eq({ 'businessLine' => 'OTH' })

zip = meta.validate_business_line({ 'businessLine' => 'TEST' })
expect(zip).to eq({ 'businessLine' => 'OTH' })

zip = meta.validate_business_line({ 'businessLine' => nil })
expect(zip).to eq({})
end

it 'errors on invalid file number' do
expect do
meta.validate_file_number({ 'fileNumber' => '123TEST89' })
end.to raise_error(ArgumentError, 'file number is invalid. It must be 8 or 9 digits')

expect do
meta.validate_file_number({ 'fileNumber' => '123456789123456789' })
end.to raise_error(ArgumentError, 'file number is invalid. It must be 8 or 9 digits')

expect do
meta.validate_file_number({ 'fileNumber' => '12345' })
end.to raise_error(ArgumentError, 'file number is invalid. It must be 8 or 9 digits')
end
end

describe '#validate_presence_and_stringiness' do
it 'raises a missing exception' do
expect do
meta.validate_presence_and_stringiness(nil, 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is missing')

expect do
meta.validate_presence_and_stringiness(false, 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is missing')
end

it 'raises a non-string exception' do
expect do
meta.validate_presence_and_stringiness(12, 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is not a string')

expect do
meta.validate_presence_and_stringiness(true, 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is not a string')

expect do
meta.validate_presence_and_stringiness({}, 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is not a string')
end

it 'raises a blank exception' do
expect do
meta.validate_nonblank('', 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is blank')

expect do
meta.validate_nonblank(' ', 'TEST FIELD')
end.to raise_error(ArgumentError, 'TEST FIELD is blank')
end
end

# end Rspec.describe
end

0 comments on commit c762d12

Please sign in to comment.