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 +4 -4
- data/.circleci/config.yml +43 -0
- data/HISTORY +27 -5
- data/README.md +11 -6
- data/lib/sift.rb +26 -9
- data/lib/sift/client.rb +172 -19
- data/lib/sift/version.rb +1 -1
- data/sift.gemspec +1 -0
- data/spec/unit/client_spec.rb +158 -9
- metadata +18 -4
- data/.travis.yml +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57a679adbe12a556895633060cde177cf89fcb06
|
4
|
+
data.tar.gz: d3eff77b83c2ba5696c4c391f82d592a8df7443a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
===
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
1
|
+
# Sift Ruby bindings
|
2
|
+
|
3
|
+
[](https://circleci.com/gh/SiftScience/sift-ruby)
|
2
4
|
|
3
5
|
## Requirements
|
4
6
|
|
5
|
-
* Ruby
|
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://
|
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://
|
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
|
```
|
data/lib/sift.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
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/#{
|
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/#{
|
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}
|
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}
|
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}
|
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}
|
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
|
data/lib/sift/client.rb
CHANGED
@@ -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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
198
|
-
#
|
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
|
-
|
230
|
-
|
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
|
#
|
data/lib/sift/version.rb
CHANGED
data/sift.gemspec
CHANGED
@@ -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
|
|
data/spec/unit/client_spec.rb
CHANGED
@@ -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 "
|
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
|
-
|
151
|
-
|
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 "
|
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
|
-
|
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
|
-
|
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:
|
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:
|
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.
|
175
|
+
rubygems_version: 2.5.2.3
|
162
176
|
signing_key:
|
163
177
|
specification_version: 4
|
164
178
|
summary: Sift Science Ruby API Gem
|
data/.travis.yml
DELETED
@@ -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
|