Skip to content

Commit

Permalink
feat: raise timeout error for circuit breaker (#17)
Browse files Browse the repository at this point in the history
* feat: raise HTTP::TimeoutError for CircuitBreaker

* feat: raise custom timeout error
  • Loading branch information
uncoder authored Oct 8, 2024
1 parent 1f233f9 commit f329c20
Show file tree
Hide file tree
Showing 13 changed files with 561 additions and 27 deletions.
1 change: 0 additions & 1 deletion lib/factiva.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
Dir[File.join(__dir__, "factiva", "*.rb")].each { |file| require file }

module Factiva

REQUEST_API_ACCOUNT = "request_api_account"
MONITORING_API_ACCOUNT = "monitoring_api_account"

Expand Down
4 changes: 4 additions & 0 deletions lib/factiva/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def make_request(params)
end

response_body
rescue HTTP::TimeoutError
# This error should be handled before HTTP::Error which is a superclass of HTTP::TimeoutError
# Raising Factiva::TimeoutError is required for CircuitBreaker to work properly
raise Factiva::TimeoutError
end

def expired?(timestamp)
Expand Down
4 changes: 3 additions & 1 deletion lib/factiva/errors.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module Factiva
class RequestError < StandardError; end
class Error < StandardError; end
class RequestError < Error; end
class TimeoutError < Error; end
end
24 changes: 14 additions & 10 deletions lib/factiva/monitoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ module Factiva
class Monitoring
include Dry::Monads[:result]

private_class_method :new
attr_reader :auth

COUNTRY_IDS = {
"ES" => "SPAIN",
"FR" => "FRA",
"IT" => "ITALY",
"PT" => "PORL",
}.freeze

private_class_method :new
attr_reader :auth

def self.create_case(**args)
instance.create_case(**args)
end
Expand Down Expand Up @@ -74,9 +74,9 @@ def initialize(stubbed_create_case,
stubbed_get_matches,
stubbed_log_decision
)
@stubbed_create_case = stubbed_create_case
@stubbed_create_association = stubbed_create_association
@stubbed_add_association_to_case = stubbed_add_association_to_case
@stubbed_create_case = stubbed_create_case
@stubbed_create_association = stubbed_create_association
@stubbed_add_association_to_case = stubbed_add_association_to_case
@stubbed_get_matches = stubbed_get_matches
@stubbed_log_decision = stubbed_log_decision
end
Expand Down Expand Up @@ -117,7 +117,7 @@ def create_case(case_body_payload = nil)

def create_association(first_name:, last_name:, birth_year:, external_id:, nin:, country_code:)
params = { json: association_body(
first_name,
first_name,
last_name,
birth_year,
external_id,
Expand All @@ -134,7 +134,7 @@ def create_association(first_name:, last_name:, birth_year:, external_id:, nin:,

def add_association_to_case(case_id:, association_id:)
params = { json: case_association_body(
association_id,
association_id,
)
}

Expand All @@ -153,7 +153,7 @@ def get_matches(case_id:)

def log_decision(case_id:, match_id:, comment:, state:, risk_rating:)
params = { json: log_decision_body(
comment, state, risk_rating,
comment, state, risk_rating,
)
}

Expand Down Expand Up @@ -189,7 +189,7 @@ def make_request(http_method, url, params = nil)

begin
response = HTTP
.headers(:accept => "application/json")
.headers(accept: "application/json")
.headers("Content-Type" => "application/json")
.timeout(config.timeout)
.auth("Bearer #{auth.token}")
Expand All @@ -202,6 +202,10 @@ def make_request(http_method, url, params = nil)
else
Failure({ code: response.code, error: response_body["error"] }.to_s)
end
rescue HTTP::TimeoutError
# This error should be handled before HTTP::Error which is a superclass of HTTP::TimeoutError
# Raising Factiva::TimeoutError is required for CircuitBreaker to work properly
raise Factiva::TimeoutError
rescue SocketError, HTTP::Error => error
Failure("Failed to connect to Factiva: #{error.message}")
rescue JSON::ParserError => error
Expand Down
4 changes: 4 additions & 0 deletions lib/factiva/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def make_request(method, url, params = nil)
else
Failure({ code: response.code, error: response_body["error"] }.to_s)
end
rescue HTTP::TimeoutError
# This error should be handled before HTTP::Error which is a superclass of HTTP::TimeoutError
# Raising Factiva::TimeoutError is required for CircuitBreaker to work properly
raise Factiva::TimeoutError
rescue SocketError, HTTP::Error => error
Failure("Failed to connect to Factiva: #{error.message}")
rescue JSON::ParserError => error
Expand Down
10 changes: 10 additions & 0 deletions spec/factiva/authentication_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ module Factiva
}.to raise_error(RequestError, { code: 400, error: "request_validation_error" }.to_s)
end
end

context "Factiva connection timed out" do
before do
WebMock.stub_request(:post, /oauth2\/v1\/token/).to_timeout
end

it "raises Factiva::TimeoutError for CircuitBreaker" do
expect { subject.token }.to raise_error(Factiva::TimeoutError)
end
end
end

context "Already authenticated" do
Expand Down
42 changes: 28 additions & 14 deletions spec/factiva/monitoring_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ module Factiva
}
}

context "when there isn't matches in the Case", vcr: "monitoring/no_matches" do # this VCR was recorded when there wasn't any match
# this VCR was recorded when there wasn't any match
context "when there isn't matches in the Case", vcr: "monitoring/no_matches" do
let(:case_id) { "91473caa-9eb8-4eb1-891d-bde3d7c80cdd" }

it "authenticates and doesn't get matches" do
Expand All @@ -96,7 +97,6 @@ module Factiva
end

context "when there are matches in the Case", vcr: "monitoring/matches" do

let(:case_id) { "296373b3-80ee-4fb7-9f2e-b43604051c0b" }

it "authenticates and returns 2 matches: 1 valid and 1 invalid" do
Expand All @@ -106,23 +106,23 @@ module Factiva
invalid_match = {
"peid" => "1003410",
"type" => "RELATIONSHIP",
"variation" => {"structural" => false, "linguistic" => false, "non_linguistic" => false},
"variation" => { "structural" => false, "linguistic" => false, "non_linguistic" => false },
"title" => "OFAC - Principal Significant Foreign Narcotics Trafficker List",
"match_id" => "239b4a188aff307a328984b0129dbb3a1fd5a9e9385e664fb3091fcce67ecd17",
"match_name" => "Maria Remedios Garcia Albert",
"match_type" => "PRECISE",
"percentage_match" =>1.0,
"percentage_match" => 1.0,
"subscription_name" => "Maria Remedios Garcia Albert",
"boosting_events" =>[],
"boosting_events" => [],
"icon_hints" => ["SAN-PERSON", "SI-PERSON"],
"primary_country" => "SPAIN",
"is_score_boosted" => false,
"current_state" => {"timestamp" => "2022-06-22T07:41:53", "state" => "OPEN"},
"birthdates" => [{"day" => 17, "year" => 1951, "month" => 2}],
"current_state" => { "timestamp" => "2022-06-22T07:41:53", "state" => "OPEN" },
"birthdates" => [{ "day" => 17, "year" => 1951, "month" => 2 }],
"gender" => "FEMALE",
"is_deceased" => false,
"match_date" => "2022-06-22T07:41:53",
"primary_name" => {"name_type" => "PRIMARY", "first_name" => "Maria Remedios", "last_name" => "Garcia Albert"},
"primary_name" => { "name_type" => "PRIMARY", "first_name" => "Maria Remedios", "last_name" => "Garcia Albert" },
"has_alerts" => false,
"is_match_valid" => false,
"match_invalid_date" => "2022-06-22T07:41:53.349",
Expand All @@ -132,7 +132,7 @@ module Factiva
valid_match = {
"peid" => "1003410",
"type" => "RELATIONSHIP",
"variation" => {"structural"=>false, "linguistic"=>false, "non_linguistic"=>false},
"variation" => { "structural" => false, "linguistic" => false, "non_linguistic" => false },
"title" => "OFAC - Principal Significant Foreign Narcotics Trafficker List",
"match_id" => "5e7a91388deea1e1368937aaa00c69635890f51e964909eb1f4daccf2ee69ca5",
"match_name" => "Maria Remedios Garcia Albert",
Expand All @@ -143,20 +143,21 @@ module Factiva
"icon_hints" => ["SAN-PERSON", "SI-PERSON"],
"primary_country" => "SPAIN",
"is_score_boosted" => false,
"current_state" => {"timestamp" => "2022-06-22T07:41:54", "state" => "OPEN"},
"birthdates" => [{"day"=>17, "year"=>1951, "month"=>2}],
"current_state" => { "timestamp" => "2022-06-22T07:41:54", "state" => "OPEN" },
"birthdates" => [{ "day" => 17, "year" => 1951, "month" => 2 }],
"gender" => "FEMALE",
"is_deceased" => false,
"match_date" => "2022-06-22T07:41:54",
"primary_name" => {"name_type" => "PRIMARY", "first_name" => "Maria Remedios", "last_name" => "Garcia Albert"},
"primary_name" => { "name_type" => "PRIMARY", "first_name" => "Maria Remedios", "last_name" => "Garcia Albert" },
"has_alerts" => true,
"is_match_valid" => true,
}.freeze

expect(result["data"][0]["attributes"]["external_id"]).to eq("id-XXXXXXX")
expect(result["data"][0]["attributes"]["matches"][0]["subscription_name"]).to eq("Maria Remedios Garcia Albert")
expect(result["data"][0]["attributes"]["matches"][0]["is_match_valid"]).to be_falsey
expect(result["data"][0]["attributes"]["matches"][0]["match_invalid_reason"]).to eq "Match excluded by filter: year_of_birth"
expect(result["data"][0]["attributes"]["matches"][0]["match_invalid_reason"])
.to eq "Match excluded by filter: year_of_birth"
expect(result["data"][0]["attributes"]["matches"][0]).to eq(invalid_match)

expect(result["data"][1]["attributes"]["external_id"]).to eq("id1234")
Expand All @@ -165,6 +166,18 @@ module Factiva
expect(result["data"][1]["attributes"]["matches"][0]).to eq(valid_match)
end
end

context "Factiva connection timed out", vcr: "monitoring/authentication_only" do
before do
WebMock.stub_request(:get, /risk-entity-screening-cases/).to_timeout
end

it "raises Factiva::TimeoutError for CircuitBreaker" do
expect {
subject.get_matches(case_id: "id")
}.to raise_error(Factiva::TimeoutError)
end
end
end

describe "#log_decision", vcr: "monitoring/log_decision" do
Expand All @@ -181,7 +194,8 @@ module Factiva
it "authenticates and Logs a decision to a Match" do
res = subject.log_decision(**sample_data)

expect(res["data"]["attributes"]["current_state"]).to eq({"timestamp"=>"2022-06-21T10:04:37.645", "comment"=>"False positive", "state"=>"CLEARED", "risk_rating"=>1})
expect(res["data"]["attributes"]["current_state"]).to eq({ "timestamp" => "2022-06-21T10:04:37.645",
"comment" => "False positive", "state" => "CLEARED", "risk_rating" => 1 })
end
end
end
Expand Down
24 changes: 24 additions & 0 deletions spec/factiva/request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ module Factiva
}.to raise_error(RequestError, "Failed to connect to Factiva: SocketError")
end
end

context "Factiva connection timed out", vcr: "search/authentication_only" do
before do
WebMock.stub_request(:post, /riskentities\/search/).to_timeout
end

it "raises Factiva::TimeoutError for CircuitBreaker" do
expect {
subject.search(**params)
}.to raise_error(Factiva::TimeoutError)
end
end
end

context "#Profile" do
Expand Down Expand Up @@ -166,6 +178,18 @@ module Factiva
}.to raise_error(RequestError, "Failed to connect to Factiva: SocketError")
end
end

context "Factiva connection timed out", vcr: "profile/authentication_only" do
before do
WebMock.stub_request(:get, /riskentities\/profiles/).to_timeout
end

it "raises Factiva::TimeoutError for CircuitBreaker" do
expect {
subject.profile(profile_id)
}.to raise_error(Factiva::TimeoutError)
end
end
end

context "#Stub!" do
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@
config.expect_with :rspec do |c|
c.syntax = :expect
end

config.after(:each) do
WebMock.reset!
end
end
3 changes: 2 additions & 1 deletion spec/support/vcr_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
c.filter_sensitive_data("<SECRET_USERNAME>") { URI.encode_www_form_component(Factiva.configuration.username) }
c.filter_sensitive_data("<SECRET_CLIENT_ID>") { Factiva.configuration(Factiva::MONITORING_API_ACCOUNT).client_id }
c.filter_sensitive_data("<SECRET_PASSWORD>") { Factiva.configuration(Factiva::MONITORING_API_ACCOUNT).password }
c.filter_sensitive_data("<SECRET_USERNAME>") { URI.encode_www_form_component(Factiva.configuration(Factiva::MONITORING_API_ACCOUNT).username) }
c.filter_sensitive_data("<SECRET_USERNAME>") {
URI.encode_www_form_component(Factiva.configuration(Factiva::MONITORING_API_ACCOUNT).username) }
end
Loading

0 comments on commit f329c20

Please sign in to comment.