Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jmax/lg 15676 passport api health check infrastructure #11891

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
14 changes: 14 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6232,6 +6232,20 @@ def otp_phone_validation_failed(error:, message:, context:, country:, **extra)
)
end

# Tracks the health of the DoS Passports API
# @param [Boolean] success Whether the passport api health check succeeded.
# @param [Hash] body The health check body, if present.
# @param [String] error Any additional error information we have
def passport_api_health_check(success:, body: nil, error: nil, **extra)
track_event(
:passport_api_health_check,
success:,
body:,
error:,
**extra,
)
end

# @param [Boolean] success Whether form validation was successful
# @param [Hash] error_details Details for errors that occurred in unsuccessful submission
# @param [Boolean] active_profile_present Whether active profile existed at time of change
Expand Down
35 changes: 35 additions & 0 deletions app/services/doc_auth/dos/requests/general_health_check_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module DocAuth
module Dos
module Requests
class GeneralHealthCheckRequest
def fetch(analytics)
Copy link
Contributor

@amirbey amirbey Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def fetch(analytics)
def fetch(analytics:, composite: true)

begin
faraday_response = connection.get
response = Responses::HealthCheckSuccess.new(faraday_response)
rescue Faraday::Error => faraday_error
response = Responses::HealthCheckFailure.new(faraday_error)
end
ensure
analytics.passport_api_health_check(
success: response.success?,
**response.extra,
)
end

private

attr_reader :analytics

def connection
@connection ||= Faraday::Connection.new(
url: IdentityConfig.store.passports_api_health_check_endpoint,
) do |builder|
builder.response :raise_error
end
end
Comment on lines +25 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def connection
@connection ||= Faraday::Connection.new(
url: IdentityConfig.store.passports_api_health_check_endpoint,
) do |builder|
builder.response :raise_error
end
end
def url
composite ? dos_passport_composite_healthcheck_endpoint : IdentityConfig.store.dos_passport_healthcheck_endpoint
end
def connection
@connection ||= Faraday::Connection.new(
url:,
) do |builder|
builder.response :raise_error
end
end

end
end
end
end
25 changes: 25 additions & 0 deletions app/services/doc_auth/dos/responses/health_check_failure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module DocAuth
module Dos
module Responses
class HealthCheckFailure < DocAuth::Response
def initialize(faraday_error)
errors =
if faraday_error.respond_to?(:status) # some subclasses don't
{ network: faraday_error.status }
else
{ network: true }
end

super(
success: false,
errors:,
exception: faraday_error,
extra: { error: faraday_error.inspect }
)
end
end
end
end
end
23 changes: 23 additions & 0 deletions app/services/doc_auth/dos/responses/health_check_success.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module DocAuth
module Dos
module Responses
class HealthCheckSuccess < DocAuth::Response
def initialize(faraday_response)
extra =
if faraday_response.body && !faraday_response.body.empty?
{ body: faraday_response.body }
else
{}
end

super(
success: faraday_response.success?,
extra:
)
end
end
end
end
end
1 change: 1 addition & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ outbound_connection_check_retry_count: 2
outbound_connection_check_timeout: 5
outbound_connection_check_url: 'https://checkip.amazonaws.com'
participate_in_dap: false
passports_api_health_check_endpoint: 'https://caapinpe.state.gov/api/passport-match-prc-api/v1/html'
Copy link
Contributor

@amirbey amirbey Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
passports_api_health_check_endpoint: 'https://caapinpe.state.gov/api/passport-match-prc-api/v1/html'

password_max_attempts: 3
password_pepper:
personal_key_retired: true
Expand Down
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ def self.store
config.add(:outbound_connection_check_timeout, type: :integer)
config.add(:outbound_connection_check_url)
config.add(:participate_in_dap, type: :boolean)
config.add(:passports_api_health_check_endpoint, type: :string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
config.add(:passports_api_health_check_endpoint, type: :string)

config.add(:password_max_attempts, type: :integer)
config.add(:password_pepper, type: :string)
config.add(:personal_key_retired, type: :boolean)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require 'rails_helper'

RSpec.describe DocAuth::Dos::Requests::GeneralHealthCheckRequest do
include PassportApiHelpers

subject(:health_check_request) { described_class.new }

let(:analytics) { FakeAnalytics.new }

let(:health_check_endpoint) do
IdentityConfig.store.passports_api_health_check_endpoint
end

describe '#fetch' do
let(:result) { health_check_request.fetch(analytics) }

context 'happy path' do
before do
stub_api_up
end

it 'hits the endpoint' do
result
expect(WebMock).to have_requested(:get, health_check_endpoint)
end

it 'logs the request' do
result
expect(analytics).to have_logged_event(
:passport_api_health_check,
success: true,
body: successful_api_health_check_body.to_json,
)
end

describe 'the #fetch result' do
it 'succeeds' do
expect(result).to be_success
end
end
end

context 'when Faraday raises an error' do
before do
stub_request(:get, health_check_endpoint).to_raise(Faraday::Error)
end

it 'hits the endpoint' do
result
expect(WebMock).to have_requested(:get, health_check_endpoint)
end

it 'logs the request' do
result
expect(analytics).to have_logged_event(
:passport_api_health_check,
success: false,
error: /Faraday::Error/,
)
end

describe 'the #fetch result' do
it 'does not succeed' do
expect(result).not_to be_success
end
end
end

context 'when Faraday returns an HTTP error' do
before do
stub_request(:get, health_check_endpoint).to_return(status: 500)
end

it 'hits the endpoint' do
result
expect(WebMock).to have_requested(:get, health_check_endpoint)
end

it 'logs the request' do
result
expect(analytics).to have_logged_event(
:passport_api_health_check,
success: false,
error: /Faraday::ServerError/,
)
end

describe 'the #fetch result' do
it 'does not succeed' do
expect(result).not_to be_success
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require 'rails_helper'

RSpec.describe DocAuth::Dos::Responses::HealthCheckSuccess do
subject(:health_check_result) { described_class.new(faraday_result) }

let(:faraday_result) do
Faraday.get(health_check_endpoint)
end

let(:health_check_endpoint) do
IdentityConfig.store.passports_api_health_check_endpoint
end

context 'when initialized from a successful request' do
let(:body) do
{
name: 'Passport Match Process API',
status: 'Up',
environment: 'dev-share',
comments: 'Ok',
}.to_json
end

before do
stub_request(:get, health_check_endpoint).to_return(body:)
end

it 'is successful' do
expect(health_check_result).to be_success
end

it 'has the body in the extra event parameters' do
expect(health_check_result.extra[:body]).to eq(body)
end
end

# should not happen, because the connection options in
# GeneralHealthCheckRequest prevent it, but let's stay sane if it does.
# 403 is an arbitrary choice.
context 'when initialized from an HTTP error response' do
context 'with no body' do
before do
stub_request(:get, health_check_endpoint).to_return(status: 403)
end

it 'is not successful' do
expect(health_check_result).not_to be_success
end

it 'does not include the body: key in the extras' do
expect(health_check_result.extra).not_to have_key(:body)
end
end

context 'with a body' do
before do
stub_request(:get, health_check_endpoint).to_return(status: 403, body: 'a 403 body')
end

it 'is not successful' do
expect(health_check_result).not_to be_success
end

it 'includes the body in the extras' do
expect(health_check_result.extra[:body]).to eq('a 403 body')
end
end
end
end
36 changes: 36 additions & 0 deletions spec/services/doc_auth/dos/responses/health_check_failure_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'rails_helper'

RSpec.describe DocAuth::Dos::Responses::HealthCheckFailure do
subject(:health_check_result) { described_class.new(faraday_error) }

let(:health_check_endpoint) do
IdentityConfig.store.passports_api_health_check_endpoint
end

def make_faraday_error(status:)
stub_request(:get, health_check_endpoint).to_return(status:)
begin
Faraday.get(health_check_endpoint)
rescue FaradayError => faraday_error
faraday_error
end
end

[403, 404, 500].each do |http_status|
context "when initialized from an HTTP #{http_status} error" do
let(:faraday_error) { make_faraday_error(status: http_status) }

it 'is not successful' do
expect(health_check_result).not_to be_success
end

it 'has the correct errors hash' do
expect(health_check_result.errors).to eq({ network: http_status })
end

it 'has the faraday exception' do
expect(health_check_result.exception).to eq(faraday_error)
end
end
end
end
15 changes: 15 additions & 0 deletions spec/support/passport_api_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module PassportApiHelpers
def successful_api_health_check_body = {
name: 'Passport Match Process API',
status: 'Up',
environment: 'dev-share',
comments: 'Ok',
}

def stub_api_up
stub_request(:get, health_check_endpoint)
.to_return_json(
body: successful_api_health_check_body,
)
end
end