diff --git a/Gemfile.lock b/Gemfile.lock
index c78452aaff6..85f49b65da3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -713,7 +713,7 @@ GEM
parallel (1.24.0)
parallel_tests (4.7.1)
parallel
- parser (3.3.0.5)
+ parser (3.3.1.0)
ast (~> 2.4.1)
racc
patience_diff (1.2.0)
@@ -920,8 +920,8 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
- rubocop-ast (1.31.2)
- parser (>= 3.3.0.4)
+ rubocop-ast (1.31.3)
+ parser (>= 3.3.1.0)
rubocop-capybara (2.20.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.25.1)
@@ -932,12 +932,12 @@ GEM
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
- rubocop-rspec (2.29.1)
+ rubocop-rspec (2.29.2)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
rubocop-rspec_rails (~> 2.28)
- rubocop-rspec_rails (2.28.2)
+ rubocop-rspec_rails (2.28.3)
rubocop (~> 1.40)
rubocop-thread_safety (0.5.1)
rubocop (>= 0.90.0)
diff --git a/config/features.yml b/config/features.yml
index 77f4da9f260..e17ed775e31 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -854,6 +854,9 @@ features:
profile_show_quick_submit_notification_setting:
actor_type: user
description: Show/Hide the quick submit section of notification settings in profile
+ profile_show_proof_of_veteran_status:
+ actor_type: user
+ description: Show/Hide the proof of veteran status page and links
profile_use_experimental:
description: Use experimental features for Profile application - Do not remove
enable_in_development: true
diff --git a/config/form_profile_mappings/10-7959F-1.yml b/config/form_profile_mappings/10-7959F-1.yml
index f9846dcb65e..25c47a31be4 100644
--- a/config/form_profile_mappings/10-7959F-1.yml
+++ b/config/form_profile_mappings/10-7959F-1.yml
@@ -1,22 +1,7 @@
-veteran:
- date_of_birth: [identity_information, date_of_birth]
- full_name: [identity_information, full_name]
- first: [identity_information, first]
- middle: [identity_information, middle]
- last: [identity_information, last]
-physical_address:
- country: [contact_information, country]
- street: [contact_information, street]
- city: [contact_information, city]
- state: [contact_information, state]
- postal_code: [contact_information, postal_code]
-mailing_address:
- country: [contact_information, country]
- street: [contact_information, street]
- city: [contact_information, city]
- state: [contact_information, state]
- postal_code: [contact_information, postal_code]
-ssn: [identity_information, ssn]
-phone_number: [contact_information, us_phone]
-email_address: [contact_information, email]
\ No newline at end of file
+veteranFullName: [identity_information, full_name]
+veteranAddress: [contact_information, address]
+veteranDateOfBirth: [identity_information, date_of_birth]
+veteranSocialSecurityNumber: [identity_information, ssn]
+veteranPhoneNumber: [contact_information, us_phone]
+veteranEmailAddress: [contact_information, email]
\ No newline at end of file
diff --git a/modules/claims_api/README.md b/modules/claims_api/README.md
index 53cd34fdb30..14bc40e63d2 100644
--- a/modules/claims_api/README.md
+++ b/modules/claims_api/README.md
@@ -10,9 +10,9 @@ ssh -L 4447:localhost:4447 {{aws-url}}
ssh -L 4431:localhost:4431 {{aws-url}}
## Testing
-### Unit testing BGS service operation wrappers
+### Unit testing BGS service action wrappers
If using cassettes, make sure to only make or use ones under [spec/support/vcr_cassettes/claims_api](spec/support/vcr_cassettes/claims_api)
-Check out documentation in comments for the spec helper `BGSClientHelpers#use_bgs_cassette`
+Check out documentation in comments for the spec helper `BGSClientSpecHelpers#use_bgs_cassette`
## OpenApi/Swagger Doc Generation
This api uses [rswag](https://github.com/rswag/rswag) to build the OpenApi/Swagger docs that are displayed in the [VA|Lighthouse APIs Documentation](https://developer.va.gov/explore/benefits/docs/claims?version=current). To generate/update the docs for this api, navigate to the root directory of `vets-api` and run the following command ::
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client.rb b/modules/claims_api/app/clients/claims_api/bgs_client.rb
new file mode 100644
index 00000000000..61e30c9bdec
--- /dev/null
+++ b/modules/claims_api/app/clients/claims_api/bgs_client.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ module BGSClient
+ class << self
+ ##
+ # Invokes the given BGS SOAP service action with the given payload and
+ # returns a result containing a success payload or a fault.
+ #
+ # @example Perform a request to BGS at:
+ # /VDC/ManageRepresentativeService(readPOARequest)
+ #
+ # body = <<~EOXML
+ #
+ # New
+ #
+ #
+ # 012
+ #
+ # EOXML
+ #
+ # definition =
+ # BGSClient::ServiceAction::Definition::
+ # ManageRepresentativeService::
+ # ReadPoaRequest
+ #
+ # BGSClient.perform_request(
+ # definition:,
+ # body:
+ # )
+ #
+ # @param definition [BGSClient::ServiceAction::Definition] a value object
+ # that identifies a particular BGS SOAP service action by way of:
+ # `{.service_path, .service_namespaces, .action_name}`
+ #
+ # @param body [String, #to_xml, #to_s] the action payload
+ #
+ # @param external_id [BGSClient::ServiceAction::ExternalId] a value object
+ # that arbitrarily self-identifies ourselves to BGS as its caller by:
+ # `{.external_uid, .external_key}`
+ #
+ # @return [BGSClient::ServiceAction::Request::Result]
+ # the response payload of a successful request, or the fault object of a
+ # failed request
+ def perform_request(
+ definition:, body:,
+ external_id: ServiceAction::ExternalId::DEFAULT
+ )
+ ServiceAction
+ .const_get(:Request)
+ .new(definition:, external_id:)
+ .perform(body)
+ end
+
+ ##
+ # Reveals the momentary health of a BGS service by attempting to request
+ # its WSDL and returning the HTTP status code of the response.
+ #
+ # @example
+ # definition =
+ # BGSClient::ServiceAction::Definition::
+ # ManageRepresentativeService::
+ # ReadPoaRequest
+ #
+ # BGSClient.healthcheck(definition)
+ #
+ # @param definition [BGSClient::ServiceAction::Definition] a value object
+ # that identifies a particular BGS SOAP service action by way of:
+ # `{.service_path, .service_namespaces, .action_name}`
+ #
+ # @return [Integer] HTTP status code
+ #
+ # @todo We could also introduce the notion of just the service definition
+ # in our central repository of definitions so that:
+ # 1. Service action definitions and other code would be able to refer to
+ # them
+ # 2. We could improve this API so that it doesn't need to receive
+ # extraneous action information.
+ # But this is fine for now.
+ def healthcheck(definition)
+ connection = build_connection
+ response = fetch_wsdl(connection, definition)
+ response.status
+ end
+
+ def breakers_service
+ url = URI.parse(Settings.bgs.url)
+ request_matcher =
+ proc do |request_env|
+ request_env.url.host == url.host &&
+ request_env.url.port == url.port &&
+ request_env.url.path =~ /^#{url.path}/
+ end
+
+ Breakers::Service.new(
+ name: 'BGS/Claims',
+ request_matcher:
+ )
+ end
+
+ private
+
+ def fetch_wsdl(connection, definition)
+ connection.get(definition.service_path) do |req|
+ req.params['WSDL'] = nil
+ end
+ end
+
+ def build_connection
+ ssl_verify_mode =
+ if Settings.bgs.ssl_verify_mode == 'none'
+ OpenSSL::SSL::VERIFY_NONE
+ else
+ OpenSSL::SSL::VERIFY_PEER
+ end
+
+ Faraday.new(Settings.bgs.url) do |conn|
+ conn.ssl.verify_mode = ssl_verify_mode
+ yield(conn) if block_given?
+ end
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client/service_action/definition.rb b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/definition.rb
new file mode 100644
index 00000000000..4bbfa03cf35
--- /dev/null
+++ b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/definition.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ module BGSClient
+ module ServiceAction
+ class Definition <
+ Data.define(
+ :service_path,
+ :service_namespaces,
+ :action_name
+ )
+
+ module ManageRepresentativeService
+ service = {
+ service_path: 'VDC/ManageRepresentativeService',
+ service_namespaces: { 'data' => '/data' }
+ }
+
+ ReadPoaRequest =
+ Definition.new(
+ action_name: 'readPOARequest',
+ **service
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client/service_action/external_id.rb b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/external_id.rb
new file mode 100644
index 00000000000..2509fc58386
--- /dev/null
+++ b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/external_id.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ module BGSClient
+ module ServiceAction
+ class ExternalId < Data.define(:external_uid, :external_key)
+ DEFAULT =
+ new(
+ external_uid: Settings.bgs.external_uid,
+ external_key: Settings.bgs.external_key
+ )
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request.rb b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request.rb
new file mode 100644
index 00000000000..3cbff6e2811
--- /dev/null
+++ b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request.rb
@@ -0,0 +1,216 @@
+# frozen_string_literal: true
+
+require 'claims_api/claim_logger'
+
+module ClaimsApi
+ module BGSClient
+ module ServiceAction
+ # `private_constant` is used to prevent inheritance that could eventually
+ # tempt someone to add extraneous behavior to this, the parent class.
+ # Consumers should instead directly interface with
+ # `BGSClient.perform_request`, which maintains the sole responsibility of
+ # making a request to BGS.
+ private_constant :Request
+
+ class Request
+ attr_reader :external_id
+
+ def initialize(definition:, external_id:)
+ @definition = definition
+ @external_id = external_id
+ end
+
+ def perform(body) # rubocop:disable Metrics/MethodLength
+ begin
+ wsdl =
+ log_duration('connection_wsdl_get') do
+ BGSClient.send(
+ :fetch_wsdl,
+ connection,
+ @definition
+ ).body
+ end
+
+ request_body =
+ log_duration('built_request') do
+ wsdl_body = Hash.from_xml(wsdl)
+ namespace = wsdl_body.dig('definitions', 'targetNamespace').to_s
+ build_request_body(body, namespace:)
+ end
+
+ response =
+ log_duration('connection_post') do
+ connection.post(@definition.service_path) do |req|
+ req.body = request_body
+ req.headers.merge!(
+ 'Soapaction' => %("#{@definition.action_name}"),
+ 'Content-Type' => 'text/xml;charset=UTF-8',
+ 'Host' => "#{Settings.bgs.env}.vba.va.gov"
+ )
+ end
+ end
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
+ detail = "local BGS Faraday Timeout: #{e.message}"
+ ClaimsApi::Logger.log('local_bgs', retry: true, detail:)
+ raise ::Common::Exceptions::BadGateway
+ end
+
+ log_duration('parsed_response') do
+ response_body = Hash.from_xml(response.body)
+ action_body = response_body.dig('Envelope', 'Body').to_h
+ fault = get_fault(action_body)
+
+ if fault
+ Result.new(
+ success: false,
+ value: fault
+ )
+ else
+ key = "#{@definition.action_name}Response"
+ value = action_body[key].to_h
+
+ Result.new(
+ success: true,
+ value:
+ )
+ end
+ end
+ end
+
+ private
+
+ def build_request_body(body, namespace:) # rubocop:disable Metrics/MethodLength
+ namespaces =
+ {}.tap do |value|
+ namespace = URI(namespace)
+ value['tns'] = namespace
+
+ @definition.service_namespaces.to_h.each do |aliaz, path|
+ uri = namespace.clone
+ uri.path = path
+ value[aliaz] = uri
+ end
+ end
+
+ client_ip =
+ if Rails.env.test?
+ # For all intents and purposes, BGS behaves identically no matter
+ # what IP we provide it. So in a test environment, let's just give
+ # it a fake IP so that cassette matching isn't defeated on CI and
+ # everyone's computer.
+ '127.0.0.1'
+ else
+ Socket
+ .ip_address_list
+ .detect(&:ipv4_private?)
+ .ip_address
+ end
+
+ headers =
+ Envelope::Headers.new(
+ ip: client_ip,
+ username: Settings.bgs.client_username,
+ station_id: Settings.bgs.client_station_id,
+ application_name: Settings.bgs.application,
+ external_id:
+ )
+
+ action = @definition.action_name
+
+ Envelope.generate(
+ namespaces:,
+ headers:,
+ action:,
+ body:
+ )
+ end
+
+ def get_fault(body)
+ fault = body['Fault'].to_h
+ return if fault.blank?
+
+ message =
+ fault.dig('detail', 'MessageException') ||
+ fault.dig('detail', 'MessageFaultException')
+
+ Fault.new(
+ code: fault['faultcode'].to_s.split(':').last,
+ string: fault['faultstring'],
+ message:
+ )
+ end
+
+ def connection
+ @connection ||=
+ BGSClient.send(:build_connection) do |conn|
+ # Should all of this connection configuration really not be
+ # involved in the BGS service healthcheck performed by
+ # `BGSClient.healthcheck`? Under the hood, that just fetches WSDL
+ # which we also do here but with this more sophisticated logic.
+ # Maybe we truly don't want `breakers` and `timeout` logic to
+ # impact our assessment of service health in that context?
+ conn.use :breakers
+ conn.options.timeout = Settings.bgs.timeout || 120
+ end
+ end
+
+ # Use features of `SemanticLogger` like tags, metrics, benchmarking,
+ # appenders, etc rather than making bespoke implementations?
+ # https://logger.rocketjob.io/
+ def log_duration(event_name)
+ start = now
+ result = yield
+ finish = now
+
+ duration = (finish - start).round(4)
+ event = {
+ # event should be first key in log, duration last
+ event: event_name,
+ endpoint: @definition.service_path,
+ action: @definition.action_name,
+ duration:
+ }
+
+ ClaimsApi::Logger.log('local_bgs', **event)
+ metric = "api.claims_api.local_bgs.#{event_name}.duration"
+ StatsD.measure(metric, duration, tags: {})
+
+ result
+ end
+
+ def now
+ ::Process.clock_gettime(
+ ::Process::CLOCK_MONOTONIC
+ )
+ end
+
+ Fault =
+ Data.define(
+ :code,
+ :string,
+ :message
+ )
+
+ # Tiny subset of the API for `Dry::Monads[:result]`. Chose this
+ # particularly because some SOAP `500` really isn't error-like, and it
+ # is awkward to wrap exception handling for non-exceptional cases.
+ class Result
+ attr_reader :value
+
+ def initialize(value:, success:)
+ @value = value
+ @success = success
+ end
+
+ def success?
+ @success
+ end
+
+ def failure?
+ !success?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request/envelope.rb b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request/envelope.rb
new file mode 100644
index 00000000000..08bf04177b1
--- /dev/null
+++ b/modules/claims_api/app/clients/claims_api/bgs_client/service_action/request/envelope.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ module BGSClient
+ module ServiceAction
+ class Request
+ module Envelope
+ Headers =
+ Data.define(
+ :ip,
+ :username,
+ :station_id,
+ :application_name,
+ :external_id
+ )
+
+ # rubocop:disable Style/FormatStringToken
+ TEMPLATE = <<~EOXML
+
+
+
+
+
+ %{username}
+
+
+ %{ip}
+ %{station_id}
+ %{application_name}
+ %{external_uid}
+ %{external_key}
+
+
+
+
+ %{body}
+
+
+ EOXML
+ # rubocop:enable Style/FormatStringToken
+
+ class << self
+ def generate(namespaces:, headers:, action:, body:)
+ namespaces =
+ namespaces.map do |aliaz, uri|
+ %(xmlns:#{aliaz}="#{uri}")
+ end
+
+ headers = headers.to_h
+ external_id = headers.delete(:external_id).to_h
+
+ format(
+ TEMPLATE,
+ namespaces: namespaces.join("\n"),
+ **headers,
+ **external_id,
+ action:,
+ body:
+ )
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs.rb b/modules/claims_api/lib/bgs_service/local_bgs.rb
index 70cec2d24df..530bb62e046 100644
--- a/modules/claims_api/lib/bgs_service/local_bgs.rb
+++ b/modules/claims_api/lib/bgs_service/local_bgs.rb
@@ -212,8 +212,6 @@ def all(id)
end
# END: switching v1 from evss to bgs. Delete after EVSS is no longer available. Fix controller first.
- private
-
def header # rubocop:disable Metrics/MethodLength
# Stock XML structure {{{
header = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb b/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb
new file mode 100644
index 00000000000..07710b9d56a
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'bgs_service/local_bgs_refactored'
+require 'bgs_service/local_bgs'
+
+module ClaimsApi
+ # Proxy class that switches at runtime between using `LocalBGS` and
+ # `LocalBGSRefactored` depending on the value of our feature toggle.
+ class LocalBGSProxy
+ legacy_ancestors =
+ LocalBGS.ancestors -
+ LocalBGSRefactored.ancestors
+
+ legacy_api =
+ legacy_ancestors.flat_map do |ancestor|
+ ancestor.instance_methods(false) - [:initialize]
+ end
+
+ refactored_ancestors =
+ LocalBGSRefactored.ancestors -
+ LocalBGS.ancestors
+
+ refactored_api =
+ refactored_ancestors.flat_map do |ancestor|
+ ancestor.instance_methods(false) - [:initialize]
+ end
+
+ # This makes the assumption that we'll maintain compatibility for callers of
+ # `LocalBGS` by considering only its public instance methods, and in
+ # particular those not installed by framework-level ancestors. A "one-time"
+ # check was performed to ensure that instance methods that callers invoke
+ # directly are contained in `common_api` and not contained in `missing_api`.
+ missing_api = legacy_api - refactored_api
+ common_api = legacy_api & refactored_api
+
+ Rails.logger.trace(
+ "Comparison between LocalBGS and LocalBGSRefactored API's",
+ missing_api:,
+ common_api:
+ )
+
+ delegate(*common_api, to: :proxied)
+ attr_reader :proxied
+
+ def initialize(...)
+ @proxied =
+ if Flipper.enabled?(:claims_api_local_bgs_refactor)
+ LocalBGSRefactored.new(...)
+ else
+ LocalBGS.new(...)
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb b/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb
new file mode 100644
index 00000000000..5d8199fdc6a
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+# As a work of the United States Government, this project is in the
+# public domain within the United States.
+#
+# Additionally, we waive copyright and related rights in the work
+# worldwide through the CC0 1.0 Universal public domain dedication.
+
+require 'bgs_service/local_bgs_refactored/error_handler'
+require 'bgs_service/local_bgs_refactored/miscellaneous'
+require 'claims_api/claim_logger'
+
+module ClaimsApi
+ # @deprecated Use {BGSClient.perform_request} instead. There ought to be a
+ # clear separation between the single method that performs the transport to
+ # BGS and any business logic that invokes said transport. By housing that
+ # single method as an instance method of this class, we encouraged
+ # business logic modules to inherit this class and then inevitably start to
+ # conflate business logic back into the transport layer here. There was a
+ # particularly easy temptation to put business object state validation as
+ # well as the dumping and loading of business object state into this layer,
+ # but that should live in the business logic layer and not here.
+ class LocalBGSRefactored
+ include Miscellaneous
+
+ attr_reader :external_id
+
+ def initialize(
+ external_uid: Settings.bgs.external_uid,
+ external_key: Settings.bgs.external_key
+ )
+ @external_id =
+ BGSClient::ServiceAction::ExternalId.new(
+ external_uid:,
+ external_key:
+ )
+ end
+
+ def healthcheck(endpoint)
+ definition =
+ BGSClient::ServiceAction::Definition.new(
+ service_path: endpoint,
+ service_namespaces: nil,
+ action_name: nil
+ )
+
+ BGSClient.healthcheck(
+ definition
+ )
+ end
+
+ def make_request( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
+ endpoint:, action:, body:, key: nil,
+ namespaces: {}, transform_response: true
+ )
+ definition =
+ BGSClient::ServiceAction::Definition.new(
+ service_path: endpoint,
+ service_namespaces: namespaces,
+ action_name: action
+ )
+
+ request =
+ BGSClient::ServiceAction.const_get(:Request).new(
+ definition:,
+ external_id:
+ )
+
+ result = request.perform(body)
+
+ if result.success?
+ value = result.value.to_h
+ value = value[key].to_h if key.present?
+
+ if transform_response
+ request.send(:log_duration, 'transformed_response') do
+ value.deep_transform_keys! do |key|
+ key.underscore.to_sym
+ end
+ end
+ end
+
+ return value
+ end
+
+ ErrorHandler.handle_errors!(
+ result.value
+ )
+
+ {}
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_refactored/error_handler.rb b/modules/claims_api/lib/bgs_service/local_bgs_refactored/error_handler.rb
new file mode 100644
index 00000000000..2310b41620a
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/local_bgs_refactored/error_handler.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ class LocalBGSRefactored
+ # list of fault codes: https://hub.verj.io/ebase/doc/SOAP_Faults.htm
+ #
+ # TODO: Some (or all) of these cases should be handled in consumers and not
+ # in a central location.
+ class ErrorHandler
+ class << self
+ def handle_errors!(fault)
+ new(fault).handle_errors!
+ end
+ end
+
+ def initialize(fault)
+ @fault = fault
+ end
+
+ def handle_errors!
+ return if not_error?
+
+ if not_found?
+ raise ::Common::Exceptions::ResourceNotFound.new(detail: 'Resource not found.')
+ elsif bnft_claim_not_found?
+ {}
+ elsif unprocessable?
+ raise ::Common::Exceptions::UnprocessableEntity.new(
+ detail: 'Please try again after checking your input values.'
+ )
+ else
+ soap_logging('500')
+ raise ::Common::Exceptions::ServiceError.new(detail: 'An external server is experiencing difficulty.')
+ end
+ end
+
+ private
+
+ def not_error?
+ @fault.string.include?('IntentToFileWebService') &&
+ @fault.string.include?('not found')
+ end
+
+ def not_found?
+ errors = ['bnftClaimId-->bnftClaimId/text()', 'not found', 'No Person found']
+ has_errors = errors.any? { |error| @fault.string.include? error }
+ soap_logging('404') if has_errors
+ has_errors
+ end
+
+ def bnft_claim_not_found?
+ errors = ['No BnftClaim found']
+ has_errors = errors.any? { |error| @fault.string.include? error }
+ soap_logging('404') if has_errors
+ has_errors
+ end
+
+ def unprocessable?
+ errors = ['java.sql', 'MessageException', 'Validation errors', 'Exception Description',
+ 'does not have necessary info', 'Error committing transaction', 'Transaction Rolledback',
+ 'Unexpected error', 'XML reader error', 'could not be converted']
+ has_errors = errors.any? { |error| @fault.string.include? error }
+ soap_logging('422') if has_errors
+ has_errors
+ end
+
+ def soap_logging(status_code)
+ ClaimsApi::Logger.log('soap_error_handler',
+ detail: "Returning #{status_code} via local_bgs & soap_error_handler, " \
+ "fault_string: #{@fault.string}, with message: #{@fault.message}, " \
+ "and fault_code: #{@fault.code}.")
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_refactored/miscellaneous.rb b/modules/claims_api/lib/bgs_service/local_bgs_refactored/miscellaneous.rb
new file mode 100644
index 00000000000..bc17d46b50c
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/local_bgs_refactored/miscellaneous.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+
+require 'claims_api/evss_bgs_mapper'
+
+module ClaimsApi
+ class LocalBGSRefactored
+ module Miscellaneous # rubocop:disable Metrics/ModuleLength
+ def find_poa_by_participant_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ptcpntId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'ClaimantServiceBean/ClaimantWebService', action: 'findPOAByPtcpntId', body:,
+ key: 'return')
+ end
+
+ def find_by_ssn(ssn)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ssn: }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'PersonWebServiceBean/PersonWebService', action: 'findPersonBySSN', body:,
+ key: 'PersonDTO')
+ end
+
+ def find_poa_history_by_ptcpnt_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ptcpntId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'OrgWebServiceBean/OrgWebService', action: 'findPoaHistoryByPtcpntId', body:,
+ key: 'PoaHistory')
+ end
+
+ def find_benefit_claims_status_by_ptcpnt_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ptcpntId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'EBenefitsBnftClaimStatusWebServiceBean/EBenefitsBnftClaimStatusWebService',
+ action: 'findBenefitClaimsStatusByPtcpntId', body:)
+ end
+
+ def claims_count(id)
+ find_benefit_claims_status_by_ptcpnt_id(id).count
+ rescue ::Common::Exceptions::ResourceNotFound
+ 0
+ end
+
+ def find_benefit_claim_details_by_benefit_claim_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { bnftClaimId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'EBenefitsBnftClaimStatusWebServiceBean/EBenefitsBnftClaimStatusWebService',
+ action: 'findBenefitClaimDetailsByBnftClaimId', body:)
+ end
+
+ def insert_intent_to_file(options)
+ request_body = construct_itf_body(options)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+
+ EOXML
+
+ request_body.each do |k, z|
+ node = Nokogiri::XML::Node.new k.to_s, body
+ node.content = z.to_s
+ opt = body.at('intentToFileDTO')
+ node.parent = opt
+ end
+ make_request(endpoint: 'IntentToFileWebServiceBean/IntentToFileWebService', action: 'insertIntentToFile',
+ body:, key: 'IntentToFileDTO')
+ end
+
+ def find_tracked_items(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { claimId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: 'TrackedItemService/TrackedItemService', action: 'findTrackedItems', body:,
+ key: 'BenefitClaim')
+ end
+
+ def find_intent_to_file_by_ptcpnt_id_itf_type_cd(id, type)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ ptcpnt_id = body.at 'ptcpntId'
+ ptcpnt_id.content = id.to_s
+ itf_type_cd = body.at 'itfTypeCd'
+ itf_type_cd.content = type.to_s
+
+ response =
+ make_request(
+ endpoint: 'IntentToFileWebServiceBean/IntentToFileWebService',
+ action: 'findIntentToFileByPtcpntIdItfTypeCd',
+ body:
+ )
+
+ Array.wrap(response[:intent_to_file_dto])
+ end
+
+ # BEGIN: switching v1 from evss to bgs. Delete after EVSS is no longer available. Fix controller first.
+ def update_from_remote(id)
+ bgs_claim = find_benefit_claim_details_by_benefit_claim_id(id)
+ transform_bgs_claim_to_evss(bgs_claim)
+ end
+
+ def all(id)
+ claims = find_benefit_claims_status_by_ptcpnt_id(id)
+ return [] if claims.count < 1 || claims[:benefit_claims_dto].blank?
+
+ transform_bgs_claims_to_evss(claims)
+ end
+ # END: switching v1 from evss to bgs. Delete after EVSS is no longer available. Fix controller first.
+
+ def construct_itf_body(options)
+ request_body = {
+ itfTypeCd: options[:intent_to_file_type_code],
+ ptcpntVetId: options[:participant_vet_id],
+ rcvdDt: options[:received_date],
+ signtrInd: options[:signature_indicated],
+ submtrApplcnTypeCd: options[:submitter_application_icn_type_code]
+ }
+ request_body[:ptcpntClmantId] = options[:participant_claimant_id] if options.key?(:participant_claimant_id)
+ request_body[:clmantSsn] = options[:claimant_ssn] if options.key?(:claimant_ssn)
+ request_body
+ end
+
+ def transform_bgs_claim_to_evss(claim)
+ bgs_claim = ClaimsApi::EvssBgsMapper.new(claim[:benefit_claim_details_dto])
+ return if bgs_claim.nil?
+
+ bgs_claim.map_and_build_object
+ end
+
+ def transform_bgs_claims_to_evss(claims)
+ claims_array = [claims[:benefit_claims_dto][:benefit_claim]].flatten
+ claims_array&.map do |claim|
+ bgs_claim = ClaimsApi::EvssBgsMapper.new(claim)
+ bgs_claim.map_and_build_object
+ end
+ end
+
+ def convert_nil_values(options)
+ arg_strg = ''
+ options.each do |option|
+ arg = option[0].to_s.camelize(:lower)
+ arg_strg += (option[1].nil? ? "<#{arg} xsi:nil='true'/>" : "<#{arg}>#{option[1]}#{arg}>")
+ end
+ arg_strg
+ end
+
+ def validate_opts!(opts, required_keys)
+ keys = opts.keys.map(&:to_s)
+ required_keys = required_keys.map(&:to_s)
+ missing_keys = required_keys - keys
+ raise ArgumentError, "Missing required keys: #{missing_keys.join(', ')}" if missing_keys.present?
+ end
+
+ def jrn
+ {
+ jrn_dt: Time.current.iso8601,
+ jrn_lctn_id: Settings.bgs.client_station_id,
+ jrn_status_type_cd: 'U',
+ jrn_user_id: Settings.bgs.client_username,
+ jrn_obj_id: Settings.bgs.application
+ }
+ end
+
+ def to_camelcase(claim:)
+ claim.deep_transform_keys { |k| k.to_s.camelize(:lower) }
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/create_veteran_representative_request_spec.rb b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/create_veteran_representative_request_spec.rb
index ebcf7905201..6b4a039e1cf 100644
--- a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/create_veteran_representative_request_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/create_veteran_representative_request_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/veteran_representative_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'veteran_representative_service',
- operation: 'create_veteran_representative'
+ action: 'create_veteran_representative'
}
}
diff --git a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/read_all_veteran_representatives_spec.rb b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/read_all_veteran_representatives_spec.rb
index cdbabb597e4..4bdcde0fdbf 100644
--- a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/read_all_veteran_representatives_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/read_all_veteran_representatives_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/veteran_representative_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'veteran_representative_service',
- operation: 'read_all_veteran_representatives'
+ action: 'read_all_veteran_representatives'
},
run_at: '2024-04-17T23:10:31+00:00'
}
diff --git a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/veteran_representative_service_spec.rb b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/veteran_representative_service_spec.rb
index c2db3c02fd1..5a35a2cacad 100644
--- a/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/veteran_representative_service_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/bgs/veteran_representative_service/veteran_representative_service_spec.rb
@@ -2,7 +2,6 @@
require 'rails_helper'
require 'bgs_service/veteran_representative_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
describe ClaimsApi::VeteranRepresentativeService do
let(:header_params) do
diff --git a/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb b/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb
new file mode 100644
index 00000000000..f016cfa02ca
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'bgs_service/local_bgs_proxy'
+
+describe ClaimsApi::LocalBGSProxy do
+ subject do
+ described_class.new(
+ external_uid: nil,
+ external_key: nil
+ )
+ end
+
+ expected_instance_methods = {
+ all: %i[id],
+ claims_count: %i[id],
+ construct_itf_body: %i[options],
+ convert_nil_values: %i[options],
+ find_benefit_claim_details_by_benefit_claim_id: %i[id],
+ find_benefit_claims_status_by_ptcpnt_id: %i[id],
+ find_by_ssn: %i[ssn],
+ find_intent_to_file_by_ptcpnt_id_itf_type_cd: %i[id type],
+ find_poa_by_participant_id: %i[id],
+ find_poa_history_by_ptcpnt_id: %i[id],
+ find_tracked_items: %i[id],
+ healthcheck: %i[endpoint],
+ insert_intent_to_file: %i[options],
+ jrn: %i[],
+ make_request: [endpoint: nil, action: nil, body: nil],
+ to_camelcase: [claim: nil],
+ transform_bgs_claim_to_evss: %i[claim],
+ transform_bgs_claims_to_evss: %i[claims],
+ update_from_remote: %i[id],
+ validate_opts!: %i[opts required_keys]
+ }
+
+ expected_instance_methods.each_value(&:freeze)
+ expected_instance_methods.freeze
+
+ it 'defines the correct set of instance methods' do
+ actual = described_class.instance_methods(false) - [:proxied]
+ expect(actual).to match_array(expected_instance_methods.keys)
+ end
+
+ describe 'claims_api_local_bgs_refactor feature toggling' do
+ before do
+ expect(Flipper).to(
+ receive(:enabled?)
+ .with(:claims_api_local_bgs_refactor)
+ .and_return(toggle)
+ )
+ end
+
+ define_singleton_method(:it_delegates_every_instance_method) do |to:|
+ it "has a proxied of type #{to}" do
+ expect(subject.proxied).to be_a(to)
+ end
+
+ expected_instance_methods.each do |meth, args|
+ describe "when instance method is `#{meth}`" do
+ it "delegates to `#{to}`" do
+ if args.empty?
+ expect(subject.proxied).to receive(meth).with(no_args).once
+ subject.send(meth)
+ else
+ args = args.deep_dup
+ kwargs = args.extract_options!
+ expect(subject.proxied).to receive(meth).with(*args, **kwargs).once
+ subject.send(meth, *args, **kwargs)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'with refactor toggled off' do
+ let(:toggle) { false }
+
+ it_delegates_every_instance_method(
+ to: ClaimsApi::LocalBGS
+ )
+ end
+
+ describe 'with refactor toggled on' do
+ let(:toggle) { true }
+
+ it_delegates_every_instance_method(
+ to: ClaimsApi::LocalBGSRefactored
+ )
+ end
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb b/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb
new file mode 100644
index 00000000000..bd0bb5c4689
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'bgs_service/local_bgs_proxy'
+
+describe ClaimsApi::LocalBGSProxy do
+ subject { described_class.new external_uid: 'xUid', external_key: 'xKey' }
+
+ before do
+ allow(Flipper).to(
+ receive(:enabled?)
+ .with(:claims_api_local_bgs_refactor)
+ .and_return(true)
+ )
+ end
+
+ let(:soap_error_handler) { ClaimsApi::LocalBGSRefactored::ErrorHandler }
+
+ describe '#find_poa_by_participant_id' do
+ it 'responds as expected, with extra ClaimsApi::Logger logging' do
+ VCR.use_cassette('claims_api/bgs/claimant_web_service/find_poa_by_participant_id') do
+ # Events logged:
+ # 1: connection_wsdl_get - duration of WSDL request cycle
+ # 2: built_request - how long to build the request
+ # 3: connection_post - how long does the post itself take for the request cycle
+ # 4: parsed_response - how long to parse the response
+ # 5: transformed_response - how long to transform the response
+ expect(ClaimsApi::Logger).to receive(:log).exactly(5).times
+ result = subject.find_poa_by_participant_id('does-not-matter')
+ expect(result).to be_a Hash
+ expect(result[:end_date]).to eq '08/26/2020'
+ end
+ end
+
+ describe 'breakers' do
+ it 'returns a Bad Gateway' do
+ stub_request(:any, "#{Settings.bgs.url}/ClaimantServiceBean/ClaimantWebService?WSDL").to_timeout
+ expect do
+ subject.find_poa_by_participant_id('also-does-not-matter')
+ end.to raise_error(Common::Exceptions::BadGateway)
+ end
+
+ it 'hits breakers' do
+ ClaimsApi::BGSClient.breakers_service.begin_forced_outage!
+ expect { subject.find_poa_by_participant_id('also-does-not-matter') }.to raise_error(Breakers::OutageException)
+ ClaimsApi::BGSClient.breakers_service.end_forced_outage!
+ end
+ end
+
+ it 'triggers StatsD measurements' do
+ VCR.use_cassette('claims_api/bgs/claimant_web_service/find_poa_by_participant_id',
+ allow_playback_repeats: true) do
+ %w[connection_wsdl_get built_request connection_post parsed_response transformed_response].each do |event|
+ expect { subject.find_poa_by_participant_id('does-not-matter') }
+ .to trigger_statsd_measure("api.claims_api.local_bgs.#{event}.duration")
+ end
+ end
+ end
+ end
+
+ # Testing potential ways the current check could be tricked
+ describe '#all' do
+ let(:subject_instance) { subject }
+ let(:id) { 12_343 }
+ let(:error_message) { { error: 'Did not work', code: 'XXX' } }
+ let(:bgs_unknown_error_message) { { error: 'Unexpected error' } }
+ let(:empty_array) { [] }
+
+ context 'when an error message gets returned it still does not pass the count check' do
+ it 'returns an empty array' do
+ expect(error_message.count).to eq(2) # trick the claims count check
+ # error message should trigger return
+ allow(subject_instance.proxied).to(
+ receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(error_message)
+ )
+ expect(subject.all(id)).to eq([]) # verify correct return
+ end
+ end
+
+ context 'when claims come back as a hash instead of an array' do
+ it 'casts the hash as an array' do
+ VCR.use_cassette('claims_api/bgs/claims/claims_trimmed_down') do
+ claims = subject_instance.find_benefit_claims_status_by_ptcpnt_id('600061742')
+ claims[:benefit_claims_dto][:benefit_claim] = claims[:benefit_claims_dto][:benefit_claim][0]
+ allow(subject_instance.proxied).to(
+ receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(claims)
+ )
+
+ begin
+ ret = subject_instance.send(:transform_bgs_claims_to_evss, claims)
+ expect(ret.class).to_be Array
+ expect(ret.size).to eq 1
+ rescue => e
+ expect(e.message).not_to include 'no implicit conversion of Array into Hash'
+ end
+ end
+ end
+ end
+
+ # Already being checked but based on an error seen just want to lock this in to ensure nothing gets missed
+ context 'when an empty array gets returned it still does not pass the count check' do
+ it 'returns an empty array' do
+ # error message should trigger return
+ allow(subject_instance.proxied).to(
+ receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(empty_array)
+ )
+ expect(subject.all(id)).to eq([]) # verify correct return
+ end
+ end
+
+ context 'when an error message gets returns unknown' do
+ it 'the soap error handler returns unprocessable' do
+ allow(subject_instance).to receive(:make_request).with(endpoint: 'PersonWebServiceBean/PersonWebService',
+ action: 'findPersonBySSN',
+ body: Nokogiri::XML::DocumentFragment.new(
+ Nokogiri::XML::Document.new
+ ),
+ key: 'PersonDTO').and_return(:bgs_unknown_error_message)
+ begin
+ allow(soap_error_handler).to receive(:handle_errors!)
+ .with(:bgs_unknown_error_message).and_raise(Common::Exceptions::UnprocessableEntity)
+ ret = soap_error_handler.send(:handle_errors!, :bgs_unknown_error_message)
+ expect(ret.class).to_be Array
+ expect(ret.size).to eq 1
+ rescue => e
+ expect(e.message).to include 'Unprocessable Entity'
+ end
+ end
+ end
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/manage_representative_service/read_poa_request_spec.rb b/modules/claims_api/spec/lib/claims_api/manage_representative_service/read_poa_request_spec.rb
index 24ba183322a..af695f4a26c 100644
--- a/modules/claims_api/spec/lib/claims_api/manage_representative_service/read_poa_request_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/manage_representative_service/read_poa_request_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/manage_representative_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'manage_representative_service',
- operation: 'read_poa_request'
+ action: 'read_poa_request'
}
}
diff --git a/modules/claims_api/spec/lib/claims_api/manage_representative_service/update_poa_request_spec.rb b/modules/claims_api/spec/lib/claims_api/manage_representative_service/update_poa_request_spec.rb
index 525f504f95b..680e7acf9b9 100644
--- a/modules/claims_api/spec/lib/claims_api/manage_representative_service/update_poa_request_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/manage_representative_service/update_poa_request_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/manage_representative_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'manage_representative_service',
- operation: 'update_poa_request'
+ action: 'update_poa_request'
}
}
diff --git a/modules/claims_api/spec/lib/claims_api/vnp_atchms_service_spec.rb b/modules/claims_api/spec/lib/claims_api/vnp_atchms_service_spec.rb
index 6260c7993a6..96e078dbde3 100644
--- a/modules/claims_api/spec/lib/claims_api/vnp_atchms_service_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/vnp_atchms_service_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/vnp_atchms_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'vnp_atchms_service',
- operation: 'vnp_atchms_create'
+ action: 'vnp_atchms_create'
}
}
diff --git a/modules/claims_api/spec/lib/claims_api/vnp_person_service_spec.rb b/modules/claims_api/spec/lib/claims_api/vnp_person_service_spec.rb
index 47acbb2681c..9cf16770b8c 100644
--- a/modules/claims_api/spec/lib/claims_api/vnp_person_service_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/vnp_person_service_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/vnp_person_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'vnp_person_service',
- operation: 'vnp_person_create'
+ action: 'vnp_person_create'
}
}
diff --git a/modules/claims_api/spec/lib/claims_api/vnp_ptcpnt_addrs_service_spec.rb b/modules/claims_api/spec/lib/claims_api/vnp_ptcpnt_addrs_service_spec.rb
index e9dfa8550cb..7a626a44c3f 100644
--- a/modules/claims_api/spec/lib/claims_api/vnp_ptcpnt_addrs_service_spec.rb
+++ b/modules/claims_api/spec/lib/claims_api/vnp_ptcpnt_addrs_service_spec.rb
@@ -2,12 +2,12 @@
require 'rails_helper'
require 'bgs_service/vnp_ptcpnt_addrs_service'
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
bgs: {
service: 'vnp_ptcpnt_addrs_service',
- operation: 'vnp_ptcpnt_addrs_create'
+ action: 'vnp_ptcpnt_addrs_create'
}
}
diff --git a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag_spec.rb b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag_spec.rb
index b4125980b5e..fa1816989a4 100644
--- a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag_spec.rb
+++ b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag_spec.rb
@@ -4,14 +4,14 @@
require Rails.root.join('spec', 'rswag_override.rb').to_s
require 'rails_helper'
require Rails.root.join('modules', 'claims_api', 'spec', 'rails_helper.rb')
-require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_helpers.rb')
+require Rails.root.join('modules', 'claims_api', 'spec', 'support', 'bgs_client_spec_helpers.rb')
metadata = {
openapi_spec: Rswag::TextHelpers.new.claims_api_docs,
production: false,
bgs: {
service: 'manage_representative_service',
- operation: 'read_poa_request'
+ action: 'read_poa_request'
}
}
diff --git a/modules/claims_api/spec/support/bgs_client_helpers.rb b/modules/claims_api/spec/support/bgs_client_spec_helpers.rb
similarity index 74%
rename from modules/claims_api/spec/support/bgs_client_helpers.rb
rename to modules/claims_api/spec/support/bgs_client_spec_helpers.rb
index 4d6394c50fd..884d28f3c01 100644
--- a/modules/claims_api/spec/support/bgs_client_helpers.rb
+++ b/modules/claims_api/spec/support/bgs_client_spec_helpers.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module BGSClientHelpers
+module BGSClientSpecHelpers
# If one finds this request matcher useful elsewhere in the future,
# Rather than using a callable custom request matcher:
# https://benoittgt.github.io/vcr/#/request_matching/custom_matcher?id=use-a-callable-as-a-custom-request-matcher
@@ -19,44 +19,53 @@ module BGSClientHelpers
end
VCR_OPTIONS = {
- # Consider matching on `:headers` too?
match_requests_on: [
- :method, :uri,
+ :method, :uri, :headers,
body_as_xml_matcher.freeze
].freeze
}.freeze
# This convenience method affords a handful of quality of life improvements
- # for developing BGS service operation wrappers. It makes development a less
+ # for developing BGS service action wrappers. It makes development a less
# manual process. It also turns VCR cassettes into a human readable resource
# that documents the behavior of BGS.
#
# In order to take advantage of this method, you will need to have supplied,
# to your example or example group, metadata of this form:
- # `{ bgs: { service: "service", operation: "operation" } }`.
+ # `{ bgs: { service: "service", action: "action" } }`.
#
# Then, HTTP interactions that occur within the block supplied to this method
# will be captured by VCR cassettes that have the following convenient
# properties:
- # - They will be nicely organized at `claims_api/bgs/:service/:operation/:name`
+ # - They will be nicely organized at `claims_api/bgs/:service/:action/:name`
# - Cassette matching will be done on canonicalized XML bodies, so
# reformatting cassettes for human readability won't defeat matching
def use_bgs_cassette(name, &)
metadata = RSpec.current_example.metadata[:bgs].to_h
- service, operation = metadata.values_at(:service, :operation)
+ service, action = metadata.values_at(:service, :action)
- if service.blank? || operation.blank?
+ if service.blank? || action.blank?
raise ArgumentError, <<~HEREDOC
Must provide spec metadata of the form:
- `{ bgs: { service: "service", operation: "operation" } }'
+ `{ bgs: { service: "service", action: "action" } }'
HEREDOC
end
- name = File.join('claims_api/bgs', service, operation, name)
+ name = File.join('claims_api/bgs', service, action, name)
VCR.use_cassette(name, VCR_OPTIONS, &)
end
end
RSpec.configure do |config|
- config.include BGSClientHelpers, :bgs
+ config.include BGSClientSpecHelpers, :bgs
+
+ unless Settings.bgs.refactor.nil?
+ config.before(:example, :bgs) do
+ allow(Flipper).to(
+ receive(:enabled?)
+ .with(:claims_api_local_bgs_refactor)
+ .and_return(Settings.bgs.refactor)
+ )
+ end
+ end
end
diff --git a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
index a34f2dfb283..9a033b26359 100644
--- a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
+++ b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/uploads_controller.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
require 'ddtrace'
-require 'simple_forms_api_submission/service'
require 'simple_forms_api_submission/metadata_validator'
-require 'simple_forms_api_submission/s3'
require 'lgy/service'
module SimpleFormsApi
@@ -112,7 +110,8 @@ def submit_form_to_central_mail
parsed_form_data = JSON.parse(params.to_json)
file_path, metadata, form = get_file_paths_and_metadata(parsed_form_data)
- status, confirmation_number = upload_pdf_to_benefits_intake(file_path, metadata, form_id)
+ status, confirmation_number = SimpleFormsApi::PdfUploader.new(file_path, metadata,
+ form_id).upload_to_benefits_intake(params)
form.track_user_identity(confirmation_number)
Rails.logger.info(
@@ -147,43 +146,6 @@ def get_file_paths_and_metadata(parsed_form_data)
[file_path, metadata, form]
end
- def get_upload_location_and_uuid(lighthouse_service, form_id)
- upload_location = lighthouse_service.get_upload_location.body
- if form_id == 'vba_40_10007'
- uuid = upload_location.dig('data', 'id')
- SimpleFormsApi::PdfStamper.stamp4010007_uuid(uuid)
- end
- {
- uuid: upload_location.dig('data', 'id'),
- location: upload_location.dig('data', 'attributes', 'location')
- }
- end
-
- def upload_pdf_to_benefits_intake(file_path, metadata, form_id)
- lighthouse_service = SimpleFormsApiSubmission::Service.new
- uuid_and_location = get_upload_location_and_uuid(lighthouse_service, form_id)
- form_submission = FormSubmission.create(
- form_type: params[:form_number],
- benefits_intake_uuid: uuid_and_location[:uuid],
- form_data: params.to_json,
- user_account: @current_user&.user_account
- )
- FormSubmissionAttempt.create(form_submission:)
-
- Datadog::Tracing.active_trace&.set_tag('uuid', uuid_and_location[:uuid])
- Rails.logger.info(
- 'Simple forms api - preparing to upload PDF to benefits intake',
- { location: uuid_and_location[:location], uuid: uuid_and_location[:uuid] }
- )
- response = lighthouse_service.upload_doc(
- upload_url: uuid_and_location[:location],
- file: file_path,
- metadata: metadata.to_json
- )
-
- [response.status, uuid_and_location[:uuid]]
- end
-
def form_is210966
params[:form_number] == '21-0966'
end
diff --git a/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb b/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb
new file mode 100644
index 00000000000..40ed1d8179b
--- /dev/null
+++ b/modules/simple_forms_api/app/services/simple_forms_api/pdf_uploader.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'simple_forms_api_submission/service'
+
+module SimpleFormsApi
+ class PdfUploader
+ attr_reader :file_path, :metadata, :form_id
+
+ def initialize(file_path, metadata, form_id)
+ @file_path = file_path
+ @metadata = metadata
+ @form_id = form_id
+ end
+
+ def upload_to_benefits_intake(params)
+ lighthouse_service = SimpleFormsApiSubmission::Service.new
+ uuid_and_location = get_upload_location_and_uuid(lighthouse_service, form_id)
+ form_submission = FormSubmission.create(
+ form_type: params[:form_number],
+ benefits_intake_uuid: uuid_and_location[:uuid],
+ form_data: params.to_json,
+ user_account: @current_user&.user_account
+ )
+ FormSubmissionAttempt.create(form_submission:)
+
+ Datadog::Tracing.active_trace&.set_tag('uuid', uuid_and_location[:uuid])
+ Rails.logger.info(
+ 'Simple forms api - preparing to upload PDF to benefits intake',
+ { location: uuid_and_location[:location], uuid: uuid_and_location[:uuid] }
+ )
+ response = lighthouse_service.upload_doc(
+ upload_url: uuid_and_location[:location],
+ file: file_path,
+ metadata: metadata.to_json
+ )
+
+ [response.status, uuid_and_location[:uuid]]
+ end
+
+ private
+
+ def get_upload_location_and_uuid(lighthouse_service, form_id)
+ upload_location = lighthouse_service.get_upload_location.body
+ if form_id == 'vba_40_10007'
+ uuid = upload_location.dig('data', 'id')
+ SimpleFormsApi::PdfStamper.stamp4010007_uuid(uuid)
+ end
+ {
+ uuid: upload_location.dig('data', 'id'),
+ location: upload_location.dig('data', 'attributes', 'location')
+ }
+ end
+ end
+end
diff --git a/modules/simple_forms_api/spec/requests/v1/uploads_spec.rb b/modules/simple_forms_api/spec/requests/v1/uploads_spec.rb
index a9c7d84b2cd..abdc9dd939c 100644
--- a/modules/simple_forms_api/spec/requests/v1/uploads_spec.rb
+++ b/modules/simple_forms_api/spec/requests/v1/uploads_spec.rb
@@ -516,8 +516,8 @@
it 'successful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([200, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([200, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -536,8 +536,8 @@
it 'unsuccessful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([500, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([500, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -557,8 +557,8 @@
it 'successful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([200, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([200, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -577,8 +577,8 @@
it 'unsuccessful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([500, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([500, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -604,11 +604,8 @@
it 'successful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(
- SimpleFormsApi::V1::UploadsController
- ).to receive(
- :upload_pdf_to_benefits_intake
- ).and_return([200, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([200, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -628,11 +625,8 @@
it 'unsuccessful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(
- SimpleFormsApi::V1::UploadsController
- ).to receive(
- :upload_pdf_to_benefits_intake
- ).and_return([500, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([500, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -652,8 +646,8 @@
it 'successful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([200, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([200, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
@@ -672,8 +666,8 @@
it 'unsuccessful submission' do
allow(VANotify::EmailJob).to receive(:perform_async)
- allow_any_instance_of(SimpleFormsApi::V1::UploadsController)
- .to receive(:upload_pdf_to_benefits_intake).and_return([500, confirmation_number])
+ allow_any_instance_of(SimpleFormsApi::PdfUploader)
+ .to receive(:upload_to_benefits_intake).and_return([500, confirmation_number])
post '/simple_forms_api/v1/simple_forms', params: data
diff --git a/modules/simple_forms_api/spec/services/pdf_uploader_spec.rb b/modules/simple_forms_api/spec/services/pdf_uploader_spec.rb
new file mode 100644
index 00000000000..afdaf7b8d58
--- /dev/null
+++ b/modules/simple_forms_api/spec/services/pdf_uploader_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require SimpleFormsApi::Engine.root.join('spec', 'spec_helper.rb')
+require 'simple_forms_api_submission/service'
+
+describe SimpleFormsApi::PdfUploader do
+ describe '#upload_to_benefits_intake' do
+ it 'returns the status and uuid from Lighthouse' do
+ file_path = '/some-path'
+ metadata = { 'meta' => 'data' }
+ form_id = '12-3456'
+ expected_status = 200
+ expected_uuid = 'some-uuid'
+ lighthouse_service = double
+ upload_location = double
+ body = { 'data' => { 'id' => expected_uuid } }
+ params = { form_number: form_id }
+ expected_response = double(status: expected_status)
+ allow(upload_location).to receive(:body).and_return body
+ allow(lighthouse_service).to receive(:get_upload_location).and_return upload_location
+ allow(lighthouse_service).to receive(:upload_doc).and_return expected_response
+ allow(SimpleFormsApiSubmission::Service).to receive(:new).and_return lighthouse_service
+
+ pdf_uploader = SimpleFormsApi::PdfUploader.new(file_path, metadata, form_id)
+
+ expect(pdf_uploader.upload_to_benefits_intake(params)).to eq [expected_status, expected_uuid]
+ end
+ end
+end
diff --git a/spec/models/form_profile_spec.rb b/spec/models/form_profile_spec.rb
index f6c9d2ddac8..c7e16b972eb 100644
--- a/spec/models/form_profile_spec.rb
+++ b/spec/models/form_profile_spec.rb
@@ -1815,5 +1815,20 @@ def expect_prefilled(form_id)
instance2.prefill
end
end
+
+ context '10-7959F-1 form profile instances' do
+ let(:instance) { FormProfile.new(form_id: '10-7959F-1', user:) }
+
+ it 'loads the yaml file only once' do
+ expect(YAML).to receive(:load_file).once.and_return(
+ 'veteran_full_name' => %w[identity_information full_name],
+ 'veteran_address' => %w[contact_information address],
+ 'veteranSocialSecurityNumber' => %w[identity_information ssn],
+ 'phoneNumber' => %w[contact_information us_phone],
+ 'emailAddress' => %w[contact_information email]
+ )
+ instance.prefill
+ end
+ end
end
end