From c1bf6c2fd9d625db4b7eb73cf4bf8dd7600c6531 Mon Sep 17 00:00:00 2001 From: Alex Paino Date: Tue, 31 Jul 2018 14:55:01 -0700 Subject: [PATCH] Add support for get_user_score and rescore_user --- HISTORY | 3 ++ lib/sift.rb | 5 ++ lib/sift/client.rb | 108 +++++++++++++++++++++++++++++++++++++++ lib/sift/version.rb | 2 +- spec/unit/client_spec.rb | 70 +++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 1 deletion(-) diff --git a/HISTORY b/HISTORY index 065f131..ff2a0d0 100644 --- a/HISTORY +++ b/HISTORY @@ -1,3 +1,6 @@ +=== 3.3.0 2018-07-31 +- Add support for rescore_user and get_user_score APIs + === 3.2.0 2018-07-05 - Add new query parameter force_workflow_run diff --git a/lib/sift.rb b/lib/sift.rb index 82f396b..b009cea 100644 --- a/lib/sift.rb +++ b/lib/sift.rb @@ -13,6 +13,11 @@ def self.score_api_path(user_id, version=API_VERSION) "/v#{version}/score/#{URI.encode(user_id)}/" end + # Returns the User Score API path for the specified user ID and API version + def self.user_score_api_path(user_id, version=API_VERSION) + "/v#{version}/users/#{URI.encode(user_id)}/score" + end + # Returns the users API path for the specified user ID and API version def self.users_label_api_path(user_id, version=API_VERSION) "/v#{version}/users/#{URI.encode(user_id)}/labels" diff --git a/lib/sift/client.rb b/lib/sift/client.rb index cc7bf5a..9b661d7 100644 --- a/lib/sift/client.rb +++ b/lib/sift/client.rb @@ -295,6 +295,114 @@ def score(user_id, opts = {}) end + # Fetches the latest score(s) computed for the specified user and abuse types. + # + # As opposed to client.score() and client.rescore_user(), this *does not* compute + # a new score for the user; it simply fetches the latest score(s) which have computed. + # These scores may be arbitrarily old. + # + # See https://siftscience.com/developers/docs/ruby/score-api/get-score for more details. + # + # ==== Parameters: + # + # user_id:: + # A user's id. This id should be the same as the user_id used in + # event calls. + # + # opts (optional):: + # A Hash of optional parameters for the request -- + # + # :abuse_types:: + # List of abuse types, specifying for which abuse types a + # score should be returned. By default, a score is returned + # for every abuse type to which you are subscribed. + # + # :api_key:: + # Overrides the API key for this call. + # + # :timeout:: + # Overrides the timeout (in seconds) for this call. + # + # ==== Returns: + # + # A Response object containing a status code, status message, and, + # if successful, the user's score(s). + # + def get_user_score(user_id, opts = {}) + abuse_types = opts[:abuse_types] + api_key = opts[:api_key] || @api_key + timeout = opts[:timeout] || @timeout + + raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty? + raise("Bad api_key parameter") if api_key.empty? + + query = {} + query["api_key"] = api_key + query["abuse_types"] = abuse_types.join(",") if abuse_types + + options = { + :headers => {"User-Agent" => user_agent}, + :query => query + } + options.merge!(:timeout => timeout) unless timeout.nil? + + response = self.class.get(Sift.user_score_api_path(user_id, @version), options) + Response.new(response.body, response.code, response.response) + end + + + # Rescores the specified user for the specified abuse types and returns the resulting score(s). + # + # See https://siftscience.com/developers/docs/ruby/score-api/rescore for more details. + # + # ==== Parameters: + # + # user_id:: + # A user's id. This id should be the same as the user_id used in + # event calls. + # + # opts (optional):: + # A Hash of optional parameters for the request -- + # + # :abuse_types:: + # List of abuse types, specifying for which abuse types a + # score should be returned. By default, a score is returned + # for every abuse type to which you are subscribed. + # + # :api_key:: + # Overrides the API key for this call. + # + # :timeout:: + # Overrides the timeout (in seconds) for this call. + # + # ==== Returns: + # + # A Response object containing a status code, status message, and, + # if successful, the user's score(s). + # + def rescore_user(user_id, opts = {}) + abuse_types = opts[:abuse_types] + api_key = opts[:api_key] || @api_key + timeout = opts[:timeout] || @timeout + + raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty? + raise("Bad api_key parameter") if api_key.empty? + + query = {} + query["api_key"] = api_key + query["abuse_types"] = abuse_types.join(",") if abuse_types + + options = { + :headers => {"User-Agent" => user_agent}, + :query => query + } + options.merge!(:timeout => timeout) unless timeout.nil? + + response = self.class.post(Sift.user_score_api_path(user_id, @version), options) + Response.new(response.body, response.code, response.response) + end + + # Labels a user. # # See https://siftscience.com/developers/docs/ruby/labels-api/label-user . diff --git a/lib/sift/version.rb b/lib/sift/version.rb index b4cd537..b904c3d 100644 --- a/lib/sift/version.rb +++ b/lib/sift/version.rb @@ -1,4 +1,4 @@ module Sift - VERSION = "3.1.0" + VERSION = "3.3.0" API_VERSION = "205" end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index a481ad4..af97a69 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -43,6 +43,38 @@ def score_response_json } end + def user_score_response_json + { + :entity_type => "user", + :entity_id => "247019", + :scores => { + :payment_abuse => { + :score => 0.78 + }, + :content_abuse => { + :score => 0.11 + } + }, + :latest_decisions => { + :payment_abuse => { + :id => "user_looks_bad_payment_abuse", + :category => "block", + :source => "AUTOMATED_RULE", + :time => 1352201880, + :description => "Bad Fraudster" + } + }, + :latest_labels => { + :payment_abuse => { + :is_bad => true, + :time => 1352201880 + } + }, + :status => 0, + :error_message => "OK" + } + end + def action_response_json { :user_id => "247019", @@ -272,6 +304,44 @@ def fully_qualified_api_endpoint end + it "Successfully executes client.get_user_score()" do + api_key = "foobar" + response_json = user_score_response_json + + stub_request(:get, "https://api.siftscience.com/v205/users/247019/score?api_key=foobar") + .to_return(:status => 200, :body => MultiJson.dump(response_json), + :headers => {"content-type"=>"application/json; charset=UTF-8", + "content-length"=> "74"}) + + response = Sift::Client.new(:api_key => api_key).get_user_score(user_score_response_json[:entity_id]) + expect(response.ok?).to eq(true) + expect(response.api_status).to eq(0) + expect(response.api_error_message).to eq("OK") + + expect(response.body["entity_id"]).to eq("247019") + expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78) + end + + + it "Successfully executes client.rescore_user()" do + api_key = "foobar" + response_json = user_score_response_json + + stub_request(:post, "https://api.siftscience.com/v205/users/247019/score?api_key=foobar") + .to_return(:status => 200, :body => MultiJson.dump(response_json), + :headers => {"content-type"=>"application/json; charset=UTF-8", + "content-length"=> "74"}) + + response = Sift::Client.new(:api_key => api_key).rescore_user(user_score_response_json[:entity_id]) + expect(response.ok?).to eq(true) + expect(response.api_status).to eq(0) + expect(response.api_error_message).to eq("OK") + + expect(response.body["entity_id"]).to eq("247019") + expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78) + end + + it "Successfully make a sync score request" do api_key = "foobar" response_json = {