diff --git a/.travis.yml b/.travis.yml
index e8fe38f..c939e47 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,11 @@ script: "bundle exec rake spec"
rvm:
- 1.9.3
- 2.0.0
+ - 2.1.5
+ - 2.2.1
+before_install:
+ - gem install bundler
+ - bundle --version
env:
# None for now
gemfile:
diff --git a/HISTORY b/HISTORY
index 55668e1..bdeb3e1 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,10 @@
+=== 2.0.0.0 2016-07-19
+* adds support for v204 of Sift Science's APIs
+* adds Workflow Status API, User Decisions API, Order Decisions API
+* v204 APIs are now called by default -- this is an incompatible change
+ (use :version => 203 to call the previous API version)
+* uses Hash arg for optional params in Client methods -- incompatible change
+
=== 1.1.7.2 2015-04-13
* Fixed backwards compatibility issue
diff --git a/README.rdoc b/README.rdoc
index 0bc55b8..d21e355 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,5 +1,6 @@
= Sift Science Ruby bindings {}[https://travis-ci.org/SiftScience/sift-ruby]
+
== Requirements
* Ruby 1.8.7 or above. (Ruby 1.8.6 might work if you load ActiveSupport.)
@@ -12,6 +13,7 @@ For development only:
* webmock, 1.16 or greater
* rake, any version
+
== Installation
If you want to build the gem from source:
@@ -22,16 +24,19 @@ Alternatively, you can install the gem from Rubyforge:
$ gem install sift
+
== Usage
+
require "sift"
Sift.api_key = ''
+ Sift.account_id = ''
client = Sift::Client.new()
# send a transaction event -- note this is blocking
event = "$transaction"
- user_id = "23056" # User ID's may only contain a-z, A-Z, 0-9, =, ., -, _, +, @, :, &, ^, %, !, $
+ user_id = "23056" # User ID's may only contain a-z, A-Z, 0-9, =, ., -, _, +, @, :, &, ^, %, !, $
properties = {
"$user_id" => user_id,
@@ -50,20 +55,39 @@ Alternatively, you can install the gem from Rubyforge:
}
response = client.track(event, properties)
-
- response.ok? # returns true or false
-
- response.http_status_code # HTTP response code, 200 is ok.
-
- response.api_status # status field in the return body, Link to Error Codes
- response.api_error_message # Error message associated with status Error Code
+ response.ok? # returns true or false
+ response.body # API response body
+ response.http_status_code # HTTP response code, 200 is ok.
+ response.api_status # status field in the return body, Link to Error Codes
+ response.api_error_message # Error message associated with status Error Code
- # Request a score forthe user with user_id 23056
+
+ # Request a score for the user with user_id 23056
response = client.score(user_id)
-
+
+
# Label the user with user_id 23056 as Bad with all optional fields
- response = client.label(user_id,{ "$is_bad" => true, "$reasons" => ["$chargeback", ], "$description" => "Chargeback issued", "$source" => "Manual Review", "$analyst" => "analyst.name@your_domain.com"})
+ response = client.label(user_id, {
+ "$is_bad" => true,
+ "$abuse_type" => "payment_abuse",
+ "$description" => "Chargeback issued",
+ "$source" => "Manual Review",
+ "$analyst" => "analyst.name@your_domain.com"
+ })
+
+
+ # Get the status of a workflow run
+ response = client.get_workflow_status('my_run_id')
+
+
+ # Get the latest decisions for a user
+ response = client.get_user_decisions('example_user_id')
+
+
+ # Get the latest decisions for an order
+ response = client.get_order_decisions('example_order_id')
+
== Building
@@ -78,6 +102,7 @@ Building and publishing the gem is captured by the following steps:
$ rake install
$ rake release
+
== Testing
To run the various tests use the rake command as follows:
diff --git a/lib/sift.rb b/lib/sift.rb
index b5584de..2c35174 100644
--- a/lib/sift.rb
+++ b/lib/sift.rb
@@ -3,21 +3,46 @@
module Sift
- # Returns the path for the current API version
- def self.current_rest_api_path
- "/v#{API_VERSION}/events"
+ # Returns the path for the specified API version
+ def self.rest_api_path(version=API_VERSION)
+ "/v#{version}/events"
end
- def self.current_users_label_api_path(user_id)
- # This API version is a minor version ahead of the /events API
- "/v#{API_VERSION}/users/#{URI.encode(user_id)}/labels"
+ # Returns the Score API path for the specified user ID and API version
+ def self.score_api_path(user_id, version=API_VERSION)
+ "/v#{version}/score/#{URI.encode(user_id)}/"
end
-
- # Adding module scoped public API key
+
+ # 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"
+ end
+
+ # Returns the path for the Workflow Status API
+ def self.workflow_status_path(account_id, run_id)
+ "/v3/accounts/#{account_id}/workflows/runs/#{run_id}"
+ end
+
+ # Returns the path for User Decisions API
+ def self.user_decisions_api_path(account_id, user_id)
+ "/v3/accounts/#{account_id}/users/#{user_id}/decisions"
+ end
+
+ # Returns the path for Orders Decisions API
+ def self.order_decisions_api_path(account_id, order_id)
+ "/v3/accounts/#{account_id}/orders/#{order_id}/decisions"
+ end
+
+ # Module-scoped public API key
class << self
attr_accessor :api_key
end
+ # Module-scoped account ID
+ class << self
+ attr_accessor :account_id
+ end
+
# Sets the Output logger to use within the client. This can be left uninitializaed
# but is useful for debugging.
def self.logger=(logger)
diff --git a/lib/sift/client.rb b/lib/sift/client.rb
index 663f22e..17422f9 100644
--- a/lib/sift/client.rb
+++ b/lib/sift/client.rb
@@ -15,8 +15,9 @@ class Response
# Constructor
#
- # == Parameters:
- # http_response
+ # ==== Parameters:
+ #
+ # http_response::
# The HTTP body text returned from the API call. The body is expected to be
# a JSON object that can be decoded into status, message and request
# sections.
@@ -37,10 +38,11 @@ def initialize(http_response, http_response_code, http_raw_response)
# Helper method returns true if and only if the response from the API call was
# successful
#
- # == Returns:
- # true on success; false otherwise
+ # ==== Returns:
+ #
+ # true on success; false otherwise
+ #
def ok?
-
if @http_raw_response.kind_of? Net::HTTPNoContent
#if there is no content expected, use HTTP code
204 == @http_status_code
@@ -51,14 +53,14 @@ def ok?
end
- # DEPRECIATED
- # Getter method for depreciated 'json' member variable.
+ # DEPRECATED
+ # Getter method for deprecated 'json' member variable.
def json
@body
end
- # DEPRECIATED
- # Getter method for depreciated 'original_request' member variable.
+ # DEPRECATED
+ # Getter method for deprecated 'original_request' member variable.
def original_request
@request
end
@@ -67,29 +69,50 @@ def original_request
# This class wraps accesses through the API
#
class Client
- API_ENDPOINT = "https://api.siftscience.com"
- API_TIMEOUT = 2
+ API_ENDPOINT = 'https://api.siftscience.com'
+ API3_ENDPOINT = 'https://api3.siftscience.com'
include HTTParty
base_uri API_ENDPOINT
+
# Constructor
#
- # == Parameters:
- # api_key
- # The Sift Science API key associated with your customer account. This parameter
- # cannot be nil or blank.
- # path
- # The path to the event API, e.g., "/v201/events"
+ # ==== Parameters:
#
- def initialize(api_key = Sift.api_key, path = Sift.current_rest_api_path, timeout = API_TIMEOUT)
- raise("api_key must be a non-empty string") if !api_key.is_a?(String) || api_key.empty?
- raise("path must be a non-empty string") if !path.is_a?(String) || path.empty?
- @api_key = api_key
- @path = path
- @timeout = timeout
-
-
+ # opts (optional)::
+ # A Hash of optional parameters for this Client --
+ #
+ # :api_key::
+ # The Sift Science API key associated with your account.
+ # Sift.api_key is used if this parameter is not set.
+ #
+ # :account_id::
+ # The ID of your Sift Science account. Sift.account_id is
+ # used if this parameter is not set.
+ #
+ # :timeout::
+ # The number of seconds to wait before failing a request. By
+ # default this is configured to 2 seconds.
+ #
+ # :version::
+ # The version of the Events API, Score API, and Labels API to call.
+ # By default, version 204.
+ #
+ # :path::
+ # The URL path to use for Events API path. By default, the
+ # official path of the specified-version of the Events API.
+ #
+ #
+ def initialize(opts = {})
+ @api_key = opts[:api_key] || Sift.api_key
+ @account_id = opts[:account_id] || Sift.account_id
+ @version = opts[:version] || API_VERSION
+ @timeout = opts[:timeout] || 2 # 2-second timeout by default
+ @path = opts[:path] || Sift.rest_api_path(@version)
+
+ raise("api_key must be a non-empty string") if !@api_key.is_a?(String) || @api_key.empty?
+ raise("path must be a non-empty string") if !@path.is_a?(String) || @path.empty?
end
def api_key
@@ -97,64 +120,95 @@ def api_key
end
def user_agent
- "SiftScience/v#{API_VERSION} sift-ruby/#{VERSION}"
+ "SiftScience/v#{@version} sift-ruby/#{VERSION}"
end
- # Tracks an event and associated properties through the Sift Science API. This call
- # is blocking.
+
+ # Sends an event to the Sift Science Events API.
#
- # == Parameters:
- # event
- # The name of the event to send. This can be either a reserved event name, like
- # $transaction or $label or a custom event name (that does not start with a $).
- # This parameter must be specified.
+ # See https://siftscience.com/developers/docs/ruby/events-api .
#
- # properties
- # A hash of name-value pairs that specify the event-specific attributes to track.
- # This parameter must be specified.
+ # ==== Parameters:
+ #
+ # event::
+ # The name of the event to send. This can be either a reserved
+ # event name, like $transaction or $label or a custom event name
+ # (that does not start with a $). This parameter must be
+ # specified.
+ #
+ # properties::
+ # A hash of name-value pairs that specify the event-specific
+ # attributes to track. This parameter must be specified.
+ #
+ # opts (optional)::
+ # A Hash of optional parameters for the request --
+ #
+ # :return_score::
+ # If true, requests that the response include a score for this
+ # user, computed using the submitted event. See
+ # https://siftscience.com/developers/docs/ruby/score-api/synchronous-scores
+ #
+ # :abuse_types::
+ # List of abuse types, specifying for which abuse types a
+ # score should be returned (if scoring was requested). By
+ # default, a score is returned for every abuse type to which
+ # you are subscribed.
#
- # timeout (optional)
- # The number of seconds to wait before failing the request. By default this is
- # configured to 2 seconds (see above). This parameter is optional.
+ # :return_action::
+ # If true, requests that the response include any actions
+ # triggered as a result of the tracked event.
#
- # path (optional)
- # Overrides the default API path with a different URL.
+ # :return_workflow_status::
+ # If true, requests that the response include the status of
+ # any workflow run as a result of the tracked event. See
+ # https://siftscience.com/developers/docs/ruby/workflows-api/workflow-decisions
#
- # return_score (optional)
- # Whether the API response should include a score for this user. The score will
- # be calculated using the submitted event. This feature must be
- # enabled for your account in order to use it. Please contact
- # support@siftscience.com if you are interested in using this feature.
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
#
- # return_action (optional)
- # Whether the API response should include an action triggered for this transaction.
+ # :api_key::
+ # Overrides the API key for this call.
#
- # == Returns:
- # In the case of an HTTP error (timeout, broken connection, etc.), this
- # method returns nil; otherwise, a Response object is returned and captures
- # the status message and status code. In general, you can ignore the returned
- # result, though.
+ # :version::
+ # Overrides the version of the Events API to call.
#
- def track(event, properties = {}, timeout = nil, path = nil, return_score = false, api_key = @api_key, return_action = false)
- warn "[WARNING] api_key cannot be empty, fallback to default api_key." if api_key.to_s.empty?
- api_key ||= @api_key
+ # :path::
+ # Overrides the URI path for this API call.
+ #
+ # ==== Returns:
+ #
+ # In the case of a connection error (timeout, broken connection,
+ # etc.), this method returns nil; otherwise, a Response object is
+ # returned that captures the status message and status code.
+ #
+ def track(event, properties = {}, opts = {})
+ api_key = opts[:api_key] || @api_key
+ version = opts[:version] || @version
+ path = opts[:path] || (version && Sift.rest_api_path(version)) || @path
+ timeout = opts[:timeout] || @timeout
+ return_score = opts[:return_score]
+ return_action = opts[:return_action]
+ return_workflow_status = opts[:return_workflow_status]
+ abuse_types = opts[:abuse_types]
+
raise("event must be a non-empty string") if (!event.is_a? String) || event.empty?
raise("properties cannot be empty") if properties.empty?
- raise("Bad api_key parameter") if api_key.empty?
- path ||= @path
- timeout ||= @timeout
+ raise("api_key cannot be empty") if api_key.empty?
- uri = URI.parse(API_ENDPOINT)
- uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s) << ["return_score", "true"]) if return_score
- uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s) << ["return_action", "true"]) if return_action
- path = path + "?" + uri.query if !uri.query.to_s.empty?
+ query = {}
+ query["return_score"] = "true" if return_score
+ query["return_action"] = "true" if return_action
+ query["return_workflow_status"] = "true" if return_workflow_status
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
options = {
:body => MultiJson.dump(delete_nils(properties).merge({"$type" => event,
"$api_key" => api_key})),
- :headers => {"User-Agent" => user_agent}
+ :headers => {"User-Agent" => user_agent},
+ :query => query
}
options.merge!(:timeout => timeout) unless timeout.nil?
+
begin
response = self.class.post(path, options)
Response.new(response.body, response.code, response.response)
@@ -165,91 +219,280 @@ def track(event, properties = {}, timeout = nil, path = nil, return_score = fals
end
end
- # Retrieves a user's fraud score from the Sift Science API. This call
- # is blocking.
+
+ # Retrieves a user's fraud score from the Sift Science API.
#
- # == Parameters:
- # user_id
+ # See https://siftscience.com/developers/docs/ruby/score-api/score-api .
+ #
+ # ==== Parameters:
+ #
+ # user_id::
# A user's id. This id should be the same as the user_id used in
# event calls.
#
- # == Returns:
- # A Response object is returned and captures the status message and
- # status code. In general, you can ignore the returned result, though.
+ # 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.
+ #
+ # :version::
+ # Overrides the version of the Events API to call.
+ #
+ # ==== Returns:
#
- def score(user_id, timeout = nil, api_key = @api_key)
+ # A Response object containing a status code, status message, and, if
+ # successful, the user's score(s). Returns nil on a connection error
+ # (timeout, broken connection, etc.).
+ #
+ def score(user_id, opts = {})
+ abuse_types = opts[:abuse_types]
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+ version = opts[:version] || @version
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?
- timetout ||= @timeout
- options = { :headers => {"User-Agent" => user_agent} }
+ 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("/v#{API_VERSION}/score/#{user_id}/?api_key=#{api_key}", options)
+ response = self.class.get(Sift.score_api_path(user_id, version), options)
Response.new(response.body, response.code, response.response)
-
end
- # Labels a user as either good or bad. This call is blocking.
+
+ # Labels a user.
+ #
+ # See https://siftscience.com/developers/docs/ruby/labels-api/label-user .
#
- # == Parameters:
- # user_id
+ # ==== Parameters:
+ #
+ # user_id::
# A user's id. This id should be the same as the user_id used in
# event calls.
#
- # properties
+ # properties::
# A hash of name-value pairs that specify the label attributes.
# This parameter must be specified.
#
- # timeout (optional)
- # The number of seconds to wait before failing the request. By default this is
- # configured to 2 seconds (see above). This parameter is optional.
+ # opts (optional)::
+ # A Hash of optional parameters for the request --
+ #
+ # :api_key::
+ # Overrides the API key for this call.
+ #
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
+ #
+ # :version::
+ # Overrides the version of the Events API to call.
#
- # == Returns:
- # A Response object is returned and captures the status message and
- # status code. In general, you can ignore the returned result, though.
+ # ==== Returns:
#
- def label(user_id, properties = {}, timeout = nil, api_key = @api_key)
+ # In the case of a connection error (timeout, broken connection,
+ # etc.), this method returns nil; otherwise, a Response object is
+ # returned that captures the status message and status code.
+ #
+ def label(user_id, properties = {}, opts = {})
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+ version = opts[:version] || @version
+ path = Sift.users_label_api_path(user_id, version)
raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
- path = Sift.current_users_label_api_path(user_id)
-
- # No return_action logic supported when using labels.
- track("$label", delete_nils(properties), timeout, path, false, api_key, false)
+ track("$label", delete_nils(properties),
+ :path => path, :api_key => api_key, :timeout => timeout)
end
- # Unlabels a user. This call is blocking.
+
+ # Unlabels a user.
#
- # == Parameters:
- # user_id
+ # See https://siftscience.com/developers/docs/ruby/labels-api/unlabel-user .
+ #
+ # ==== Parameters:
+ #
+ # user_id::
# A user's id. This id should be the same as the user_id used in
# event calls.
#
- # timeout (optional)
- # The number of seconds to wait before failing the request. By default this is
- # configured to 2 seconds (see above). This parameter is optional.
+ # opts (optional)::
+ # A Hash of optional parameters for this request --
+ #
+ # :abuse_type::
+ # The abuse type for which the user should be unlabeled. If
+ # omitted, the user is unlabeled for all abuse types.
+ #
+ # :api_key::
+ # Overrides the API key for this call.
+ #
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
+ #
+ # :version::
+ # Overrides the version of the Events API to call.
+ #
+ # ==== Returns:
#
- # == Returns:
- # A Response object is returned with only an http code of 204.
+ # A Response object is returned with only an http code of 204.
+ # Returns nil on a connection error (timeout, broken connection,
+ # etc.).
#
- def unlabel(user_id, timeout = nil)
+ def unlabel(user_id, opts = {})
+ abuse_type = opts[:abuse_type]
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+ version = opts[:version] || @version
raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
- timetout ||= @timeout
- options = { :headers => {"User-Agent" => user_agent} }
+ query = {}
+ query[:api_key] = api_key
+ query[:abuse_type] = abuse_type if abuse_type
+
+ options = {
+ :headers => {},
+ :query => query
+ }
options.merge!(:timeout => timeout) unless timeout.nil?
- path = Sift.current_users_label_api_path(user_id)
- response = self.class.delete(path + "?api_key=#{@api_key}", options)
+
+ response = self.class.delete(Sift.users_label_api_path(user_id, version), options)
Response.new(response.body, response.code, response.response)
end
+
+ # Gets the status of a workflow run.
+ #
+ # See https://siftscience.com/developers/docs/ruby/workflows-api/workflow-status .
+ #
+ # ==== Parameters
+ #
+ # run_id::
+ # The ID of a workflow run.
+ #
+ # opts (optional)::
+ # A Hash of optional parameters for this request --
+ #
+ # :account_id::
+ # Overrides the API key for this call.
+ #
+ # :api_key::
+ # Overrides the API key for this call.
+ #
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
+ #
+ def get_workflow_status(run_id, opts = {})
+ account_id = opts[:account_id] || @account_id
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+
+ options = {
+ :headers => { "User-Agent" => user_agent },
+ :basic_auth => { :username => api_key, :password => "" }
+ }
+ options.merge!(:timeout => timeout) unless timeout.nil?
+
+ uri = API3_ENDPOINT + Sift.workflow_status_path(account_id, run_id)
+ response = self.class.get(uri, options)
+ Response.new(response.body, response.code, response.response)
+ end
+
+
+ # Gets the decision status of a user.
+ #
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
+ #
+ # ==== Parameters
+ #
+ # user_id::
+ # The ID of user.
+ #
+ # opts (optional)::
+ # A Hash of optional parameters for this request --
+ #
+ # :account_id::
+ # Overrides the API key for this call.
+ #
+ # :api_key::
+ # Overrides the API key for this call.
+ #
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
+ #
+ def get_user_decisions(user_id, opts = {})
+ account_id = opts[:account_id] || @account_id
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+
+ options = {
+ :headers => { "User-Agent" => user_agent },
+ :basic_auth => { :username => api_key, :password => "" }
+ }
+ options.merge!(:timeout => timeout) unless timeout.nil?
+
+ uri = API3_ENDPOINT + Sift.user_decisions_api_path(account_id, user_id)
+ response = self.class.get(uri, options)
+ Response.new(response.body, response.code, response.response)
+ end
+
+
+ # Gets the decision status of an order.
+ #
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
+ #
+ # ==== Parameters
+ #
+ # order_id::
+ # The ID of an order.
+ #
+ # opts (optional)::
+ # A Hash of optional parameters for this request --
+ #
+ # :account_id::
+ # Overrides the API key for this call.
+ #
+ # :api_key::
+ # Overrides the API key for this call.
+ #
+ # :timeout::
+ # Overrides the timeout (in seconds) for this call.
+ #
+ def get_order_decisions(order_id, opts = {})
+ account_id = opts[:account_id] || @account_id
+ api_key = opts[:api_key] || @api_key
+ timeout = opts[:timeout] || @timeout
+
+ options = {
+ :headers => { "User-Agent" => user_agent },
+ :basic_auth => { :username => api_key, :password => "" }
+ }
+ options.merge!(:timeout => timeout) unless timeout.nil?
+
+ uri = API3_ENDPOINT + Sift.order_decisions_api_path(account_id, order_id)
+ response = self.class.get(uri, options)
+ Response.new(response.body, response.code, response.response)
+ end
+
+
private
- # def add_query_parameter(query_parameter)
- # uri = URI.parse(API_ENDPOINT)
- # end
+
def delete_nils(properties)
properties.delete_if do |k, v|
case v
diff --git a/lib/sift/version.rb b/lib/sift/version.rb
index b50263c..b3658ba 100644
--- a/lib/sift/version.rb
+++ b/lib/sift/version.rb
@@ -1,4 +1,4 @@
module Sift
- VERSION = "1.1.7.3"
- API_VERSION = "203"
+ VERSION = "2.0.0.0"
+ API_VERSION = "204"
end
diff --git a/sift.gemspec b/sift.gemspec
index ba17772..5e6a0da 100644
--- a/sift.gemspec
+++ b/sift.gemspec
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
s.name = "sift"
s.version = Sift::VERSION
s.platform = Gem::Platform::RUBY
- s.authors = ["Fred Sadaghiani", "Yoav Schatzberg"]
+ s.authors = ["Fred Sadaghiani", "Yoav Schatzberg", "Jacob Burnim"]
s.email = ["support@siftscience.com"]
s.homepage = "http://siftscience.com"
s.summary = %q{Sift Science Ruby API Gem}
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
# Gems that must be intalled for sift to compile and build
s.add_development_dependency "rspec", ">=2.14.1"
- s.add_development_dependency "webmock", ">= 1.16.0"
+ s.add_development_dependency "webmock", ">= 1.16.0", "< 2"
# Gems that must be intalled for sift to work
s.add_dependency "httparty", ">= 0.11.0"
diff --git a/spec/unit/client_203_spec.rb b/spec/unit/client_203_spec.rb
new file mode 100644
index 0000000..40dd50c
--- /dev/null
+++ b/spec/unit/client_203_spec.rb
@@ -0,0 +1,192 @@
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Sift::Client do
+
+ before :each do
+ Sift.api_key = nil
+ end
+
+ def valid_transaction_properties
+ {
+ :$buyer_user_id => "123456",
+ :$seller_user_id => "654321",
+ :$amount => 1253200,
+ :$currency_code => "USD",
+ :$time => Time.now.to_i,
+ :$transaction_id => "my_transaction_id",
+ :$billing_name => "Mike Snow",
+ :$billing_bin => "411111",
+ :$billing_last4 => "1111",
+ :$billing_address1 => "123 Main St.",
+ :$billing_city => "San Francisco",
+ :$billing_region => "CA",
+ :$billing_country => "US",
+ :$billing_zip => "94131",
+ :$user_email => "mike@example.com"
+ }
+ end
+
+ def score_response_json
+ {
+ :user_id => "247019",
+ :score => 0.93,
+ :reasons => [{
+ :name => "UsersPerDevice",
+ :value => 4,
+ :details => {
+ :users => "a, b, c, d"
+ }
+ }],
+ :status => 0,
+ :error_message => "OK"
+ }
+ end
+
+ def action_response_json
+ {
+ :user_id => "247019",
+ :score => 0.93,
+ :actions => [{
+ :action_id => "1234567890abcdefghijklmn",
+ :time => 1437421587052,
+ :triggers => [{
+ :triggerType => "FORMULA",
+ :source => "synchronous_action",
+ :trigger_id => "12345678900987654321abcd"
+ }],
+ :entity => {
+ :type => "USER",
+ :id => "23056"
+ }
+ },
+ {
+ :action_id => "12345678901234567890abcd",
+ :time => 1437421587410,
+ :triggers => [{
+ :triggerType => "FORMULA",
+ :source => "synchronous_action",
+ :trigger_id => "abcd12345678901234567890"
+ }],
+ :entity => {
+ :type => "ORDER",
+ :id => "order_at_ 1437421587009"
+ }
+ }],
+ :status => 0,
+ :error_message => "OK"
+ }
+ end
+
+ def fully_qualified_api_endpoint
+ Sift::Client::API_ENDPOINT + Sift.rest_api_path
+ end
+
+ it "Successfully submits a v203 event with overridden key" do
+ response_json = { :status => 0, :error_message => "OK"}
+ stub_request(:post, "https://api.siftscience.com/v203/events").
+ with { | request|
+ parsed_body = JSON.parse(request.body)
+ expect(parsed_body).to include("$buyer_user_id" => "123456")
+ expect(parsed_body).to include("$api_key" => "overridden")
+ }.to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
+
+ api_key = "foobar"
+ event = "$transaction"
+ properties = valid_transaction_properties
+
+ response = Sift::Client.new(:api_key => api_key, :version => "203")
+ .track(event, properties, :api_key => "overridden")
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+ end
+
+
+ it "Successfully fetches a v203 score" do
+
+ api_key = "foobar"
+ response_json = score_response_json
+
+ stub_request(:get, "https://api.siftscience.com/v203/score/247019/?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)
+ .score(score_response_json[:user_id], :version => 203)
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+
+ expect(response.body["score"]).to eq(0.93)
+ end
+
+
+ it "Successfully fetches a v203 score with an overridden key" do
+
+ api_key = "foobar"
+ response_json = score_response_json
+
+ stub_request(:get, "https://api.siftscience.com/v203/score/247019/?api_key=overridden")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
+
+ response = Sift::Client.new(:api_key => api_key, :version => 203)
+ .score(score_response_json[:user_id], :api_key => "overridden")
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+
+ expect(response.body["score"]).to eq(0.93)
+ end
+
+
+ it "Successfuly make a v203 sync score request" do
+
+ api_key = "foobar"
+ response_json = {
+ :status => 0,
+ :error_message => "OK",
+ :score_response => score_response_json
+ }
+
+ stub_request(:post, "https://api.siftscience.com/v203/events?return_score=true")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
+
+ event = "$transaction"
+ properties = valid_transaction_properties
+ response = Sift::Client.new(:api_key => api_key)
+ .track(event, properties, :return_score => true, :version => "203")
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+ expect(response.body["score_response"]["score"]).to eq(0.93)
+ end
+
+
+ it "Successfuly make a v203 sync action request" do
+
+ api_key = "foobar"
+ response_json = {
+ :status => 0,
+ :error_message => "OK",
+ :score_response => action_response_json
+ }
+
+ stub_request(:post, "https://api.siftscience.com/v203/events?return_action=true")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
+
+ event = "$transaction"
+ properties = valid_transaction_properties
+ response = Sift::Client.new(:api_key => api_key, :version => "203")
+ .track(event, properties, :return_action => true)
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+ expect(response.body["score_response"]["actions"].first["entity"]["type"]).to eq("USER")
+ end
+
+end
diff --git a/spec/unit/client_label_spec.rb b/spec/unit/client_label_spec.rb
index 0af8b51..03216e7 100644
--- a/spec/unit/client_label_spec.rb
+++ b/spec/unit/client_label_spec.rb
@@ -3,6 +3,14 @@
describe Sift::Client do
def valid_label_properties
+ {
+ :$abuse_type => 'content_abuse',
+ :$is_bad => true,
+ :$description => "Listed a fake item"
+ }
+ end
+
+ def valid_label_properties_203
{
:$reasons => [ "$fake" ],
:$is_bad => true,
@@ -10,39 +18,75 @@ def valid_label_properties
}
end
- def fully_qualified_users_labels_endpoint(user_id)
- Sift::Client::API_ENDPOINT + Sift.current_users_label_api_path(user_id)
+
+ it "Successfuly handles a $label and returns OK" do
+
+ response_json = { :status => 0, :error_message => "OK" }
+ user_id = "frodo_baggins"
+
+ stub_request(:post, "https://api.siftscience.com/v204/users/frodo_baggins/labels")
+ .with(:body => ('{"$abuse_type":"content_abuse","$is_bad":true,"$description":"Listed a fake item","$type":"$label","$api_key":"foobar"}'))
+ .to_return(:body => MultiJson.dump(response_json), :status => 200,
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
+
+ api_key = "foobar"
+ properties = valid_label_properties
+
+ response = Sift::Client.new(:api_key => api_key).label(user_id, properties)
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
end
+
+ it "Successfully handles an $unlabel and returns OK" do
+ response_json = { :status => 0, :error_message => "OK" }
+ user_id = "frodo_baggins"
+
+ stub_request(:delete,
+ "https://api.siftscience.com/v204/users/frodo_baggins/labels?api_key=foobar&abuse_type=payment_abuse")
+ .to_return(:status => 204)
+
+ api_key = "foobar"
+
+ response = Sift::Client.new(:api_key => api_key).unlabel(user_id, :abuse_type => 'payment_abuse')
+ expect(response.ok?).to eq(true)
+ end
+
+
it "Successfuly handles a $label with the v203 API and returns OK" do
response_json = { :status => 0, :error_message => "OK" }
user_id = "frodo_baggins"
- stub_request(:post, "https://api.siftscience.com/v203/users/frodo_baggins/labels").
- with(:body => '{"$reasons":["$fake"],"$is_bad":true,"$description":"Listed a fake item","$type":"$label","$api_key":"foobar"}').
- to_return(:body => MultiJson.dump(response_json), :status => 200, :headers =>
- {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ stub_request(:post, "https://api.siftscience.com/v203/users/frodo_baggins/labels")
+ .with(:body => ('{"$reasons":["$fake"],"$is_bad":true,"$description":"Listed a fake item","$type":"$label","$api_key":"foobar"}'))
+ .to_return(:body => MultiJson.dump(response_json), :status => 200,
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
api_key = "foobar"
- properties = valid_label_properties
+ properties = valid_label_properties_203
- response = Sift::Client.new(api_key).label(user_id, properties)
+ response = Sift::Client.new(:api_key => api_key, :version => 203).label(user_id, properties)
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
end
+
it "Successfully handles an $unlabel with the v203 API endpoing and returns OK" do
response_json = { :status => 0, :error_message => "OK" }
user_id = "frodo_baggins"
- stub_request(:delete, "https://api.siftscience.com/v203/users/frodo_baggins/labels?api_key=foobar").
- to_return(:status => 204)
+ stub_request(:delete,
+ "https://api.siftscience.com/v203/users/frodo_baggins/labels?api_key=foobar")
+ .to_return(:status => 204)
api_key = "foobar"
- response = Sift::Client.new(api_key).unlabel(user_id)
+ response = Sift::Client.new(:api_key => api_key).unlabel(user_id, :version => "203")
expect(response.ok?).to eq(true)
end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index 4b81e11..fd9e795 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -78,60 +78,69 @@ def action_response_json
end
def fully_qualified_api_endpoint
- Sift::Client::API_ENDPOINT + Sift.current_rest_api_path
+ Sift::Client::API_ENDPOINT + Sift.rest_api_path
end
+
it "Can instantiate client with blank api key if Sift.api_key set" do
Sift.api_key = "test_global_api_key"
expect(Sift::Client.new().api_key).to eq(Sift.api_key)
end
+
it "Parameter passed api key takes precedence over Sift.api_key" do
Sift.api_key = "test_global_api_key"
api_key = "test_local_api_key"
- expect(Sift::Client.new(api_key).api_key).to eq(api_key)
+ expect(Sift::Client.new(:api_key => api_key).api_key).to eq(api_key)
end
+
it "Cannot instantiate client with nil, empty, non-string, or blank api key" do
- expect(lambda { Sift::Client.new(nil) }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new("") }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new(123456) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new(:api_key => nil) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new(:api_key => "") }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new(:api_key => 123456) }).to raise_error(StandardError)
expect(lambda { Sift::Client.new() }).to raise_error(StandardError)
end
- it "Cannot instantiate client with nil, empty, non-string, or blank path" do
+
+ it "Cannot instantiate client with empty, non-string, or blank path" do
api_key = "test_local_api_key"
- expect(lambda { Sift::Client.new(api_key, nil) }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new(api_key, "") }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new(api_key, 123456) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new(:path => "") }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new(:path => 123456) }).to raise_error(StandardError)
end
+
it "Can instantiate client with non-default timeout" do
- expect(lambda { Sift::Client.new("test_local_api_key", Sift.current_rest_api_path, 4) }).not_to raise_error
+ expect(lambda { Sift::Client.new(:api_key => "foo", :timeout => 4) })
+ .not_to raise_error
end
+
it "Track call must specify an event name" do
- expect(lambda { Sift::Client.new("foo").track(nil) }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new("foo").track("") }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().track(nil) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().track("") }).to raise_error(StandardError)
end
+
it "Must specify an event name" do
- expect(lambda { Sift::Client.new("foo").track(nil) }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new("foo").track("") }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().track(nil) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().track("") }).to raise_error(StandardError)
end
+
it "Must specify properties" do
event = "custom_event_name"
- expect(lambda { Sift::Client.new("foo").track(event) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().track(event) }).to raise_error(StandardError)
end
+
it "Score call must specify a user_id" do
- expect(lambda { Sift::Client.new("foo").score(nil) }).to raise_error(StandardError)
- expect(lambda { Sift::Client.new("foo").score("") }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().score(nil) }).to raise_error(StandardError)
+ expect(lambda { Sift::Client.new().score("") }).to raise_error(StandardError)
end
- it "Doesn't raise an exception on Net/HTTP errors" do
+ it "Doesn't raise an exception on Net/HTTP errors" do
api_key = "foobar"
event = "$transaction"
properties = valid_transaction_properties
@@ -140,11 +149,11 @@ def fully_qualified_api_endpoint
# This method should just return nil -- the track call failed because
# of an HTTP error
- expect(Sift::Client.new(api_key).track(event, properties)).to eq(nil)
+ expect(Sift::Client.new(:api_key => api_key).track(event, properties)).to eq(nil)
end
- it "Returns nil when a StandardError occurs within the request" do
+ it "Returns nil when a StandardError occurs within the request" do
api_key = "foobar"
event = "$transaction"
properties = valid_transaction_properties
@@ -153,32 +162,35 @@ def fully_qualified_api_endpoint
# This method should just return nil -- the track call failed because
# a StandardError exception was thrown
- expect(Sift::Client.new(api_key).track(event, properties)).to eq(nil)
+ expect(Sift::Client.new(:api_key => api_key).track(event, properties)).to eq(nil)
end
- it "Successfuly handles an event and returns OK" do
+ it "Successfuly handles an event and returns OK" do
response_json = { :status => 0, :error_message => "OK" }
- stub_request(:post, "https://api.siftscience.com/v203/events").
+ stub_request(:post, "https://api.siftscience.com/v204/events").
with { |request|
parsed_body = JSON.parse(request.body)
expect(parsed_body).to include("$buyer_user_id" => "123456")
- }.to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ }.to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
api_key = "foobar"
event = "$transaction"
properties = valid_transaction_properties
- response = Sift::Client.new(api_key).track(event, properties)
+ response = Sift::Client.new(:api_key => api_key).track(event, properties)
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
end
+
it "Successfully submits event with overridden key" do
response_json = { :status => 0, :error_message => "OK"}
- stub_request(:post, "https://api.siftscience.com/v203/events").
+ stub_request(:post, "https://api.siftscience.com/v204/events").
with { | request|
parsed_body = JSON.parse(request.body)
expect(parsed_body).to include("$buyer_user_id" => "123456")
@@ -189,22 +201,25 @@ def fully_qualified_api_endpoint
event = "$transaction"
properties = valid_transaction_properties
- response = Sift::Client.new(api_key).track(event, properties, nil, nil, false, "overridden", false)
+ response = Sift::Client.new(:api_key => api_key)
+ .track(event, properties, :api_key => "overridden")
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
end
- it "Successfuly scrubs nils" do
+ it "Successfully scrubs nils" do
response_json = { :status => 0, :error_message => "OK" }
- stub_request(:post, "https://api.siftscience.com/v203/events").
- with { |request|
+ stub_request(:post, "https://api.siftscience.com/v204/events")
+ .with { |request|
parsed_body = JSON.parse(request.body)
expect(parsed_body).not_to include("fake_property")
expect(parsed_body).to include("sub_object" => {"one" => "two"})
- }.to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ }.to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
api_key = "foobar"
event = "$transaction"
@@ -215,21 +230,23 @@ def fully_qualified_api_endpoint
"three" => nil
}
)
- response = Sift::Client.new(api_key).track(event, properties)
+ response = Sift::Client.new(:api_key => api_key).track(event, properties)
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
end
- it "Successfully fetches a score" do
+ it "Successfully fetches a score" do
api_key = "foobar"
response_json = score_response_json
- stub_request(:get, "https://api.siftscience.com/v203/score/247019/?api_key=foobar").
- to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ stub_request(:get, "https://api.siftscience.com/v204/score/247019/?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).score(score_response_json[:user_id])
+ response = Sift::Client.new(:api_key => api_key).score(score_response_json[:user_id])
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
@@ -237,15 +254,16 @@ def fully_qualified_api_endpoint
expect(response.body["score"]).to eq(0.93)
end
- it "Successfully fetches a score with an overridden key" do
+ it "Successfully fetches a score with an overridden key" do
api_key = "foobar"
response_json = score_response_json
- stub_request(:get, "https://api.siftscience.com/v203/score/247019/?api_key=overridden").
- to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
+ stub_request(:get, "https://api.siftscience.com/v204/score/247019/?api_key=overridden")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {})
- response = Sift::Client.new(api_key).score(score_response_json[:user_id], nil, "overridden")
+ response = Sift::Client.new(:api_key => api_key)
+ .score(score_response_json[:user_id], :api_key => "overridden")
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
@@ -254,8 +272,7 @@ def fully_qualified_api_endpoint
end
- it "Successfuly make a sync score request" do
-
+ it "Successfully make a sync score request" do
api_key = "foobar"
response_json = {
:status => 0,
@@ -263,20 +280,23 @@ def fully_qualified_api_endpoint
:score_response => score_response_json
}
- stub_request(:post, "https://api.siftscience.com/v203/events?return_score=true").
- to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ stub_request(:post, "https://api.siftscience.com/v204/events?return_score=true")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
event = "$transaction"
properties = valid_transaction_properties
- response = Sift::Client.new(api_key).track(event, properties, nil, nil, true, nil, nil)
+ response = Sift::Client.new(:api_key => api_key)
+ .track(event, properties, :return_score => true)
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
expect(response.body["score_response"]["score"]).to eq(0.93)
end
- it "Successfuly make a sync action request" do
+ it "Successfully make a sync action request" do
api_key = "foobar"
response_json = {
:status => 0,
@@ -284,12 +304,15 @@ def fully_qualified_api_endpoint
:score_response => action_response_json
}
- stub_request(:post, "https://api.siftscience.com/v203/events?return_action=true").
- to_return(:status => 200, :body => MultiJson.dump(response_json), :headers => {"content-type"=>"application/json; charset=UTF-8","content-length"=> "74"})
+ stub_request(:post, "https://api.siftscience.com/v204/events?return_action=true")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
event = "$transaction"
properties = valid_transaction_properties
- response = Sift::Client.new(api_key).track(event, properties, nil, nil, nil, nil, true)
+ response = Sift::Client.new(:api_key => api_key)
+ .track(event, properties, :return_action => true)
expect(response.ok?).to eq(true)
expect(response.api_status).to eq(0)
expect(response.api_error_message).to eq("OK")
@@ -297,5 +320,74 @@ def fully_qualified_api_endpoint
end
+ it "Successfully make a sync workflow request" do
+ api_key = "foobar"
+ response_json = {
+ :status => 0,
+ :error_message => "OK",
+ :score_response => {
+ :status => -1,
+ :error_message => "Internal server error."
+ }
+ }
+
+ stub_request(:post,
+ "https://api.siftscience.com/v204/events?return_workflow_status=true&abuse_types=legacy,payment_abuse")
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
+ "content-length"=> "74"})
+
+ event = "$transaction"
+ properties = valid_transaction_properties
+ response = Sift::Client.new(:api_key => api_key)
+ .track(event, properties,
+ :return_workflow_status => true, :abuse_types => ['legacy', 'payment_abuse'])
+ expect(response.ok?).to eq(true)
+ expect(response.api_status).to eq(0)
+ expect(response.api_error_message).to eq("OK")
+ end
+
+
+ it "Successfully make a workflow status request" do
+ response_text = '{"id":"skdjfnkse","config":{"id":"5rrbr4iaaa","version":"1468367620871"},"config_display_name":"workflow config","abuse_types":["payment_abuse"],"state":"running","entity":{"id":"example_user","type":"user"},"history":[{"app":"user","name":"Entity","state":"finished","config":{}}]}'
+
+ stub_request(:get, "https://foobar:@api3.siftscience.com/v3/accounts/ACCT/workflows/runs/skdjfnkse")
+ .to_return(:status => 200, :body => response_text, :headers => {})
+
+ client = Sift::Client.new(:api_key => "foobar", :account_id => "ACCT")
+ response = client.get_workflow_status("skdjfnkse")
+
+ expect(response.ok?).to eq(true)
+ expect(response.body["id"]).to eq("skdjfnkse")
+ expect(response.body["state"]).to eq("running")
+ end
+
+
+ it "Successfully make a user decisions request" do
+ response_text = '{"decisions":{"content_abuse":{"decision":{"id":"user_decision"},"time":1468707128659,"webhook_succeeded":false}}}'
+
+ stub_request(:get, "https://foobar:@api3.siftscience.com/v3/accounts/ACCT/users/example_user/decisions")
+ .to_return(:status => 200, :body => response_text, :headers => {})
+
+ client = Sift::Client.new(:api_key => "foobar", :account_id => "ACCT")
+ response = client.get_user_decisions("example_user")
+
+ expect(response.ok?).to eq(true)
+ expect(response.body["decisions"]["content_abuse"]["decision"]["id"]).to eq("user_decision")
+ end
+
+
+ it "Successfully make an order decisions request" do
+ response_text = '{"decisions":{"payment_abuse":{"decision":{"id":"decision7"},"time":1468599638005,"webhook_succeeded":false},"promotion_abuse":{"decision":{"id":"good_order"},"time":1468517407135,"webhook_succeeded":true}}}'
+
+ stub_request(:get, "https://foobar:@api3.siftscience.com/v3/accounts/ACCT/orders/example_order/decisions")
+ .to_return(:status => 200, :body => response_text, :headers => {})
+
+ client = Sift::Client.new(:api_key => "foobar", :account_id => "ACCT")
+ response = client.get_order_decisions("example_order", :timeout => 3)
+
+ expect(response.ok?).to eq(true)
+ expect(response.body["decisions"]["payment_abuse"]["decision"]["id"]).to eq("decision7")
+ end
end