sift 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbaf2000cc0d9083f104b38c5e04ca271cc2cd9c
4
- data.tar.gz: c90f468af4bdfdf91a04b22faaa0ec7eb637fd29
3
+ metadata.gz: 57a679adbe12a556895633060cde177cf89fcb06
4
+ data.tar.gz: d3eff77b83c2ba5696c4c391f82d592a8df7443a
5
5
  SHA512:
6
- metadata.gz: bb51191b838c93928607ef420f8ffd5588bf1de49f3e2f5e6ade15b9ce3e9d4a8d4b043b62605716cd452f261f4d39a48aacc33938d8a97c0c41a18ebc243482
7
- data.tar.gz: 57fcde0d67394bacc175815dc12c60edf25782f75baad7174acc8bcded6d1d63a277aad4df2c1d7bceab2bcadbb7f520c772cad3c7be5c57c267dcff049ca059
6
+ metadata.gz: 5809b998556c975ff30faceacf5468a812e815dead3e836640343fe681c1d612192a9a26615f8a0c35c4c13a3cb9b232f3aff1bbf7649d6b3e6c8250cd7f2896
7
+ data.tar.gz: 8b941c79e41784d1452f8ae4fe8b1052f7f32ed3954f8afb16b16f3e7346a0bf6e5cfc71f6080c6475ff75a0fce78172a58e4f8ece8f73ab47385ffd57d3001a
@@ -0,0 +1,43 @@
1
+ version: 2
2
+
3
+ jobs:
4
+ build:
5
+ parallelism: 3 # run three instances of this job in parallel
6
+ docker:
7
+ - image: circleci/ruby:2.4.2-jessie-node
8
+ environment:
9
+ BUNDLE_JOBS: 3
10
+ BUNDLE_RETRY: 3
11
+ BUNDLE_PATH: vendor/bundle
12
+ steps:
13
+ - checkout
14
+
15
+ - run:
16
+ name: Which bundler?
17
+ command: bundle -v
18
+
19
+ - restore_cache:
20
+ keys:
21
+ - sift-bundle-{{ checksum "sift.gemspec" }}
22
+ - sift-bundle-
23
+
24
+ - run:
25
+ name: Bundle Install
26
+ command: bundle check || bundle install
27
+
28
+ - save_cache:
29
+ key: sift-bundle-{{ checksum "sift.gemspec" }}
30
+ paths:
31
+ - vendor/bundle
32
+
33
+ - run:
34
+ name: Run rspec in parallel
35
+ command: |
36
+ bundle exec rspec --profile 10 \
37
+ --format RspecJunitFormatter \
38
+ --out test_results/rspec.xml \
39
+ --format progress \
40
+ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
41
+
42
+ - store_test_results:
43
+ path: test_results
data/HISTORY CHANGED
@@ -1,8 +1,30 @@
1
- === 3.0.0.0 2018-03-05
2
- * adds support for v205 of Sift Science's APIs
3
- * v205 APIs are now called by default -- this is an incompatible change
4
- (use :version => 204 to call the previous API version)
5
- * Add content level decisions in Apply Decisions APIs.
1
+ === 4.0.0 2019-05-15
2
+ - Breaking change: Propagate exception in Client.track() previously we were silently swallowing exceptions and now we are bubbling them up. You will need to add exception handling code.
3
+ - Fix URL encoding
4
+ - Add Circle build
5
+
6
+ === 3.3.0 2018-07-31
7
+ - Add support for rescore_user and get_user_score APIs
8
+
9
+ === 3.2.0 2018-07-05
10
+ - Add new query parameter force_workflow_run
11
+
12
+ === 3.1.0 2018-06-04
13
+ - Adds support for get session decisions to [Decisions API](https://siftscience.com/developers/docs/curl/decisions-api)
14
+
15
+ === 3.0.1 2018-04-06
16
+ - Improved documentation on HISTORY and README.MD
17
+
18
+ === 3.0.0 2018-03-05
19
+ - Adds support for Sift Science API Version 205, including new [`$create_content`](https://siftscience.com/developers/docs/curl/events-api/reserved-events/create-content) and [`$update_content`](https://siftscience.com/developers/docs/curl/events-api/reserved-events/update-content) formats
20
+ - V205 APIs are now called -- **this is an incompatible change**
21
+ - (use `:version => 204` to call the previous API version)
22
+ - Adds support for content decisions to [Decisions API](https://siftscience.com/developers/docs/curl/decisions-api)
23
+
24
+ INCOMPATIBLE CHANGES INTRODUCED IN API V205:
25
+ - `$create_content` and `$update_content` have significantly changed, and the old format will be rejected
26
+ - `$send_message` and `$submit_review` events are no longer valid
27
+ - V205 improves server-side event data validation. In V204 and earlier, server-side validation accepted some events that did not conform to the published APIs in our [developer documentation](https://siftscience.com/developers/docs/curl/events-api). V205 does not modify existing event APIs other than those mentioned above, but may reject invalid event data that were previously accepted. **Please test your integration on V205 in sandbox before using in production.**
6
28
 
7
29
  === 2.2.1.0 2018-02-12
8
30
  * Add session level decisions in Apply Decisions APIs.
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # Sift Science Ruby bindings [![Build Status](https://travis-ci.org/SiftScience/sift-ruby.png?branch=master)](https://travis-ci.org/SiftScience/sift-ruby)
1
+ # Sift Ruby bindings
2
+
3
+ [![CircleCI](https://circleci.com/gh/SiftScience/sift-ruby.svg?style=svg)](https://circleci.com/gh/SiftScience/sift-ruby)
2
4
 
3
5
  ## Requirements
4
6
 
5
- * Ruby 1.9.3 or above.
7
+ * Ruby 2.0.0 or above.
6
8
 
7
9
 
8
10
  ## Installation
@@ -32,7 +34,7 @@ client = Sift::Client.new()
32
34
 
33
35
  ##### OR
34
36
 
35
- client = Sift::Client.new(api_key: '<your_api_key_here>', account_id: '<your_account_id_here>'
37
+ client = Sift::Client.new(api_key: '<your_api_key_here>', account_id: '<your_account_id_here>')
36
38
 
37
39
  ```
38
40
 
@@ -73,14 +75,14 @@ response = client.score(user_id)
73
75
 
74
76
  ## Decisions
75
77
 
76
- To learn more about the decisions endpoint visit our [developer docs](https://siftscience.com/developers/docs/ruby/decisions-api/get-decisions).
78
+ To learn more about the decisions endpoint visit our [developer docs](https://sift.com/developers/docs/ruby/decisions-api/get-decisions).
77
79
 
78
80
  ### List of Configured Decisions
79
81
 
80
82
  Get a list of your decisions.
81
83
 
82
84
  **Optional Params**
83
- - `entity_type`: `:user` or `:order` or `:session`
85
+ - `entity_type`: `:user` or `:order` or `:session` or `:content`
84
86
  - `abuse_types`: `["payment_abuse", "content_abuse", "content_abuse",
85
87
  "account_abuse", "legacy", "account_takeover"]`
86
88
 
@@ -117,7 +119,7 @@ end
117
119
 
118
120
  ### Apply a decision
119
121
 
120
- Applies a decision to an entity. Visit our [developer docs](http://siftscience.com/developers/docs/ruby/decisions-api/apply-decision) for more information.
122
+ Applies a decision to an entity. Visit our [developer docs](http://sift.com/developers/docs/ruby/decisions-api/apply-decision) for more information.
121
123
 
122
124
  **Required Params:**
123
125
  - `decision_id`, `source`, `user_id`
@@ -207,6 +209,9 @@ response = client.get_user_decisions('example_user_id')
207
209
  # Get the latest decisions for an order
208
210
  response = client.get_order_decisions('example_order_id')
209
211
 
212
+ # Get the latest decisions for a session
213
+ response = client.get_session_decisions('example_user_id', 'example_session_id')
214
+
210
215
  # Get the latest decisions for an content
211
216
  response = client.get_content_decisions('example_user_id', 'example_order_id')
212
217
  ```
@@ -1,5 +1,6 @@
1
- require_relative "./sift/version"
2
- require_relative "./sift/client"
1
+ require_relative './sift/version'
2
+ require_relative './sift/client'
3
+ require 'erb'
3
4
 
4
5
  module Sift
5
6
 
@@ -10,32 +11,49 @@ module Sift
10
11
 
11
12
  # Returns the Score API path for the specified user ID and API version
12
13
  def self.score_api_path(user_id, version=API_VERSION)
13
- "/v#{version}/score/#{URI.encode(user_id)}/"
14
+ "/v#{version}/score/#{ERB::Util.url_encode(user_id)}/"
15
+ end
16
+
17
+ # Returns the User Score API path for the specified user ID and API version
18
+ def self.user_score_api_path(user_id, version=API_VERSION)
19
+ "/v#{version}/users/#{ERB::Util.url_encode(user_id)}/score"
14
20
  end
15
21
 
16
22
  # Returns the users API path for the specified user ID and API version
17
23
  def self.users_label_api_path(user_id, version=API_VERSION)
18
- "/v#{version}/users/#{URI.encode(user_id)}/labels"
24
+ "/v#{version}/users/#{ERB::Util.url_encode(user_id)}/labels"
19
25
  end
20
26
 
21
27
  # Returns the path for the Workflow Status API
22
28
  def self.workflow_status_path(account_id, run_id)
23
- "/v3/accounts/#{account_id}/workflows/runs/#{run_id}"
29
+ "/v3/accounts/#{ERB::Util.url_encode(account_id)}" \
30
+ "/workflows/runs/#{ERB::Util.url_encode(run_id)}"
24
31
  end
25
32
 
26
33
  # Returns the path for User Decisions API
27
34
  def self.user_decisions_api_path(account_id, user_id)
28
- "/v3/accounts/#{account_id}/users/#{user_id}/decisions"
35
+ "/v3/accounts/#{ERB::Util.url_encode(account_id)}" \
36
+ "/users/#{ERB::Util.url_encode(user_id)}/decisions"
29
37
  end
30
38
 
31
39
  # Returns the path for Orders Decisions API
32
40
  def self.order_decisions_api_path(account_id, order_id)
33
- "/v3/accounts/#{account_id}/orders/#{order_id}/decisions"
41
+ "/v3/accounts/#{ERB::Util.url_encode(account_id)}" \
42
+ "/orders/#{ERB::Util.url_encode(order_id)}/decisions"
43
+ end
44
+
45
+ # Returns the path for Session Decisions API
46
+ def self.session_decisions_api_path(account_id, user_id, session_id)
47
+ "/v3/accounts/#{ERB::Util.url_encode(account_id)}" \
48
+ "/users/#{ERB::Util.url_encode(user_id)}" \
49
+ "/sessions/#{ERB::Util.url_encode(session_id)}/decisions"
34
50
  end
35
51
 
36
52
  # Returns the path for Content Decisions API
37
53
  def self.content_decisions_api_path(account_id, user_id, content_id)
38
- "/v3/accounts/#{account_id}/users/#{user_id}/content/#{content_id}/decisions"
54
+ "/v3/accounts/#{ERB::Util.url_encode(account_id)}" \
55
+ "/users/#{ERB::Util.url_encode(user_id)}" \
56
+ "/content/#{ERB::Util.url_encode(content_id)}/decisions"
39
57
  end
40
58
 
41
59
  # Module-scoped public API key
@@ -69,5 +87,4 @@ module Sift
69
87
  def self.fatal(msg)
70
88
  @logger.fatal(msg) if @logger
71
89
  end
72
-
73
90
  end
@@ -34,15 +34,25 @@ module Sift
34
34
 
35
35
  # only set these variables if a message-body is expected.
36
36
  if not @http_raw_response.kind_of? Net::HTTPNoContent
37
- @body = MultiJson.load(http_response) unless http_response.nil?
38
- @request = MultiJson.load(@body["request"].to_s) if @body["request"]
39
- @api_status = @body["status"].to_i if @body["status"]
40
- @api_error_message = @body["error_message"]
41
-
42
- if @body["error"]
43
- @api_error_message = @body["error"]
44
- @api_error_description = @body["description"]
45
- @api_error_issues = @body["issues"] || {}
37
+
38
+ begin
39
+ @body = MultiJson.load(http_response) unless http_response.nil?
40
+ rescue
41
+ if @http_status_code == 200
42
+ raise TypeError.new
43
+ end
44
+ end
45
+
46
+ if not @body.nil?
47
+ @request = MultiJson.load(@body["request"].to_s) if @body["request"]
48
+ @api_status = @body["status"].to_i if @body["status"]
49
+ @api_error_message = @body["error_message"]
50
+
51
+ if @body["error"]
52
+ @api_error_message = @body["error"]
53
+ @api_error_description = @body["description"]
54
+ @api_error_issues = @body["issues"] || {}
55
+ end
46
56
  end
47
57
  end
48
58
  end
@@ -194,8 +204,8 @@ module Sift
194
204
  #
195
205
  # ==== Returns:
196
206
  #
197
- # In the case of a connection error (timeout, broken connection,
198
- # etc.), this method returns nil; otherwise, a Response object is
207
+ # In the case of a network error (timeout, broken connection, etc.),
208
+ # this method propagates the exception, otherwise, a Response object is
199
209
  # returned that captures the status message and status code.
200
210
  #
201
211
  def track(event, properties = {}, opts = {})
@@ -206,6 +216,7 @@ module Sift
206
216
  return_score = opts[:return_score]
207
217
  return_action = opts[:return_action]
208
218
  return_workflow_status = opts[:return_workflow_status]
219
+ force_workflow_run = opts[:force_workflow_run]
209
220
  abuse_types = opts[:abuse_types]
210
221
 
211
222
  raise("event must be a non-empty string") if (!event.is_a? String) || event.empty?
@@ -216,6 +227,7 @@ module Sift
216
227
  query["return_score"] = "true" if return_score
217
228
  query["return_action"] = "true" if return_action
218
229
  query["return_workflow_status"] = "true" if return_workflow_status
230
+ query["force_workflow_run"] = "true" if force_workflow_run
219
231
  query["abuse_types"] = abuse_types.join(",") if abuse_types
220
232
 
221
233
  options = {
@@ -226,14 +238,8 @@ module Sift
226
238
  }
227
239
  options.merge!(:timeout => timeout) unless timeout.nil?
228
240
 
229
- begin
230
- response = self.class.post(path, options)
231
- Response.new(response.body, response.code, response.response)
232
- rescue StandardError => e
233
- Sift.warn("Failed to track event: " + e.to_s)
234
- Sift.warn(e.backtrace)
235
- nil
236
- end
241
+ response = self.class.post(path, options)
242
+ Response.new(response.body, response.code, response.response)
237
243
  end
238
244
 
239
245
 
@@ -293,6 +299,114 @@ module Sift
293
299
  end
294
300
 
295
301
 
302
+ # Fetches the latest score(s) computed for the specified user and abuse types.
303
+ #
304
+ # As opposed to client.score() and client.rescore_user(), this *does not* compute
305
+ # a new score for the user; it simply fetches the latest score(s) which have computed.
306
+ # These scores may be arbitrarily old.
307
+ #
308
+ # See https://siftscience.com/developers/docs/ruby/score-api/get-score for more details.
309
+ #
310
+ # ==== Parameters:
311
+ #
312
+ # user_id::
313
+ # A user's id. This id should be the same as the user_id used in
314
+ # event calls.
315
+ #
316
+ # opts (optional)::
317
+ # A Hash of optional parameters for the request --
318
+ #
319
+ # :abuse_types::
320
+ # List of abuse types, specifying for which abuse types a
321
+ # score should be returned. By default, a score is returned
322
+ # for every abuse type to which you are subscribed.
323
+ #
324
+ # :api_key::
325
+ # Overrides the API key for this call.
326
+ #
327
+ # :timeout::
328
+ # Overrides the timeout (in seconds) for this call.
329
+ #
330
+ # ==== Returns:
331
+ #
332
+ # A Response object containing a status code, status message, and,
333
+ # if successful, the user's score(s).
334
+ #
335
+ def get_user_score(user_id, opts = {})
336
+ abuse_types = opts[:abuse_types]
337
+ api_key = opts[:api_key] || @api_key
338
+ timeout = opts[:timeout] || @timeout
339
+
340
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
341
+ raise("Bad api_key parameter") if api_key.empty?
342
+
343
+ query = {}
344
+ query["api_key"] = api_key
345
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
346
+
347
+ options = {
348
+ :headers => {"User-Agent" => user_agent},
349
+ :query => query
350
+ }
351
+ options.merge!(:timeout => timeout) unless timeout.nil?
352
+
353
+ response = self.class.get(Sift.user_score_api_path(user_id, @version), options)
354
+ Response.new(response.body, response.code, response.response)
355
+ end
356
+
357
+
358
+ # Rescores the specified user for the specified abuse types and returns the resulting score(s).
359
+ #
360
+ # See https://siftscience.com/developers/docs/ruby/score-api/rescore for more details.
361
+ #
362
+ # ==== Parameters:
363
+ #
364
+ # user_id::
365
+ # A user's id. This id should be the same as the user_id used in
366
+ # event calls.
367
+ #
368
+ # opts (optional)::
369
+ # A Hash of optional parameters for the request --
370
+ #
371
+ # :abuse_types::
372
+ # List of abuse types, specifying for which abuse types a
373
+ # score should be returned. By default, a score is returned
374
+ # for every abuse type to which you are subscribed.
375
+ #
376
+ # :api_key::
377
+ # Overrides the API key for this call.
378
+ #
379
+ # :timeout::
380
+ # Overrides the timeout (in seconds) for this call.
381
+ #
382
+ # ==== Returns:
383
+ #
384
+ # A Response object containing a status code, status message, and,
385
+ # if successful, the user's score(s).
386
+ #
387
+ def rescore_user(user_id, opts = {})
388
+ abuse_types = opts[:abuse_types]
389
+ api_key = opts[:api_key] || @api_key
390
+ timeout = opts[:timeout] || @timeout
391
+
392
+ raise("user_id must be a non-empty string") if (!user_id.is_a? String) || user_id.to_s.empty?
393
+ raise("Bad api_key parameter") if api_key.empty?
394
+
395
+ query = {}
396
+ query["api_key"] = api_key
397
+ query["abuse_types"] = abuse_types.join(",") if abuse_types
398
+
399
+ options = {
400
+ :headers => {"User-Agent" => user_agent},
401
+ :query => query
402
+ }
403
+ options.merge!(:timeout => timeout) unless timeout.nil?
404
+
405
+ response = self.class.post(Sift.user_score_api_path(user_id, @version), options)
406
+ Response.new(response.body, response.code, response.response)
407
+ end
408
+
409
+
296
410
  # Labels a user.
297
411
  #
298
412
  # See https://siftscience.com/developers/docs/ruby/labels-api/label-user .
@@ -504,6 +618,45 @@ module Sift
504
618
  Response.new(response.body, response.code, response.response)
505
619
  end
506
620
 
621
+ # Gets the decision status of a session.
622
+ #
623
+ # See https://siftscience.com/developers/docs/ruby/decisions-api/decision-status .
624
+ #
625
+ # ==== Parameters
626
+ #
627
+ # user_id::
628
+ # The ID of the user in the session.
629
+ #
630
+ # session_id::
631
+ # The ID of a session.
632
+ #
633
+ # opts (optional)::
634
+ # A Hash of optional parameters for this request --
635
+ #
636
+ # :account_id::
637
+ # Overrides the account id for this call.
638
+ #
639
+ # :api_key::
640
+ # Overrides the API key for this call.
641
+ #
642
+ # :timeout::
643
+ # Overrides the timeout (in seconds) for this call.
644
+ #
645
+ def get_session_decisions(user_id, session_id, opts = {})
646
+ account_id = opts[:account_id] || @account_id
647
+ api_key = opts[:api_key] || @api_key
648
+ timeout = opts[:timeout] || @timeout
649
+
650
+ options = {
651
+ :headers => { "User-Agent" => user_agent },
652
+ :basic_auth => { :username => api_key, :password => "" }
653
+ }
654
+ options.merge!(:timeout => timeout) unless timeout.nil?
655
+
656
+ uri = API3_ENDPOINT + Sift.session_decisions_api_path(account_id, user_id, session_id)
657
+ response = self.class.get(uri, options)
658
+ Response.new(response.body, response.code, response.response)
659
+ end
507
660
 
508
661
  # Gets the decision status of a piece of content.
509
662
  #
@@ -1,4 +1,4 @@
1
1
  module Sift
2
- VERSION = "3.0.0"
2
+ VERSION = "4.0.0"
3
3
  API_VERSION = "205"
4
4
  end
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # Gems that must be intalled for sift to compile and build
23
23
  s.add_development_dependency "rspec", "~> 3.5"
24
+ s.add_development_dependency "rspec_junit_formatter"
24
25
  s.add_development_dependency "pry"
25
26
  s.add_development_dependency "webmock", ">= 1.16.0", "< 2"
26
27
 
@@ -43,6 +43,38 @@ describe Sift::Client do
43
43
  }
44
44
  end
45
45
 
46
+ def user_score_response_json
47
+ {
48
+ :entity_type => "user",
49
+ :entity_id => "247019",
50
+ :scores => {
51
+ :payment_abuse => {
52
+ :score => 0.78
53
+ },
54
+ :content_abuse => {
55
+ :score => 0.11
56
+ }
57
+ },
58
+ :latest_decisions => {
59
+ :payment_abuse => {
60
+ :id => "user_looks_bad_payment_abuse",
61
+ :category => "block",
62
+ :source => "AUTOMATED_RULE",
63
+ :time => 1352201880,
64
+ :description => "Bad Fraudster"
65
+ }
66
+ },
67
+ :latest_labels => {
68
+ :payment_abuse => {
69
+ :is_bad => true,
70
+ :time => 1352201880
71
+ }
72
+ },
73
+ :status => 0,
74
+ :error_message => "OK"
75
+ }
76
+ end
77
+
46
78
  def action_response_json
47
79
  {
48
80
  :user_id => "247019",
@@ -140,29 +172,52 @@ describe Sift::Client do
140
172
  end
141
173
 
142
174
 
143
- it "Doesn't raise an exception on Net/HTTP errors" do
175
+ it "Handles parse exceptions for 500 status" do
144
176
  api_key = "foobar"
145
177
  event = "$transaction"
146
178
  properties = valid_transaction_properties
179
+ res = nil
180
+
181
+ stub_request(:any, /.*/).to_return(:body => "{123", :status => 500)
182
+
183
+ expect { res = Sift::Client.new(:api_key => api_key).track(event, properties) }.not_to raise_error
184
+ expect(res.http_status_code).to eq(500)
185
+ end
186
+
187
+
188
+ it "Handles parse exceptions for 200 status" do
189
+ api_key = "foobar"
190
+ event = "$transaction"
191
+ properties = valid_transaction_properties
192
+ res = nil
193
+
194
+ stub_request(:any, /.*/).to_return(:body => "{123", :status => 200)
195
+
196
+ expect { res = Sift::Client.new(:api_key => api_key).track(event, properties) }.to raise_error(TypeError)
197
+ end
198
+
199
+
200
+ it "Preserves response on HTTP errors but does not raise an exception" do
201
+ api_key = "foobar"
202
+ event = "$transaction"
203
+ properties = valid_transaction_properties
204
+ res = nil
147
205
 
148
206
  stub_request(:any, /.*/).to_return(:status => 401)
149
207
 
150
- # This method should just return nil -- the track call failed because
151
- # of an HTTP error
152
- expect(Sift::Client.new(:api_key => api_key).track(event, properties)).to eq(nil)
208
+ expect { res = Sift::Client.new(:api_key => api_key).track(event, properties) }.not_to raise_error
209
+ expect(res.http_status_code).to eq(401)
153
210
  end
154
211
 
155
212
 
156
- it "Returns nil when a StandardError occurs within the request" do
213
+ it "Propagates exception when a StandardError occurs within the request" do
157
214
  api_key = "foobar"
158
215
  event = "$transaction"
159
216
  properties = valid_transaction_properties
160
217
 
161
218
  stub_request(:any, /.*/).to_raise(StandardError)
162
219
 
163
- # This method should just return nil -- the track call failed because
164
- # a StandardError exception was thrown
165
- expect(Sift::Client.new(:api_key => api_key).track(event, properties)).to eq(nil)
220
+ expect { Sift::Client.new(:api_key => api_key).track(event, properties) }.to raise_error(StandardError)
166
221
  end
167
222
 
168
223
 
@@ -272,6 +327,88 @@ describe Sift::Client do
272
327
  end
273
328
 
274
329
 
330
+ it "Successfully executes client.get_user_score()" do
331
+ api_key = "foobar"
332
+ response_json = user_score_response_json
333
+
334
+ stub_request(:get, "https://api.siftscience.com/v205/users/247019/score?api_key=foobar")
335
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
336
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
337
+ "content-length"=> "74"})
338
+
339
+ response = Sift::Client.new(:api_key => api_key).get_user_score(user_score_response_json[:entity_id])
340
+ expect(response.ok?).to eq(true)
341
+ expect(response.api_status).to eq(0)
342
+ expect(response.api_error_message).to eq("OK")
343
+
344
+ expect(response.body["entity_id"]).to eq("247019")
345
+ expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78)
346
+ end
347
+
348
+
349
+ it "Successfully executes client.get_user_score() with abuse types specified" do
350
+ api_key = "foobar"
351
+ response_json = user_score_response_json
352
+
353
+ stub_request(:get, "https://api.siftscience.com/v205/users/247019/score" +
354
+ "?api_key=foobar&abuse_types=content_abuse,payment_abuse")
355
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
356
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
357
+ "content-length"=> "74"})
358
+
359
+ response = Sift::Client.new(:api_key => api_key).get_user_score(user_score_response_json[:entity_id],
360
+ :abuse_types => ["content_abuse",
361
+ "payment_abuse"])
362
+ expect(response.ok?).to eq(true)
363
+ expect(response.api_status).to eq(0)
364
+ expect(response.api_error_message).to eq("OK")
365
+
366
+ expect(response.body["entity_id"]).to eq("247019")
367
+ expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78)
368
+ end
369
+
370
+
371
+ it "Successfully executes client.rescore_user()" do
372
+ api_key = "foobar"
373
+ response_json = user_score_response_json
374
+
375
+ stub_request(:post, "https://api.siftscience.com/v205/users/247019/score?api_key=foobar")
376
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
377
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
378
+ "content-length"=> "74"})
379
+
380
+ response = Sift::Client.new(:api_key => api_key).rescore_user(user_score_response_json[:entity_id])
381
+ expect(response.ok?).to eq(true)
382
+ expect(response.api_status).to eq(0)
383
+ expect(response.api_error_message).to eq("OK")
384
+
385
+ expect(response.body["entity_id"]).to eq("247019")
386
+ expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78)
387
+ end
388
+
389
+
390
+ it "Successfully executes client.rescore_user() with abuse types specified" do
391
+ api_key = "foobar"
392
+ response_json = user_score_response_json
393
+
394
+ stub_request(:post, "https://api.siftscience.com/v205/users/247019/score" +
395
+ "?api_key=foobar&abuse_types=content_abuse,payment_abuse")
396
+ .to_return(:status => 200, :body => MultiJson.dump(response_json),
397
+ :headers => {"content-type"=>"application/json; charset=UTF-8",
398
+ "content-length"=> "74"})
399
+
400
+ response = Sift::Client.new(:api_key => api_key).rescore_user(user_score_response_json[:entity_id],
401
+ :abuse_types => ["content_abuse",
402
+ "payment_abuse"])
403
+ expect(response.ok?).to eq(true)
404
+ expect(response.api_status).to eq(0)
405
+ expect(response.api_error_message).to eq("OK")
406
+
407
+ expect(response.body["entity_id"]).to eq("247019")
408
+ expect(response.body["scores"]["payment_abuse"]["score"]).to eq(0.78)
409
+ end
410
+
411
+
275
412
  it "Successfully make a sync score request" do
276
413
  api_key = "foobar"
277
414
  response_json = {
@@ -363,7 +500,7 @@ describe Sift::Client do
363
500
  end
364
501
 
365
502
 
366
- it "Successfully make a user decisions request" do
503
+ it "Successfully make a user decisions request" do
367
504
  response_text = '{"decisions":{"content_abuse":{"decision":{"id":"user_decision"},"time":1468707128659,"webhook_succeeded":false}}}'
368
505
 
369
506
  stub_request(:get, "https://foobar:@api3.siftscience.com/v3/accounts/ACCT/users/example_user/decisions")
@@ -390,6 +527,18 @@ describe Sift::Client do
390
527
  expect(response.body["decisions"]["payment_abuse"]["decision"]["id"]).to eq("decision7")
391
528
  end
392
529
 
530
+ it "Successfully make a session decisions request" do
531
+ response_text = '{"decisions":{"account_takeover":{"decision":{"id":"session_decision"},"time":1468707128659,"webhook_succeeded":false}}}'
532
+
533
+ stub_request(:get, "https://foobar:@api3.siftscience.com/v3/accounts/ACCT/users/example_user/sessions/example_session/decisions")
534
+ .to_return(:status => 200, :body => response_text, :headers => {})
535
+
536
+ client = Sift::Client.new(:api_key => "foobar", :account_id => "ACCT")
537
+ response = client.get_session_decisions("example_user", "example_session")
538
+
539
+ expect(response.ok?).to eq(true)
540
+ expect(response.body["decisions"]["account_takeover"]["decision"]["id"]).to eq("session_decision")
541
+ end
393
542
 
394
543
  it "Successfully make an content decisions request" do
395
544
  response_text = '{"decisions":{"content_abuse":{"decision":{"id":"decision7"},"time":1468599638005,"webhook_succeeded":false}}}'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sift
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Sadaghiani
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-04-05 00:00:00.000000000 Z
13
+ date: 2019-05-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
28
  version: '3.5'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rspec_junit_formatter
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
29
43
  - !ruby/object:Gem::Dependency
30
44
  name: pry
31
45
  requirement: !ruby/object:Gem::Requirement
@@ -109,8 +123,8 @@ executables: []
109
123
  extensions: []
110
124
  extra_rdoc_files: []
111
125
  files:
126
+ - ".circleci/config.yml"
112
127
  - ".gitignore"
113
- - ".travis.yml"
114
128
  - Gemfile
115
129
  - HISTORY
116
130
  - LICENSE
@@ -158,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
172
  version: '0'
159
173
  requirements: []
160
174
  rubyforge_project: sift
161
- rubygems_version: 2.4.6
175
+ rubygems_version: 2.5.2.3
162
176
  signing_key:
163
177
  specification_version: 4
164
178
  summary: Sift Science Ruby API Gem
@@ -1,18 +0,0 @@
1
- language: ruby
2
- script: "bundle exec rake spec"
3
- rvm:
4
- - 1.9.3
5
- - 2.0.0
6
- - 2.1.5
7
- - 2.2.1
8
- before_install:
9
- - gem install bundler
10
- - bundle --version
11
- env:
12
- # None for now
13
- gemfile:
14
- - Gemfile
15
- services:
16
- # None for now
17
- notifications:
18
- # None for now