diff --git a/app/services/usps_in_person_proofing/proofer.rb b/app/services/usps_in_person_proofing/proofer.rb index 4210627b77e..21c3c4b479a 100644 --- a/app/services/usps_in_person_proofing/proofer.rb +++ b/app/services/usps_in_person_proofing/proofer.rb @@ -1,6 +1,6 @@ module UspsInPersonProofing class Proofer - mattr_reader :token, :token_expires_at + API_TOKEN_CACHE_KEY = :usps_ippaas_api_token # Makes HTTP request to get nearby in-person proofing facilities # Requires address, city, state and zip code. @@ -102,17 +102,28 @@ def request_enrollment_code(unique_id) end # Makes a request to retrieve a new OAuth token - # and modifies self to store the token and when + # and updates the cached token value # it expires (15 minutes). - # @return [String] the token + # @return [String] Auth token def retrieve_token! - body = request_token - @@token_expires_at = Time.zone.now + body['expires_in'] - @@token = "#{body['token_type']} #{body['access_token']}" + token, expires_in, token_type = request_token.fetch_values( + 'access_token', + 'expires_in', + 'token_type', + ) + authz = "#{token_type} #{token}" + Rails.cache.write( + API_TOKEN_CACHE_KEY, authz, + expires_at: Time.zone.now + expires_in + ) + authz end - def token_valid? - token.present? && token_expires_at.present? && token_expires_at.future? + # Checks the cache for an unexpired token and returns it. If the cache has expired, retrieves + # a new token and returns it + # @return [String] Auth token + def token + Rails.cache.read(API_TOKEN_CACHE_KEY) || retrieve_token! end private @@ -144,8 +155,6 @@ def faraday # # Returns the same value returned by that block of code. def dynamic_headers - retrieve_token! unless token_valid? - { 'Authorization' => token, 'RequestID' => request_id, diff --git a/spec/services/arcgis_api/geocoder_spec.rb b/spec/services/arcgis_api/geocoder_spec.rb index c6d63ed0207..0d4b1152c62 100644 --- a/spec/services/arcgis_api/geocoder_spec.rb +++ b/spec/services/arcgis_api/geocoder_spec.rb @@ -81,11 +81,12 @@ end describe '#retrieve_token!' do - it 'sets token and token_expires_at' do + it 'caches the token' do stub_generate_token_response - subject.retrieve_token! + token = subject.retrieve_token! - expect(subject.token).to be_present + expect(subject).not_to receive(:request_token) + expect(subject.token).to eq(token) end it 'calls the endpoint with the expected params' do @@ -121,10 +122,6 @@ end it 'implicitly refreshes the token when expired' do - root_url = 'http://my.root.url' - allow(IdentityConfig.store).to receive(:arcgis_api_root_url). - and_return(root_url) - stub_generate_token_response(expires_at: 1.hour.from_now.to_i, token: 'token1') stub_request_suggestions subject.suggest('100 Main') diff --git a/spec/services/usps_in_person_proofing/proofer_spec.rb b/spec/services/usps_in_person_proofing/proofer_spec.rb index d906eca641e..5f2ab701087 100644 --- a/spec/services/usps_in_person_proofing/proofer_spec.rb +++ b/spec/services/usps_in_person_proofing/proofer_spec.rb @@ -6,12 +6,18 @@ let(:subject) { UspsInPersonProofing::Proofer.new } describe '#retrieve_token!' do - it 'sets token and token_expires_at' do - stub_request_token - subject.retrieve_token! - - expect(subject.token).to be_present - expect(subject.token_expires_at).to be_present + let(:applicant) do + double( + 'applicant', + address: Faker::Address.street_address, + city: Faker::Address.city, + state: Faker::Address.state_abbr, + zip_code: Faker::Address.zip_code, + first_name: Faker::Name.first_name, + last_name: Faker::Name.last_name, + email: Faker::Internet.safe_email, + unique_id: '123456789', + ) end it 'calls the endpoint with the expected params' do @@ -50,6 +56,68 @@ }, ) end + + it 'caches the token' do + stub_request_token + token = subject.retrieve_token! + + expect(subject).not_to receive(:request_token) + expect(subject.token).to eq(token) + end + + it 'reuses the cached token on subsequent requests' do + stub_request_token + stub_request_enroll + stub_request_enroll + stub_request_enroll + + subject.request_enroll(applicant) + subject.request_enroll(applicant) + subject.request_enroll(applicant) + expect(WebMock).to have_requested(:post, %r{/oauth/authenticate}).once + end + + it 'implicitly refreshes the token when expired' do + stub_request_token(expires_in: 1.hour.to_i, access_token: 'token1') + stub_request_enroll + subject.request_enroll(applicant) + + travel 2.hours + + stub_request_token(access_token: 'token2') + stub_request_enroll + subject.request_enroll(applicant) + + expect(WebMock).to have_requested(:post, %r{/oauth/authenticate}).twice + expect(WebMock).to have_requested( + :post, + %r{/ivs-ippaas-api/IPPRest/resources/rest/optInIPPApplicant}, + ). + with(headers: { 'Authorization' => 'Bearer token1' }).once + expect(WebMock).to have_requested( + :post, + %r{/ivs-ippaas-api/IPPRest/resources/rest/optInIPPApplicant}, + ). + with(headers: { 'Authorization' => 'Bearer token2' }).once + end + + it 'reuses the cached token across instances' do + stub_request_token(access_token: 'token1') + stub_request_enroll + stub_request_enroll + + other_instance = UspsInPersonProofing::Proofer.new + + subject.request_enroll(applicant) + other_instance.request_enroll(applicant) + + expect(WebMock).to have_requested(:post, %r{/oauth/authenticate}).once + expect(WebMock).to have_requested( + :post, + %r{/ivs-ippaas-api/IPPRest/resources/rest/optInIPPApplicant}, + ). + with(headers: { 'Authorization' => 'Bearer token1' }).twice + end end def check_facility(facility) diff --git a/spec/support/usps_ipp_helper.rb b/spec/support/usps_ipp_helper.rb index 726ee901d69..c1e5d84a367 100644 --- a/spec/support/usps_ipp_helper.rb +++ b/spec/support/usps_ipp_helper.rb @@ -1,8 +1,13 @@ module UspsIppHelper - def stub_request_token + def stub_request_token(access_token: nil, expires_in: nil) + # Overwrite fixture values if values are specified + defaults = JSON.parse(UspsInPersonProofing::Mock::Fixtures.request_token_response) + body = defaults.merge( + { access_token: access_token, expires_in: expires_in }.compact, + ) stub_request(:post, %r{/oauth/authenticate}).to_return( status: 200, - body: UspsInPersonProofing::Mock::Fixtures.request_token_response, + body: body.to_json, headers: { 'content-type' => 'application/json' }, ) end