koala 3.0.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +32 -0
- data/Gemfile +5 -3
- data/changelog.md +98 -0
- data/koala.gemspec +3 -0
- data/lib/koala/api/graph_batch_api.rb +21 -8
- data/lib/koala/api/graph_error_checker.rb +3 -2
- data/lib/koala/api.rb +17 -3
- data/lib/koala/configuration.rb +7 -0
- data/lib/koala/errors.rb +25 -3
- data/lib/koala/http_service/request.rb +1 -3
- data/lib/koala/http_service/uploadable_io.rb +0 -1
- data/lib/koala/http_service.rb +15 -4
- data/lib/koala/version.rb +1 -1
- data/readme.md +33 -2
- data/spec/cases/api_spec.rb +77 -0
- data/spec/cases/error_spec.rb +20 -5
- data/spec/cases/graph_api_batch_spec.rb +69 -12
- data/spec/cases/graph_error_checker_spec.rb +29 -3
- data/spec/cases/http_service/request_spec.rb +14 -6
- data/spec/cases/http_service_spec.rb +46 -12
- data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +1 -1
- data/spec/support/koala_test.rb +3 -3
- data/spec/support/mock_http_service.rb +2 -2
- metadata +47 -40
- data/.travis.yml +0 -19
- data/lib/koala/http_service/multipart_request.rb +0 -37
- data/spec/cases/multipart_request_spec.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27591b64021ee915c13d9bcea2763739c2c86ce04d961dd5b540d964515a9905
|
4
|
+
data.tar.gz: 4a8c184674f681b193a3686b4eeb0023da0c00765d60be9694af9098014dacfd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78e4a3d670c3524211d438b8b67713f3f27cc0638aee6660b544b8b3b83a99397546a96ae826c6d613a1b6799184894c816b95008dc6083689f866357ae7ae13
|
7
|
+
data.tar.gz: 7a135a41f63d84e7e4f6c68632574eb19140aaf32ea9d9220bb2aaa4c79c50097c7ee1c957f7e9740d6b2c1141b5d24ebffdb47edd510c28bd7590f3cc09bf4f
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
name: on ruby ${{matrix.ruby}}
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
strategy:
|
11
|
+
fail-fast: false
|
12
|
+
matrix:
|
13
|
+
ruby: [2.7, "3.0", 3.1, 3.2, 3.3, head]
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- name: Checkout repository
|
17
|
+
uses: actions/checkout@v4
|
18
|
+
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{matrix.ruby}}
|
23
|
+
|
24
|
+
- name: Install dependencies
|
25
|
+
run: bundle install --jobs 4 --retry 3
|
26
|
+
|
27
|
+
- name: Specs & Coverage
|
28
|
+
uses: paambaati/codeclimate-action@v6
|
29
|
+
env:
|
30
|
+
CC_TEST_REPORTER_ID: 7af99d9225b4c14640f9ec3cb2e24d2f7103ac49417b0bd989188fb6c25f2909
|
31
|
+
with:
|
32
|
+
coverageCommand: bundle exec rspec
|
data/Gemfile
CHANGED
@@ -7,15 +7,17 @@ group :development do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
group :development, :test do
|
10
|
+
gem "psych", '< 4.0.0' # safe_load signature not compatible with older rubies
|
10
11
|
gem "rake"
|
11
12
|
gem "typhoeus" unless defined? JRUBY_VERSION
|
13
|
+
gem 'faraday-typhoeus' unless defined? JRUBY_VERSION
|
12
14
|
end
|
13
15
|
|
14
16
|
group :test do
|
15
|
-
gem "rspec",
|
16
|
-
gem "vcr"
|
17
|
+
gem "rspec", "~> 3.0", "< 3.10" # resrict rspec version until https://github.com/rspec/rspec-support/pull/537 gets merged
|
18
|
+
gem "vcr", github: 'vcr/vcr', ref: '8ced6c96e01737a418cd270e0382a8c2c6d85f7f' # needs https://github.com/vcr/vcr/pull/907 for ruby 3.1
|
17
19
|
gem "webmock"
|
18
|
-
gem "
|
20
|
+
gem "simplecov"
|
19
21
|
end
|
20
22
|
|
21
23
|
gem "jruby-openssl" if defined? JRUBY_VERSION
|
data/changelog.md
CHANGED
@@ -1,3 +1,101 @@
|
|
1
|
+
Unreleased
|
2
|
+
==========
|
3
|
+
|
4
|
+
**Key breaking changes:**
|
5
|
+
|
6
|
+
New features:
|
7
|
+
|
8
|
+
Updated features:
|
9
|
+
|
10
|
+
Removed features:
|
11
|
+
|
12
|
+
Internal improvements:
|
13
|
+
|
14
|
+
Testing improvements:
|
15
|
+
|
16
|
+
Others:
|
17
|
+
|
18
|
+
v3.6.0 (2024-06-27)
|
19
|
+
==========
|
20
|
+
|
21
|
+
Updated features:
|
22
|
+
|
23
|
+
* Add fbtrace_id, x-fb-rev, x-fb-debug to error messages and error class ([#668](https://github.com/arsduo/koala/pull/686))
|
24
|
+
* Handles the invalid JSON response from Facebook when the request's http_options[:http_component] is set to ':response' ([#689](https://github.com/arsduo/koala/pull/689))
|
25
|
+
|
26
|
+
Internal improvements:
|
27
|
+
|
28
|
+
* Require base64 for ruby 3.4 support ([#688](https://github.com/arsduo/koala/pull/688))
|
29
|
+
|
30
|
+
Testing improvements:
|
31
|
+
|
32
|
+
* Fix CI for ruby 3.4 ([#688](https://github.com/arsduo/koala/pull/688))
|
33
|
+
* Add latest rubies to CI ([#687](https://github.com/arsduo/koala/pull/687))
|
34
|
+
* Bump GHA action plugins to avoid deprecation warnings ([#689](https://github.com/arsduo/koala/pull/689))
|
35
|
+
|
36
|
+
v3.5.0 (2023-08-23)
|
37
|
+
======
|
38
|
+
|
39
|
+
Internal improvements:
|
40
|
+
* Raise ClientError instead of NoMethodError when body is 'null' ([#673](https://github.com/arsduo/koala/issues/673))
|
41
|
+
|
42
|
+
v3.4.0 (2023-01-05)
|
43
|
+
======
|
44
|
+
|
45
|
+
Updated features:
|
46
|
+
|
47
|
+
* Force use by default of HTTPS (instead of HTTP) when there is no access token.
|
48
|
+
HTTP can still be used by passing :use_ssl => false in the options hash for an api call ([#678](https://github.com/arsduo/koala/pull/678/files))
|
49
|
+
|
50
|
+
v3.3.0 (2022-09-27)
|
51
|
+
======
|
52
|
+
|
53
|
+
Updated features:
|
54
|
+
|
55
|
+
* Removed restriction on faraday < 2 ([#666](https://github.com/arsduo/koala/pull/666))
|
56
|
+
|
57
|
+
Internal improvements:
|
58
|
+
|
59
|
+
* Remove multipart hack and use default faraday multipart middleware ([#664](https://github.com/arsduo/koala/pull/664))
|
60
|
+
|
61
|
+
Testing improvements:
|
62
|
+
|
63
|
+
* Fix tests with ruby-head ([#674](https://github.com/arsduo/koala/pull/674))
|
64
|
+
* Keep supported rubies (non EOL) for CI ([#675](https://github.com/arsduo/koala/pull/675))
|
65
|
+
|
66
|
+
v3.2.0 (2022-05-27)
|
67
|
+
======
|
68
|
+
|
69
|
+
New features:
|
70
|
+
|
71
|
+
* Exposes limiting headers(`x-business-use-case-usage, x-ad-account-usage, x-app-usage`) to APIError ([#668](https://github.com/arsduo/koala/pull/668))
|
72
|
+
* Add `rate_limit_hook` configuration to get rate limiting headers (`x-business-use-case-usage, x-ad-account-usage, x-app-usage`) ([#670](https://github.com/arsduo/koala/pull/670))
|
73
|
+
|
74
|
+
Testing improvements:
|
75
|
+
|
76
|
+
* Fix builds for ruby 3.x
|
77
|
+
|
78
|
+
v3.1.0 (2022-01-18)
|
79
|
+
======
|
80
|
+
|
81
|
+
New features:
|
82
|
+
|
83
|
+
* mask_tokens config (default: true) to mask tokens in logs
|
84
|
+
|
85
|
+
Updated features:
|
86
|
+
|
87
|
+
* Log before and after sending request
|
88
|
+
|
89
|
+
Internal improvements:
|
90
|
+
|
91
|
+
* Lock Faraday to < 2
|
92
|
+
* Compatibility with ruby 3.x
|
93
|
+
|
94
|
+
Testing improvements:
|
95
|
+
|
96
|
+
* Use Github actions for CI
|
97
|
+
* Run CI on latest rubies
|
98
|
+
|
1
99
|
v3.0.0 (2017-03-17)
|
2
100
|
======
|
3
101
|
|
data/koala.gemspec
CHANGED
@@ -24,6 +24,9 @@ Gem::Specification.new do |gem|
|
|
24
24
|
gem.required_ruby_version = '>= 2.1'
|
25
25
|
|
26
26
|
gem.add_runtime_dependency("faraday")
|
27
|
+
gem.add_runtime_dependency("faraday-multipart")
|
27
28
|
gem.add_runtime_dependency("addressable")
|
28
29
|
gem.add_runtime_dependency("json", ">= 1.8")
|
30
|
+
gem.add_runtime_dependency("rexml")
|
31
|
+
gem.add_runtime_dependency("base64")
|
29
32
|
end
|
@@ -53,7 +53,16 @@ module Koala
|
|
53
53
|
end
|
54
54
|
|
55
55
|
original_api.graph_call("/", args, "post", http_options) do |response|
|
56
|
-
raise bad_response if response.nil?
|
56
|
+
raise bad_response('Facebook returned an empty body') if response.nil?
|
57
|
+
|
58
|
+
# when http_component is set we receive Koala::Http_service response object
|
59
|
+
# from graph_call so this needs to be parsed
|
60
|
+
# as generate_results method handles only JSON response
|
61
|
+
if http_options[:http_component] && http_options[:http_component] == :response
|
62
|
+
response = json_body(response.body)
|
63
|
+
|
64
|
+
raise bad_response('Facebook returned an invalid body') unless response.is_a?(Array)
|
65
|
+
end
|
57
66
|
|
58
67
|
batch_results += generate_results(response, batch)
|
59
68
|
end
|
@@ -81,9 +90,9 @@ module Koala
|
|
81
90
|
end
|
82
91
|
end
|
83
92
|
|
84
|
-
def bad_response
|
93
|
+
def bad_response(message)
|
85
94
|
# Facebook sometimes reportedly returns an empty body at times
|
86
|
-
BadFacebookResponse.new(200,
|
95
|
+
BadFacebookResponse.new(200, '', message)
|
87
96
|
end
|
88
97
|
|
89
98
|
def result_from_response(response, options)
|
@@ -123,14 +132,17 @@ module Koala
|
|
123
132
|
JSON.dump calls
|
124
133
|
end
|
125
134
|
|
126
|
-
def json_body(
|
127
|
-
|
128
|
-
|
129
|
-
JSON.parse(
|
135
|
+
def json_body(body)
|
136
|
+
return if body.nil?
|
137
|
+
|
138
|
+
JSON.parse(body)
|
139
|
+
rescue JSON::ParserError => e
|
140
|
+
Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{body}")
|
141
|
+
nil
|
130
142
|
end
|
131
143
|
|
132
144
|
def desired_component(component:, response:, headers:)
|
133
|
-
result = Koala::HTTPService::Response.new(response['
|
145
|
+
result = Koala::HTTPService::Response.new(response['code'], response['body'], headers)
|
134
146
|
|
135
147
|
# Get the HTTP component they want
|
136
148
|
case component
|
@@ -138,6 +150,7 @@ module Koala
|
|
138
150
|
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
139
151
|
when :headers then headers
|
140
152
|
# (see note in regular api method about JSON parsing)
|
153
|
+
when :response then result
|
141
154
|
else GraphCollection.evaluate(result, original_api)
|
142
155
|
end
|
143
156
|
end
|
@@ -17,7 +17,7 @@ module Koala
|
|
17
17
|
|
18
18
|
# Facebook can return debug information in the response headers -- see
|
19
19
|
# https://developers.facebook.com/docs/graph-api/using-graph-api#bugdebug
|
20
|
-
DEBUG_HEADERS = [
|
20
|
+
DEBUG_HEADERS = %w[x-fb-debug x-fb-rev x-fb-trace-id x-business-use-case-usage x-ad-account-usage x-app-usage]
|
21
21
|
|
22
22
|
def error_if_appropriate
|
23
23
|
if http_status >= 400
|
@@ -61,7 +61,8 @@ module Koala
|
|
61
61
|
# Normally, we start with the response body. If it isn't valid JSON, we start with an empty
|
62
62
|
# hash and fill it with error data.
|
63
63
|
@response_hash ||= begin
|
64
|
-
JSON.parse(body)
|
64
|
+
parsed_body = JSON.parse(body)
|
65
|
+
parsed_body.is_a?(Hash) ? parsed_body : {}
|
65
66
|
rescue JSON::ParserError
|
66
67
|
{}
|
67
68
|
end
|
data/lib/koala/api.rb
CHANGED
@@ -13,14 +13,16 @@ module Koala
|
|
13
13
|
# signed by default, unless you pass appsecret_proof:
|
14
14
|
# false as an option to the API call. (See
|
15
15
|
# https://developers.facebook.com/docs/graph-api/securing-requests/)
|
16
|
+
# @param [Block] rate_limit_hook block called with limits received in facebook response headers
|
16
17
|
# @note If no access token is provided, you can only access some public information.
|
17
18
|
# @return [Koala::Facebook::API] the API client
|
18
|
-
def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret)
|
19
|
+
def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret, rate_limit_hook = Koala.config.rate_limit_hook)
|
19
20
|
@access_token = access_token
|
20
21
|
@app_secret = app_secret
|
22
|
+
@rate_limit_hook = rate_limit_hook
|
21
23
|
end
|
22
24
|
|
23
|
-
attr_reader :access_token, :app_secret
|
25
|
+
attr_reader :access_token, :app_secret, :rate_limit_hook
|
24
26
|
|
25
27
|
include GraphAPIMethods
|
26
28
|
|
@@ -58,6 +60,18 @@ module Koala
|
|
58
60
|
API::GraphCollection.evaluate(response, self)
|
59
61
|
end
|
60
62
|
|
63
|
+
if rate_limit_hook
|
64
|
+
limits = %w(x-business-use-case-usage x-ad-account-usage x-app-usage).each_with_object({}) do |key, hash|
|
65
|
+
value = response.headers.fetch(key, nil)
|
66
|
+
next unless value
|
67
|
+
hash[key] = JSON.parse(response.headers[key])
|
68
|
+
rescue JSON::ParserError => e
|
69
|
+
Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{value}")
|
70
|
+
end
|
71
|
+
|
72
|
+
rate_limit_hook.call(limits) if limits.keys.any?
|
73
|
+
end
|
74
|
+
|
61
75
|
# now process as appropriate for the given call (get picture header, etc.)
|
62
76
|
post_processing ? post_processing.call(desired_data) : desired_data
|
63
77
|
end
|
@@ -102,7 +116,7 @@ module Koala
|
|
102
116
|
args = sanitize_request_parameters(args) unless preserve_form_arguments?(options)
|
103
117
|
|
104
118
|
# add a leading / if needed...
|
105
|
-
path = "/#{path}" unless path =~ /^\//
|
119
|
+
path = "/#{path}" unless path.to_s =~ /^\//
|
106
120
|
|
107
121
|
# make the request via the provided service
|
108
122
|
result = Koala.make_request(path, args, verb, options)
|
data/lib/koala/configuration.rb
CHANGED
@@ -28,6 +28,12 @@ class Koala::Configuration
|
|
28
28
|
# The server to use when constructing dialog URLs.
|
29
29
|
attr_accessor :dialog_host
|
30
30
|
|
31
|
+
# Whether or not to mask tokens
|
32
|
+
attr_accessor :mask_tokens
|
33
|
+
|
34
|
+
# Called with the info for the rate limits in the response header
|
35
|
+
attr_accessor :rate_limit_hook
|
36
|
+
|
31
37
|
# Certain Facebook services (beta, video) require you to access different
|
32
38
|
# servers. If you're using your own servers, for instance, for a proxy,
|
33
39
|
# you can change both the matcher (what value to change when updating the URL) and the
|
@@ -45,5 +51,6 @@ class Koala::Configuration
|
|
45
51
|
Koala::HTTPService::DEFAULT_SERVERS.each_pair do |key, value|
|
46
52
|
self.public_send("#{key}=", value)
|
47
53
|
end
|
54
|
+
self.mask_tokens = true
|
48
55
|
end
|
49
56
|
end
|
data/lib/koala/errors.rb
CHANGED
@@ -22,8 +22,12 @@ module Koala
|
|
22
22
|
:fb_error_user_msg,
|
23
23
|
:fb_error_user_title,
|
24
24
|
:fb_error_trace_id,
|
25
|
+
:fb_error_debug_trace_id,
|
25
26
|
:fb_error_debug,
|
26
|
-
:fb_error_rev
|
27
|
+
:fb_error_rev,
|
28
|
+
:fb_buc_usage,
|
29
|
+
:fb_ada_usage,
|
30
|
+
:fb_app_usage
|
27
31
|
|
28
32
|
# Create a new API Error
|
29
33
|
#
|
@@ -62,13 +66,17 @@ module Koala
|
|
62
66
|
self.fb_error_message = error_info["message"]
|
63
67
|
self.fb_error_user_msg = error_info["error_user_msg"]
|
64
68
|
self.fb_error_user_title = error_info["error_user_title"]
|
69
|
+
self.fb_error_trace_id = error_info["fbtrace_id"]
|
65
70
|
|
66
|
-
self.
|
71
|
+
self.fb_error_debug_trace_id = error_info["x-fb-trace-id"]
|
67
72
|
self.fb_error_debug = error_info["x-fb-debug"]
|
68
73
|
self.fb_error_rev = error_info["x-fb-rev"]
|
74
|
+
self.fb_buc_usage = json_parse_for(error_info, "x-business-use-case-usage")
|
75
|
+
self.fb_ada_usage = json_parse_for(error_info, "x-ad-account-usage")
|
76
|
+
self.fb_app_usage = json_parse_for(error_info, "x-app-usage")
|
69
77
|
|
70
78
|
error_array = []
|
71
|
-
%w(type code error_subcode message error_user_title error_user_msg x-fb-trace-id).each do |key|
|
79
|
+
%w(type code error_subcode message error_user_title error_user_msg fbtrace_id x-fb-trace-id x-fb-debug x-fb-rev).each do |key|
|
72
80
|
error_array << "#{key}: #{error_info[key]}" if error_info[key]
|
73
81
|
end
|
74
82
|
|
@@ -82,6 +90,20 @@ module Koala
|
|
82
90
|
|
83
91
|
super(message)
|
84
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# refs: https://developers.facebook.com/docs/graph-api/overview/rate-limiting/#headers
|
97
|
+
# NOTE: The header will contain a JSON-formatted string that describes current application rate limit usage.
|
98
|
+
def json_parse_for(error_info, key)
|
99
|
+
string = error_info[key]
|
100
|
+
return if string.nil?
|
101
|
+
|
102
|
+
JSON.parse(string)
|
103
|
+
rescue JSON::ParserError => e
|
104
|
+
Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{string}")
|
105
|
+
nil
|
106
|
+
end
|
85
107
|
end
|
86
108
|
|
87
109
|
# Facebook returned an invalid response body
|
@@ -106,9 +106,7 @@ module Koala
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def add_ssl_options(opts)
|
109
|
-
# require https
|
110
|
-
return opts unless raw_args["access_token"]
|
111
|
-
|
109
|
+
# require https by default (can be overriden by explicitly setting other SSL options)
|
112
110
|
{
|
113
111
|
use_ssl: true,
|
114
112
|
ssl: {verify: true}.merge(opts[:ssl] || {})
|
data/lib/koala/http_service.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'faraday'
|
2
|
-
require '
|
2
|
+
require 'faraday/multipart' unless defined? Faraday::FilePart # hack for faraday < 1.9 to avoid warnings
|
3
3
|
require 'koala/http_service/uploadable_io'
|
4
4
|
require 'koala/http_service/response'
|
5
5
|
require 'koala/http_service/request'
|
@@ -19,7 +19,7 @@ module Koala
|
|
19
19
|
# We encode requests in a Facebook-compatible multipart request,
|
20
20
|
# and use whichever adapter has been configured for this application.
|
21
21
|
DEFAULT_MIDDLEWARE = Proc.new do |builder|
|
22
|
-
builder.
|
22
|
+
builder.request :multipart
|
23
23
|
builder.request :url_encoded
|
24
24
|
builder.adapter Faraday.default_adapter
|
25
25
|
end
|
@@ -49,6 +49,18 @@ module Koala
|
|
49
49
|
# set up our Faraday connection
|
50
50
|
conn = Faraday.new(request.server, faraday_options(request.options), &(faraday_middleware || DEFAULT_MIDDLEWARE))
|
51
51
|
|
52
|
+
filtered_args = request.raw_args.dup.transform_keys(&:to_s)
|
53
|
+
|
54
|
+
if Koala.config.mask_tokens
|
55
|
+
%w(access_token input_token).each do |arg_token|
|
56
|
+
if (token = filtered_args[arg_token])
|
57
|
+
filtered_args[arg_token] = token[0, 10] + '*****' + token[-5, 5]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Koala::Utils.debug "STARTED => #{request.verb.upcase}: #{request.path} params: #{filtered_args.inspect}"
|
63
|
+
|
52
64
|
if request.verb == "post" && request.json?
|
53
65
|
# JSON requires a bit more handling
|
54
66
|
# remember, all non-GET requests are turned into POSTs, so this covers everything but GETs
|
@@ -62,8 +74,7 @@ module Koala
|
|
62
74
|
response = conn.send(request.verb, request.path, request.post_args)
|
63
75
|
end
|
64
76
|
|
65
|
-
#
|
66
|
-
Koala::Utils.debug "#{request.verb.upcase}: #{request.path} params: #{request.raw_args.inspect}"
|
77
|
+
Koala::Utils.debug "FINISHED => #{request.verb.upcase}: #{request.path} params: #{filtered_args.inspect}"
|
67
78
|
Koala::HTTPService::Response.new(response.status.to_i, response.body, response.headers)
|
68
79
|
end
|
69
80
|
|
data/lib/koala/version.rb
CHANGED
data/readme.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Koala [![Version](https://img.shields.io/gem/v/koala.svg)](https://rubygems.org/gems/koala) [![
|
1
|
+
Koala [![Version](https://img.shields.io/gem/v/koala.svg)](https://rubygems.org/gems/koala) [![Build Status](https://img.shields.io/travis/arsduo/koala.svg)](http://travis-ci.org/arsduo/koala) [![Code Climate](https://img.shields.io/codeclimate/coverage-letter/arsduo/koala.svg)](https://codeclimate.com/github/arsduo/koala) [![Code Coverage](https://img.shields.io/codeclimate/coverage/arsduo/koala.svg)](https://codeclimate.com/github/arsduo/koala)
|
2
2
|
====
|
3
|
-
[Koala](http://github.com/arsduo/koala) is a Facebook library for Ruby, supporting the Graph API (including the batch requests and photo uploads), realtime updates, test users, and OAuth validation. We wrote Koala with four goals:
|
3
|
+
[Koala](http://github.com/arsduo/koala) is a Facebook library for Ruby, supporting the Graph API (including the batch requests and photo uploads), the Marketing API, the Atlas API, realtime updates, test users, and OAuth validation. We wrote Koala with four goals:
|
4
4
|
|
5
5
|
* Lightweight: Koala should be as light and simple as Facebook’s own libraries, providing API accessors and returning simple JSON.
|
6
6
|
* Fast: Koala should, out of the box, be quick. Out of the box, we use Facebook's faster read-only servers when possible and if available, the Typhoeus gem to make snappy Facebook requests. Of course, that brings us to our next topic:
|
@@ -177,6 +177,37 @@ Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
|
|
177
177
|
```
|
178
178
|
For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
|
179
179
|
|
180
|
+
Rate limits
|
181
|
+
-----------
|
182
|
+
|
183
|
+
We support Facebook rate limit informations as defined here: [https://developers.facebook.com/docs/graph-api/overview/rate-limiting/](https://developers.facebook.com/docs/graph-api/overview/rate-limiting/)
|
184
|
+
|
185
|
+
The information is available either via the `Facebook::APIError`:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
error.fb_buc_usage
|
189
|
+
error.fb_ada_usage
|
190
|
+
error.fb_app_usage
|
191
|
+
```
|
192
|
+
|
193
|
+
Or with the rate_limit_hook:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# App level configuration
|
197
|
+
|
198
|
+
Koala.configure do |config|
|
199
|
+
config.rate_limit_hook = ->(limits) {
|
200
|
+
limits["x-app-usage"] # {"call_count"=>0, "total_cputime"=>0, "total_time"=>0}
|
201
|
+
limits["x-ad-account-usage"] # {"acc_id_util_pct"=>9.67}
|
202
|
+
limits["x-business-use-case-usage"] # {"123456789012345"=>[{"type"=>"messenger", "call_count"=>1, "total_cputime"=>1, "total_time"=>1, "estimated_time_to_regain_access"=>0}]}
|
203
|
+
}
|
204
|
+
end
|
205
|
+
|
206
|
+
# Per API configuration
|
207
|
+
|
208
|
+
Koala::Facebook::API.new('', '', ->(limits) {})
|
209
|
+
```
|
210
|
+
|
180
211
|
Test Users
|
181
212
|
----------
|
182
213
|
|
data/spec/cases/api_spec.rb
CHANGED
@@ -282,4 +282,81 @@ describe "Koala::Facebook::API" do
|
|
282
282
|
expect(@service.graph_call('anything')).to be_falsey
|
283
283
|
end
|
284
284
|
end
|
285
|
+
|
286
|
+
describe "Rate limit hook" do
|
287
|
+
it "is called when x-business-use-case-usage header is present" do
|
288
|
+
api = Koala::Facebook::API.new('', '', ->(limits) {
|
289
|
+
expect(limits["x-business-use-case-usage"]).to eq({"123456789012345"=>[{"type"=>"messenger", "call_count"=>1, "total_cputime"=>1, "total_time"=>1, "estimated_time_to_regain_access"=>0}]})
|
290
|
+
})
|
291
|
+
|
292
|
+
result = {"a" => 2}
|
293
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, { "x-business-use-case-usage" => "{\"123456789012345\":[{\"type\":\"messenger\",\"call_count\":1,\"total_cputime\":1,\"total_time\":1,\"estimated_time_to_regain_access\":0}]}" })
|
294
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
295
|
+
|
296
|
+
api.graph_call('anything')
|
297
|
+
end
|
298
|
+
|
299
|
+
it "is called when x-ad-account-usage header is present" do
|
300
|
+
api = Koala::Facebook::API.new('', '', ->(limits) {
|
301
|
+
expect(limits["x-ad-account-usage"]).to eq({"acc_id_util_pct"=>9.67})
|
302
|
+
})
|
303
|
+
|
304
|
+
result = {"a" => 2}
|
305
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, { "x-ad-account-usage" => "{\"acc_id_util_pct\":9.67}" })
|
306
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
307
|
+
|
308
|
+
api.graph_call('anything')
|
309
|
+
end
|
310
|
+
|
311
|
+
it "is called when x-app-usage header is present" do
|
312
|
+
api = Koala::Facebook::API.new('', '', ->(limits) {
|
313
|
+
expect(limits["x-app-usage"]).to eq({"call_count"=>0, "total_cputime"=>0, "total_time"=>0})
|
314
|
+
})
|
315
|
+
|
316
|
+
result = {"a" => 2}
|
317
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, { "x-app-usage" => "{\"call_count\":0,\"total_cputime\":0,\"total_time\":0}" })
|
318
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
319
|
+
|
320
|
+
api.graph_call('anything')
|
321
|
+
end
|
322
|
+
|
323
|
+
it "isn't called if none of the rate limit header is present" do
|
324
|
+
rate_limit_hook_called = false
|
325
|
+
|
326
|
+
api = Koala::Facebook::API.new('', '', ->(limits) {
|
327
|
+
rate_limit_hook_called = true
|
328
|
+
})
|
329
|
+
|
330
|
+
result = {"a" => 2}
|
331
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, {})
|
332
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
333
|
+
|
334
|
+
api.graph_call('anything')
|
335
|
+
|
336
|
+
expect(rate_limit_hook_called).to be(false)
|
337
|
+
end
|
338
|
+
|
339
|
+
it "isn't called if no rate limit hook is defined" do
|
340
|
+
api = Koala::Facebook::API.new('', '', ->(limits) {
|
341
|
+
#noop
|
342
|
+
})
|
343
|
+
|
344
|
+
result = {"a" => 2}
|
345
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, { "x-ad-account-usage" => "{\"acc_id_util_pct\"9.67}"})
|
346
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
347
|
+
|
348
|
+
expect(Koala::Utils.logger).to receive(:error).with(/JSON::ParserError:.*unexpected token at '{"acc_id_util_pct"9.67}' while parsing x-ad-account-usage = {"acc_id_util_pct"9.67}/)
|
349
|
+
api.graph_call('anything')
|
350
|
+
end
|
351
|
+
|
352
|
+
it "logs an error if the rate limit header can't be properly parsed" do
|
353
|
+
api = Koala::Facebook::API.new('', '', nil)
|
354
|
+
|
355
|
+
result = {"a" => 2}
|
356
|
+
response = Koala::HTTPService::Response.new(200, result.to_json, {})
|
357
|
+
allow(Koala).to receive(:make_request).and_return(response)
|
358
|
+
|
359
|
+
api.graph_call('anything')
|
360
|
+
end
|
361
|
+
end
|
285
362
|
end
|
data/spec/cases/error_spec.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
BUC_USAGE_JSON = "{\"123456789012345\":[{\"type\":\"messenger\",\"call_count\":1,\"total_cputime\":1,\"total_time\":1,\"estimated_time_to_regain_access\":0}]}"
|
4
|
+
ADA_USAGE_JSON = "{\"acc_id_util_pct\":9.67}"
|
5
|
+
APP_USAGE_JSON = "{\"call_count\":0,\"total_cputime\":0,\"total_time\":0}"
|
6
|
+
|
3
7
|
describe Koala::Facebook::APIError do
|
4
8
|
it "is a Koala::KoalaError" do
|
5
9
|
expect(Koala::Facebook::APIError.new(nil, nil)).to be_a(Koala::KoalaError)
|
@@ -30,9 +34,13 @@ describe Koala::Facebook::APIError do
|
|
30
34
|
'error_subcode' => 'subcode',
|
31
35
|
'error_user_msg' => 'error user message',
|
32
36
|
'error_user_title' => 'error user title',
|
33
|
-
'
|
37
|
+
'fbtrace_id' => 'fb trace id',
|
38
|
+
'x-fb-trace-id' => 'x-fb trace id',
|
34
39
|
'x-fb-debug' => 'fb debug token',
|
35
|
-
'x-fb-rev' => 'fb revision'
|
40
|
+
'x-fb-rev' => 'fb revision',
|
41
|
+
'x-business-use-case-usage' => BUC_USAGE_JSON,
|
42
|
+
'x-ad-account-usage' => ADA_USAGE_JSON,
|
43
|
+
'x-app-usage' => APP_USAGE_JSON
|
36
44
|
}
|
37
45
|
Koala::Facebook::APIError.new(400, '', error_info)
|
38
46
|
}
|
@@ -45,8 +53,12 @@ describe Koala::Facebook::APIError do
|
|
45
53
|
:fb_error_user_msg => 'error user message',
|
46
54
|
:fb_error_user_title => 'error user title',
|
47
55
|
:fb_error_trace_id => 'fb trace id',
|
56
|
+
:fb_error_debug_trace_id => 'x-fb trace id',
|
48
57
|
:fb_error_debug => 'fb debug token',
|
49
|
-
:fb_error_rev => 'fb revision'
|
58
|
+
:fb_error_rev => 'fb revision',
|
59
|
+
:fb_buc_usage => JSON.parse(BUC_USAGE_JSON),
|
60
|
+
:fb_ada_usage => JSON.parse(ADA_USAGE_JSON),
|
61
|
+
:fb_app_usage => JSON.parse(APP_USAGE_JSON)
|
50
62
|
}.each_pair do |accessor, value|
|
51
63
|
it "sets #{accessor} to #{value}" do
|
52
64
|
expect(error.send(accessor)).to eq(value)
|
@@ -54,7 +66,7 @@ describe Koala::Facebook::APIError do
|
|
54
66
|
end
|
55
67
|
|
56
68
|
it "sets the error message appropriately" do
|
57
|
-
expect(error.message).to eq("type: type, code: 1, error_subcode: subcode, message: message, error_user_title: error user title, error_user_msg: error user message, x-fb-trace-id: fb trace id [HTTP 400]")
|
69
|
+
expect(error.message).to eq("type: type, code: 1, error_subcode: subcode, message: message, error_user_title: error user title, error_user_msg: error user message, fbtrace_id: fb trace id, x-fb-trace-id: x-fb trace id, x-fb-debug: fb debug token, x-fb-rev: fb revision [HTTP 400]")
|
58
70
|
end
|
59
71
|
end
|
60
72
|
|
@@ -76,7 +88,10 @@ describe Koala::Facebook::APIError do
|
|
76
88
|
:fb_error_code => 1,
|
77
89
|
:fb_error_subcode => 'subcode',
|
78
90
|
:fb_error_user_msg => 'error user message',
|
79
|
-
:fb_error_user_title => 'error user title'
|
91
|
+
:fb_error_user_title => 'error user title',
|
92
|
+
:fb_buc_usage => nil,
|
93
|
+
:fb_ada_usage => nil,
|
94
|
+
:fb_app_usage => nil
|
80
95
|
}.each_pair do |accessor, value|
|
81
96
|
expect(error.send(accessor)).to eq(value)
|
82
97
|
end
|
@@ -383,28 +383,85 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
383
383
|
end
|
384
384
|
end
|
385
385
|
|
386
|
-
describe
|
387
|
-
|
386
|
+
describe 'processing the request' do
|
387
|
+
let(:response_status) { 203 }
|
388
|
+
let(:response_body) { '{\"id\":\"1234\"}'.gsub('\\', '') }
|
389
|
+
let(:response_headers) { { 'Content-Type' => 'text/javascript; charset=UTF-8' } }
|
390
|
+
|
391
|
+
it 'returns the result headers as a hash if http_component is headers' do
|
388
392
|
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
|
389
393
|
result = @api.batch do |batch_api|
|
390
394
|
batch_api.get_object(KoalaTest.user1, {}, :http_component => :headers)
|
391
395
|
end
|
392
|
-
expect(
|
396
|
+
expect(response_headers).to eq(result[0])
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'returns the complete response if http_component is response' do
|
400
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
|
401
|
+
result = @api.batch do |batch_api|
|
402
|
+
batch_api.get_object(KoalaTest.user1, {}, :http_component => :response)
|
403
|
+
end
|
404
|
+
|
405
|
+
expect(response_status).to eq(result[0].status)
|
406
|
+
expect(response_body).to eq(result[0].body)
|
407
|
+
expect(response_headers).to eq(result[0].headers)
|
393
408
|
end
|
394
409
|
|
395
|
-
describe
|
396
|
-
it
|
397
|
-
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(500,
|
410
|
+
describe 'if it errors' do
|
411
|
+
it 'raises an APIError if the response is not 200' do
|
412
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(500, '[]', {}))
|
398
413
|
expect {
|
399
|
-
Koala::Facebook::API.new(
|
414
|
+
Koala::Facebook::API.new('foo').batch { |batch_api| batch_api.get_object('me') }
|
400
415
|
}.to raise_exception(Koala::Facebook::APIError)
|
401
416
|
end
|
402
417
|
|
403
|
-
it
|
404
|
-
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200,
|
418
|
+
it 'raises a BadFacebookResponse if the body is empty' do
|
419
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '', {}))
|
405
420
|
expect {
|
406
|
-
Koala::Facebook::API.new(
|
407
|
-
}.to raise_exception(Koala::Facebook::BadFacebookResponse)
|
421
|
+
Koala::Facebook::API.new('foo').batch { |batch_api| batch_api.get_object('me') }
|
422
|
+
}.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an empty body \[HTTP 200\]/)
|
423
|
+
end
|
424
|
+
|
425
|
+
describe 'handle invalid body errors' do
|
426
|
+
describe 'with http_component set to :response' do
|
427
|
+
it 'raises a BadFacebookResponse if the body is non-empty, non-array' do
|
428
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '200', {}))
|
429
|
+
expect {
|
430
|
+
Koala::Facebook::API.new('foo').batch(http_component: :response) do |batch_api|
|
431
|
+
batch_api.get_object('me')
|
432
|
+
end
|
433
|
+
}.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'raises a BadFacebookResponse if the body is invalid JSON' do
|
437
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"\"id\":\1234\"}"}', {}))
|
438
|
+
expect {
|
439
|
+
Koala::Facebook::API.new('foo').batch(http_component: :response) do |batch_api|
|
440
|
+
batch_api.get_object('me')
|
441
|
+
end
|
442
|
+
}.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
%i[headers status].each do |component|
|
447
|
+
describe "with http_component set to #{component}" do
|
448
|
+
it 'should not raise a BadFacebookResponse if the body is non-empty, non-array' do
|
449
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '200', {}))
|
450
|
+
expect {
|
451
|
+
Koala::Facebook::API.new('foo').batch(http_component: component) { |batch_api| batch_api.get_object('me') }
|
452
|
+
}.not_to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
|
453
|
+
end
|
454
|
+
|
455
|
+
it 'should not raise a BadFacebookResponse if the body is invalid JSON' do
|
456
|
+
allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"\"id\":\1234\"}"}', {}))
|
457
|
+
expect {
|
458
|
+
Koala::Facebook::API.new('foo').batch(http_component: component) do |batch_api|
|
459
|
+
batch_api.get_object('me')
|
460
|
+
end
|
461
|
+
}.not_to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
408
465
|
end
|
409
466
|
|
410
467
|
context "with error info" do
|
@@ -593,7 +650,7 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
|
|
593
650
|
hash_including(@other_access_token_args.dup),
|
594
651
|
anything,
|
595
652
|
anything
|
596
|
-
).and_return(Koala::HTTPService::Response.new(200, "",
|
653
|
+
).and_return(Koala::HTTPService::Response.new(200, "", {}))
|
597
654
|
|
598
655
|
# Page the collection
|
599
656
|
app_event_types.next_page
|
@@ -11,7 +11,10 @@ module Koala
|
|
11
11
|
expect(GraphErrorChecker::DEBUG_HEADERS).to match_array([
|
12
12
|
"x-fb-rev",
|
13
13
|
"x-fb-debug",
|
14
|
-
"x-fb-trace-id"
|
14
|
+
"x-fb-trace-id",
|
15
|
+
"x-business-use-case-usage",
|
16
|
+
"x-ad-account-usage",
|
17
|
+
"x-app-usage"
|
15
18
|
])
|
16
19
|
end
|
17
20
|
|
@@ -60,6 +63,12 @@ module Koala
|
|
60
63
|
expect(error.response_body).to eq(body)
|
61
64
|
end
|
62
65
|
|
66
|
+
it "returns a ClientError if the body is null" do
|
67
|
+
body.replace("null")
|
68
|
+
expect(error).to be_a(ClientError)
|
69
|
+
expect(error.response_body).to eq(body)
|
70
|
+
end
|
71
|
+
|
63
72
|
it "adds error data from the body" do
|
64
73
|
error_data = {
|
65
74
|
"type" => "FB error type",
|
@@ -67,10 +76,12 @@ module Koala
|
|
67
76
|
"error_subcode" => "FB error subcode",
|
68
77
|
"message" => "An error occurred!",
|
69
78
|
"error_user_msg" => "A user msg",
|
70
|
-
"error_user_title" => "usr title"
|
79
|
+
"error_user_title" => "usr title",
|
80
|
+
"fbtrace_id" => "fbtrace_id"
|
71
81
|
}
|
72
82
|
body.replace({"error" => error_data}.to_json)
|
73
83
|
|
84
|
+
expect(error.fb_error_trace_id).to eq(error_data["fbtrace_id"])
|
74
85
|
expect(error.fb_error_type).to eq(error_data["type"])
|
75
86
|
expect(error.fb_error_code).to eq(error_data["code"])
|
76
87
|
expect(error.fb_error_subcode).to eq(error_data["error_subcode"])
|
@@ -84,10 +95,25 @@ module Koala
|
|
84
95
|
"x-fb-debug" => double("fb debug"),
|
85
96
|
"x-fb-rev" => double("fb rev"),
|
86
97
|
"x-fb-trace-id" => double("fb trace id"),
|
98
|
+
"x-business-use-case-usage" => { 'a' => 1, 'b' => 2 }.to_json,
|
99
|
+
"x-ad-account-usage" => { 'c' => 3, 'd' => 4 }.to_json,
|
100
|
+
"x-app-usage" => { 'e' => 5, 'f' => 6 }.to_json
|
87
101
|
)
|
88
|
-
expect(error.
|
102
|
+
expect(error.fb_error_debug_trace_id).to eq(headers["x-fb-trace-id"])
|
89
103
|
expect(error.fb_error_debug).to eq(headers["x-fb-debug"])
|
90
104
|
expect(error.fb_error_rev).to eq(headers["x-fb-rev"])
|
105
|
+
expect(error.fb_buc_usage).to eq({ 'a' => 1, 'b' => 2 })
|
106
|
+
expect(error.fb_ada_usage).to eq({ 'c' => 3, 'd' => 4 })
|
107
|
+
expect(error.fb_app_usage).to eq({ 'e' => 5, 'f' => 6 })
|
108
|
+
end
|
109
|
+
|
110
|
+
it "logs if one of the FB debug headers can't be parsed" do
|
111
|
+
headers.merge!(
|
112
|
+
"x-app-usage" => '{invalid:json}'
|
113
|
+
)
|
114
|
+
|
115
|
+
expect(Koala::Utils.logger).to receive(:error).with(/JSON::ParserError:.*unexpected token at '{invalid:json}' while parsing x-app-usage = {invalid:json}/)
|
116
|
+
expect(error.fb_app_usage).to eq(nil)
|
91
117
|
end
|
92
118
|
|
93
119
|
context "it returns an AuthenticationError" do
|
@@ -176,9 +176,9 @@ module Koala
|
|
176
176
|
end
|
177
177
|
|
178
178
|
describe "ssl options" do
|
179
|
-
it "
|
179
|
+
it "includes the default SSL options even if there's no access token" do
|
180
180
|
request_options = Request.new(path: path, args: args.delete_if {|k, _v| k == "access_token"}, verb: verb, options: options).options
|
181
|
-
expect(request_options).
|
181
|
+
expect(request_options).to include(use_ssl: true, ssl: {verify: true})
|
182
182
|
end
|
183
183
|
|
184
184
|
context "if there is an access_token" do
|
@@ -193,9 +193,9 @@ module Koala
|
|
193
193
|
end
|
194
194
|
|
195
195
|
it "overrides default SSL options with what's provided" do
|
196
|
-
new_ssl_options = {verify: :dunno}
|
197
|
-
request_options = Request.new(path: path, args: args, verb: verb, options: options.merge(
|
198
|
-
expect(request_options
|
196
|
+
new_ssl_options = {use_ssl: false, ssl:{verify: :dunno}}
|
197
|
+
request_options = Request.new(path: path, args: args, verb: verb, options: options.merge(new_ssl_options)).options
|
198
|
+
expect(request_options).to include(new_ssl_options)
|
199
199
|
end
|
200
200
|
end
|
201
201
|
end
|
@@ -211,9 +211,17 @@ module Koala
|
|
211
211
|
expect(request.server).to eq("https://foo")
|
212
212
|
end
|
213
213
|
|
214
|
-
context "if
|
214
|
+
context "if there is no access token" do
|
215
215
|
let(:args) { {"a" => "b"} }
|
216
216
|
|
217
|
+
it "uses https" do
|
218
|
+
expect(request.server).to eq("https://graph.facebook.com")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context "if options[:use_ssl] is false" do
|
223
|
+
let(:options) { {use_ssl: false} }
|
224
|
+
|
217
225
|
it "uses http" do
|
218
226
|
expect(request.server).to eq("http://graph.facebook.com")
|
219
227
|
end
|
@@ -39,8 +39,7 @@ describe Koala::HTTPService do
|
|
39
39
|
|
40
40
|
it "adds the right default middleware" do
|
41
41
|
Koala::HTTPService::DEFAULT_MIDDLEWARE.call(builder)
|
42
|
-
expect(builder.requests).to eq([:url_encoded])
|
43
|
-
expect(builder.uses).to eq([Koala::HTTPService::MultipartRequest])
|
42
|
+
expect(builder.requests).to eq([:multipart, :url_encoded])
|
44
43
|
expect(builder.adapters).to eq([Faraday.default_adapter])
|
45
44
|
end
|
46
45
|
end
|
@@ -122,7 +121,8 @@ describe Koala::HTTPService do
|
|
122
121
|
|
123
122
|
let(:verb) { "get" }
|
124
123
|
let(:options) { {} }
|
125
|
-
let(:
|
124
|
+
let(:args) { {"an" => :arg } }
|
125
|
+
let(:request) { Koala::HTTPService::Request.new(path: "/foo", verb: verb, args: args, options: options) }
|
126
126
|
|
127
127
|
shared_examples_for :making_a_request do
|
128
128
|
before :each do
|
@@ -153,7 +153,8 @@ describe Koala::HTTPService do
|
|
153
153
|
|
154
154
|
it "logs verb, url and params to debug" do
|
155
155
|
log_message = "#{verb.upcase}: #{request.path} params: #{request.raw_args.inspect}"
|
156
|
-
expect(Koala::Utils.logger).to receive(:debug).with(log_message)
|
156
|
+
expect(Koala::Utils.logger).to receive(:debug).with("STARTED => #{log_message}")
|
157
|
+
expect(Koala::Utils.logger).to receive(:debug).with("FINISHED => #{log_message}")
|
157
158
|
|
158
159
|
Koala::HTTPService.make_request(request)
|
159
160
|
end
|
@@ -192,18 +193,16 @@ describe Koala::HTTPService do
|
|
192
193
|
|
193
194
|
it "uses the default builder block if HTTPService.faraday_middleware block is not defined" do
|
194
195
|
block = Proc.new { |builder|
|
195
|
-
builder.
|
196
|
+
builder.request :multipart
|
196
197
|
builder.request :url_encoded
|
197
|
-
builder.use Koala::HTTPService::MultipartRequest
|
198
198
|
}
|
199
199
|
stub_const("Koala::HTTPService::DEFAULT_MIDDLEWARE", block)
|
200
200
|
allow(Koala::HTTPService).to receive(:faraday_middleware).and_return(nil)
|
201
201
|
|
202
202
|
expect_any_instance_of(Faraday::Connection).to receive(:get) do |instance|
|
203
203
|
expect(instance.builder.handlers).to eq([
|
204
|
-
|
204
|
+
Faraday::Multipart::Middleware,
|
205
205
|
Faraday::Request::UrlEncoded,
|
206
|
-
Koala::HTTPService::MultipartRequest
|
207
206
|
])
|
208
207
|
mock_http_response
|
209
208
|
end
|
@@ -213,22 +212,57 @@ describe Koala::HTTPService do
|
|
213
212
|
|
214
213
|
it "uses the defined HTTPService.faraday_middleware block if defined" do
|
215
214
|
block = Proc.new { |builder|
|
216
|
-
builder.
|
215
|
+
builder.request :multipart
|
217
216
|
builder.request :url_encoded
|
218
|
-
builder.use Koala::HTTPService::MultipartRequest
|
219
217
|
}
|
220
218
|
expect(Koala::HTTPService).to receive(:faraday_middleware).and_return(block)
|
221
219
|
|
222
220
|
expect_any_instance_of(Faraday::Connection).to receive(:get) do |instance|
|
223
221
|
expect(instance.builder.handlers).to eq([
|
224
|
-
|
222
|
+
Faraday::Multipart::Middleware,
|
225
223
|
Faraday::Request::UrlEncoded,
|
226
|
-
Koala::HTTPService::MultipartRequest
|
227
224
|
])
|
228
225
|
mock_http_response
|
229
226
|
end
|
230
227
|
|
231
228
|
Koala::HTTPService.make_request(request)
|
232
229
|
end
|
230
|
+
|
231
|
+
context 'log_tokens configuration' do
|
232
|
+
let(:args) { { "an" => :arg, "access_token" => "myvisbleaccesstoken" } }
|
233
|
+
|
234
|
+
before(:each) do
|
235
|
+
allow_any_instance_of(Faraday::Connection).to receive(:get) { double(status: '200', body: 'ok', headers: {}) }
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'logs tokens' do
|
239
|
+
allow(Koala.config).to receive(:mask_tokens) { false }
|
240
|
+
|
241
|
+
expect(Koala::Utils).to receive(:debug).with('STARTED => GET: /foo params: {"an"=>:arg, "access_token"=>"myvisbleaccesstoken"}')
|
242
|
+
expect(Koala::Utils).to receive(:debug).with('FINISHED => GET: /foo params: {"an"=>:arg, "access_token"=>"myvisbleaccesstoken"}')
|
243
|
+
|
244
|
+
Koala::HTTPService.make_request(request)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'doesnt log tokens' do
|
248
|
+
allow(Koala.config).to receive(:mask_tokens) { true }
|
249
|
+
|
250
|
+
expect(Koala::Utils).to receive(:debug).with('STARTED => GET: /foo params: {"an"=>:arg, "access_token"=>"myvisbleac*****token"}')
|
251
|
+
expect(Koala::Utils).to receive(:debug).with('FINISHED => GET: /foo params: {"an"=>:arg, "access_token"=>"myvisbleac*****token"}')
|
252
|
+
|
253
|
+
Koala::HTTPService.make_request(request)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'hides the token for the debug_token api endpoint' do
|
257
|
+
request = Koala::HTTPService::Request.new(path: "/debug_token", verb: verb, args: { input_token: 'myvisibleaccesstoken', 'access_token' => 'myvisibleaccesstoken' }, options: options)
|
258
|
+
|
259
|
+
allow(Koala.config).to receive(:mask_tokens) { true }
|
260
|
+
|
261
|
+
expect(Koala::Utils).to receive(:debug).with('STARTED => GET: /debug_token params: {"input_token"=>"myvisiblea*****token", "access_token"=>"myvisiblea*****token"}')
|
262
|
+
expect(Koala::Utils).to receive(:debug).with('FINISHED => GET: /debug_token params: {"input_token"=>"myvisiblea*****token", "access_token"=>"myvisiblea*****token"}')
|
263
|
+
|
264
|
+
Koala::HTTPService.make_request(request)
|
265
|
+
end
|
266
|
+
end
|
233
267
|
end
|
234
268
|
end
|
data/spec/support/koala_test.rb
CHANGED
@@ -238,11 +238,11 @@ module KoalaTest
|
|
238
238
|
# JRuby doesn't support typhoeus on Travis
|
239
239
|
unless defined? JRUBY_VERSION
|
240
240
|
require adapter
|
241
|
-
require
|
241
|
+
require "faraday/#{adapter}"
|
242
242
|
Faraday.default_adapter = adapter.to_sym
|
243
243
|
end
|
244
|
-
rescue
|
245
|
-
puts "Unable to load adapter #{adapter}, using Net::HTTP."
|
244
|
+
rescue => e
|
245
|
+
puts "Unable to load adapter #{adapter}, using Net::HTTP. #{e.class} #{e.message}"
|
246
246
|
ensure
|
247
247
|
@adapter_activation_attempted = true
|
248
248
|
end
|
@@ -8,7 +8,7 @@ module Koala
|
|
8
8
|
# Mocks all HTTP requests for with koala_spec_with_mocks.rb
|
9
9
|
# Mocked values to be included in TEST_DATA used in specs
|
10
10
|
ACCESS_TOKEN = '*'
|
11
|
-
APP_ACCESS_TOKEN = "
|
11
|
+
APP_ACCESS_TOKEN = "********************"
|
12
12
|
OAUTH_CODE = 'OAUTHCODE'
|
13
13
|
|
14
14
|
# Loads testing data
|
@@ -30,7 +30,7 @@ module Koala
|
|
30
30
|
|
31
31
|
# Loads the mock response data via ERB to substitue values for TEST_DATA (see oauth/access_token)
|
32
32
|
mock_response_file_path = File.join(File.dirname(__FILE__), '..', 'fixtures', 'mock_facebook_responses.yml')
|
33
|
-
RESPONSES = YAML.
|
33
|
+
RESPONSES = YAML.safe_load(ERB.new(IO.read(mock_response_file_path)).result(binding), [], [], true)
|
34
34
|
|
35
35
|
def self.make_request(request)
|
36
36
|
if response = match_response(request.raw_path, request.raw_args, request.raw_verb, request.raw_options)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: koala
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Koppel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday-multipart
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: addressable
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,34 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '1.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rexml
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: base64
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
55
97
|
description: Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write
|
56
98
|
access to the social graph via the Graph and REST APIs, as well as support for realtime
|
57
99
|
updates and OAuth and Facebook Connect authentication. Koala is fully tested and
|
@@ -64,9 +106,9 @@ extra_rdoc_files:
|
|
64
106
|
- readme.md
|
65
107
|
- changelog.md
|
66
108
|
files:
|
109
|
+
- ".github/workflows/test.yml"
|
67
110
|
- ".gitignore"
|
68
111
|
- ".rspec"
|
69
|
-
- ".travis.yml"
|
70
112
|
- ".yardopts"
|
71
113
|
- Gemfile
|
72
114
|
- ISSUE_TEMPLATE
|
@@ -87,7 +129,6 @@ files:
|
|
87
129
|
- lib/koala/configuration.rb
|
88
130
|
- lib/koala/errors.rb
|
89
131
|
- lib/koala/http_service.rb
|
90
|
-
- lib/koala/http_service/multipart_request.rb
|
91
132
|
- lib/koala/http_service/request.rb
|
92
133
|
- lib/koala/http_service/response.rb
|
93
134
|
- lib/koala/http_service/uploadable_io.rb
|
@@ -109,7 +150,6 @@ files:
|
|
109
150
|
- spec/cases/http_service_spec.rb
|
110
151
|
- spec/cases/koala_spec.rb
|
111
152
|
- spec/cases/koala_test_spec.rb
|
112
|
-
- spec/cases/multipart_request_spec.rb
|
113
153
|
- spec/cases/oauth_spec.rb
|
114
154
|
- spec/cases/realtime_updates_spec.rb
|
115
155
|
- spec/cases/test_users_spec.rb
|
@@ -151,42 +191,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
191
|
- !ruby/object:Gem::Version
|
152
192
|
version: '0'
|
153
193
|
requirements: []
|
154
|
-
|
155
|
-
rubygems_version: 2.6.10
|
194
|
+
rubygems_version: 3.3.26
|
156
195
|
signing_key:
|
157
196
|
specification_version: 4
|
158
197
|
summary: A lightweight, flexible library for Facebook with support for the Graph API,
|
159
198
|
the REST API, realtime updates, and OAuth authentication.
|
160
|
-
test_files:
|
161
|
-
- spec/cases/api_spec.rb
|
162
|
-
- spec/cases/configuration_spec.rb
|
163
|
-
- spec/cases/error_spec.rb
|
164
|
-
- spec/cases/graph_api_batch_spec.rb
|
165
|
-
- spec/cases/graph_api_spec.rb
|
166
|
-
- spec/cases/graph_collection_spec.rb
|
167
|
-
- spec/cases/graph_error_checker_spec.rb
|
168
|
-
- spec/cases/http_service/request_spec.rb
|
169
|
-
- spec/cases/http_service/response_spec.rb
|
170
|
-
- spec/cases/http_service_spec.rb
|
171
|
-
- spec/cases/koala_spec.rb
|
172
|
-
- spec/cases/koala_test_spec.rb
|
173
|
-
- spec/cases/multipart_request_spec.rb
|
174
|
-
- spec/cases/oauth_spec.rb
|
175
|
-
- spec/cases/realtime_updates_spec.rb
|
176
|
-
- spec/cases/test_users_spec.rb
|
177
|
-
- spec/cases/uploadable_io_spec.rb
|
178
|
-
- spec/cases/utils_spec.rb
|
179
|
-
- spec/fixtures/beach.jpg
|
180
|
-
- spec/fixtures/cat.m4v
|
181
|
-
- spec/fixtures/facebook_data.yml
|
182
|
-
- spec/fixtures/mock_facebook_responses.yml
|
183
|
-
- spec/fixtures/vcr_cassettes/app_test_accounts.yml
|
184
|
-
- spec/fixtures/vcr_cassettes/friend_list_next_page.yml
|
185
|
-
- spec/integration/graph_collection_spec.rb
|
186
|
-
- spec/spec_helper.rb
|
187
|
-
- spec/support/custom_matchers.rb
|
188
|
-
- spec/support/graph_api_shared_examples.rb
|
189
|
-
- spec/support/koala_test.rb
|
190
|
-
- spec/support/mock_http_service.rb
|
191
|
-
- spec/support/uploadable_io_shared_examples.rb
|
192
|
-
has_rdoc:
|
199
|
+
test_files: []
|
data/.travis.yml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: false
|
3
|
-
cache: bundler
|
4
|
-
rvm:
|
5
|
-
# MRI
|
6
|
-
- 2.1
|
7
|
-
- 2.2
|
8
|
-
- 2.3.1
|
9
|
-
- 2.4.0
|
10
|
-
# Rubinius is failing due to segfaults on Travis (and takes significantly longer to run)
|
11
|
-
# those builds will be restored later
|
12
|
-
# jruby
|
13
|
-
# - jruby-19mode
|
14
|
-
bundler_args: --without development
|
15
|
-
addons:
|
16
|
-
code_climate:
|
17
|
-
repo_token: 7af99d9225b4c14640f9ec3cb2e24d2f7103ac49417b0bd989188fb6c25f2909
|
18
|
-
after_success:
|
19
|
-
- bundle exec codeclimate-test-reporter
|
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
|
3
|
-
module Koala
|
4
|
-
module HTTPService
|
5
|
-
class MultipartRequest < Faraday::Request::Multipart
|
6
|
-
# Facebook expects nested parameters to be passed in a certain way
|
7
|
-
# Based on our testing (https://github.com/arsduo/koala/issues/125),
|
8
|
-
# Faraday needs two changes to make that work:
|
9
|
-
# 1) [] need to be escaped (e.g. params[foo]=bar ==> params%5Bfoo%5D=bar)
|
10
|
-
# 2) such messages need to be multipart-encoded
|
11
|
-
|
12
|
-
self.mime_type = 'multipart/form-data'.freeze
|
13
|
-
|
14
|
-
def process_request?(env)
|
15
|
-
# if the request values contain any hashes or arrays, multipart it
|
16
|
-
super || !!(env[:body].respond_to?(:values) && env[:body].values.find {|v| v.is_a?(Hash) || v.is_a?(Array)})
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
def process_params(params, prefix = nil, pieces = nil, &block)
|
21
|
-
params.inject(pieces || []) do |all, (key, value)|
|
22
|
-
key = "#{prefix}%5B#{key}%5D" if prefix
|
23
|
-
|
24
|
-
case value
|
25
|
-
when Array
|
26
|
-
values = value.inject([]) { |a,v| a << [nil, v] }
|
27
|
-
process_params(values, key, all, &block)
|
28
|
-
when Hash
|
29
|
-
process_params(value, key, all, &block)
|
30
|
-
else
|
31
|
-
all << block.call(key, value)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Koala::HTTPService::MultipartRequest do
|
4
|
-
it "is a subclass of Faraday::Request::Multipart" do
|
5
|
-
expect(Koala::HTTPService::MultipartRequest.superclass).to eq(Faraday::Request::Multipart)
|
6
|
-
end
|
7
|
-
|
8
|
-
it "defines mime_type as multipart/form-data" do
|
9
|
-
expect(Koala::HTTPService::MultipartRequest.mime_type).to eq('multipart/form-data')
|
10
|
-
end
|
11
|
-
|
12
|
-
describe "#process_request?" do
|
13
|
-
before :each do
|
14
|
-
@env = Faraday::Env.new
|
15
|
-
@multipart = Koala::HTTPService::MultipartRequest.new
|
16
|
-
allow(@multipart).to receive(:request_type).and_return("")
|
17
|
-
end
|
18
|
-
|
19
|
-
# no way to test the call to super, unfortunately
|
20
|
-
it "returns true if env[:body] is a hash with at least one hash in its values" do
|
21
|
-
@env[:body] = {:a => {:c => 2}}
|
22
|
-
expect(@multipart.process_request?(@env)).to be_truthy
|
23
|
-
end
|
24
|
-
|
25
|
-
it "returns true if env[:body] is a hash with at least one array in its values" do
|
26
|
-
@env[:body] = {:a => [:c, 2]}
|
27
|
-
expect(@multipart.process_request?(@env)).to be_truthy
|
28
|
-
end
|
29
|
-
|
30
|
-
it "returns true if env[:body] is a hash with mixed objects in its values" do
|
31
|
-
@env[:body] = {:a => [:c, 2], :b => {:e => :f}}
|
32
|
-
expect(@multipart.process_request?(@env)).to be_truthy
|
33
|
-
end
|
34
|
-
|
35
|
-
it "returns false if env[:body] is a string" do
|
36
|
-
@env[:body] = "my body"
|
37
|
-
expect(@multipart.process_request?(@env)).to be_falsey
|
38
|
-
end
|
39
|
-
|
40
|
-
it "returns false if env[:body] is a hash without an array or hash value" do
|
41
|
-
@env[:body] = {:a => 3}
|
42
|
-
expect(@multipart.process_request?(@env)).to be_falsey
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe "#process_params" do
|
47
|
-
before :each do
|
48
|
-
@parent = Faraday::Request::Multipart.new
|
49
|
-
@multipart = Koala::HTTPService::MultipartRequest.new
|
50
|
-
@block = lambda {|k, v| "#{k}=#{v}"}
|
51
|
-
end
|
52
|
-
|
53
|
-
it "is identical to the parent for requests without a prefix" do
|
54
|
-
hash = {:a => 2, :c => "3"}
|
55
|
-
expect(@multipart.process_params(hash, &@block)).to eq(@parent.process_params(hash, &@block))
|
56
|
-
end
|
57
|
-
|
58
|
-
it "replaces encodes [ and ] if the request has a prefix" do
|
59
|
-
hash = {:a => 2, :c => "3"}
|
60
|
-
prefix = "foo"
|
61
|
-
# process_params returns an array
|
62
|
-
expect(@multipart.process_params(hash, prefix, &@block).join("&")).to eq(@parent.process_params(hash, prefix, &@block).join("&").gsub(/\[/, "%5B").gsub(/\]/, "%5D"))
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|