koala 0.4 → 3.7.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 +7 -0
- data/.github/workflows/test.yml +32 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.yardopts +3 -0
- data/Gemfile +25 -0
- data/ISSUE_TEMPLATE +25 -0
- data/LICENSE +22 -0
- data/Manifest +32 -5
- data/PULL_REQUEST_TEMPLATE +11 -0
- data/Rakefile +12 -12
- data/changelog.md +781 -0
- data/code_of_conduct.md +74 -0
- data/koala.gemspec +28 -24
- data/lib/koala/api/batch_operation.rb +86 -0
- data/lib/koala/api/graph_api_methods.rb +504 -0
- data/lib/koala/api/graph_batch_api.rb +167 -0
- data/lib/koala/api/graph_collection.rb +129 -0
- data/lib/koala/api/graph_error_checker.rb +72 -0
- data/lib/koala/api.rb +159 -0
- data/lib/koala/configuration.rb +56 -0
- data/lib/koala/errors.rb +126 -0
- data/lib/koala/http_service/request.rb +133 -0
- data/lib/koala/http_service/response.rb +20 -0
- data/lib/koala/http_service/uploadable_io.rb +183 -0
- data/lib/koala/http_service.rb +108 -0
- data/lib/koala/oauth.rb +342 -0
- data/lib/koala/realtime_updates.rb +151 -0
- data/lib/koala/test_users.rb +189 -0
- data/lib/koala/utils.rb +41 -0
- data/lib/koala/version.rb +3 -0
- data/lib/koala.rb +51 -291
- data/readme.md +269 -21
- data/spec/cases/api_spec.rb +362 -0
- data/spec/cases/configuration_spec.rb +11 -0
- data/spec/cases/error_spec.rb +143 -0
- data/spec/cases/graph_api_batch_spec.rb +788 -0
- data/spec/cases/graph_api_spec.rb +76 -0
- data/spec/cases/graph_collection_spec.rb +192 -0
- data/spec/cases/graph_error_checker_spec.rb +147 -0
- data/spec/cases/http_service/request_spec.rb +250 -0
- data/spec/cases/http_service/response_spec.rb +24 -0
- data/spec/cases/http_service_spec.rb +280 -0
- data/spec/cases/koala_spec.rb +57 -0
- data/spec/cases/koala_test_spec.rb +5 -0
- data/spec/cases/oauth_spec.rb +647 -0
- data/spec/cases/realtime_updates_spec.rb +327 -0
- data/spec/cases/test_users_spec.rb +383 -0
- data/spec/cases/uploadable_io_spec.rb +266 -0
- data/spec/cases/utils_spec.rb +55 -0
- data/spec/fixtures/beach.jpg +0 -0
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/facebook_data.yml +63 -0
- data/spec/fixtures/mock_facebook_responses.yml +483 -0
- data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +97 -0
- data/spec/fixtures/vcr_cassettes/friend_list_next_page.yml +121 -0
- data/spec/integration/graph_collection_spec.rb +24 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/custom_matchers.rb +28 -0
- data/spec/support/graph_api_shared_examples.rb +534 -0
- data/spec/support/koala_test.rb +251 -0
- data/spec/support/mock_http_service.rb +140 -0
- data/spec/support/uploadable_io_shared_examples.rb +70 -0
- metadata +206 -62
- data/CHANGELOG +0 -24
- data/init.rb +0 -2
- data/lib/http_services.rb +0 -60
- data/test/facebook_data.yml +0 -5
- data/test/koala/facebook_no_access_token_tests.rb +0 -119
- data/test/koala/facebook_with_access_token_tests.rb +0 -106
- data/test/koala_tests.rb +0 -30
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Koala::Facebook::GraphAPIMethods' do
|
|
4
|
+
before do
|
|
5
|
+
@api = Koala::Facebook::API.new(@token)
|
|
6
|
+
# app API
|
|
7
|
+
@app_id = KoalaTest.app_id
|
|
8
|
+
@app_access_token = KoalaTest.app_access_token
|
|
9
|
+
@app_api = Koala::Facebook::API.new(@app_access_token)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
let(:dummy_response) { double("fake response", data: {}, status: 200, body: "", headers: {}) }
|
|
13
|
+
|
|
14
|
+
describe 'post-processing for' do
|
|
15
|
+
let(:result) { double("result") }
|
|
16
|
+
let(:post_processing) { lambda {|arg| {"result" => result, "args" => arg} } }
|
|
17
|
+
|
|
18
|
+
# Most API methods have the same signature, we test get_object representatively
|
|
19
|
+
# and the other methods which do some post-processing locally
|
|
20
|
+
context '#get_object' do
|
|
21
|
+
it 'returns result of block' do
|
|
22
|
+
allow(@api).to receive(:api).and_return(dummy_response)
|
|
23
|
+
expect(@api.get_object('barackobama', &post_processing)["result"]).to eq(result)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "doesn't add token to received arguments" do
|
|
27
|
+
args = {}.freeze
|
|
28
|
+
expect(Koala).to receive(:make_request).and_return(dummy_response)
|
|
29
|
+
expect(@api.get_object('barackobama', args, &post_processing)["result"]).to eq(result)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context '#get_picture' do
|
|
34
|
+
it 'returns result of block' do
|
|
35
|
+
result_url = "a url"
|
|
36
|
+
allow(@api).to receive(:api).and_return(Koala::HTTPService::Response.new(200, {"data" => {"is_silhouette" => false, "url" => result_url}}.to_json, {}))
|
|
37
|
+
expect(@api.get_picture('koppel', &post_processing)["result"]).to eq(result)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context '#get_page_access_token' do
|
|
42
|
+
it 'returns result of block' do
|
|
43
|
+
token = Koala::MockHTTPService::APP_ACCESS_TOKEN
|
|
44
|
+
allow(@api).to receive(:api).and_return(
|
|
45
|
+
Koala::HTTPService::Response.new(200, {"access_token" => token}.to_json, {})
|
|
46
|
+
)
|
|
47
|
+
response = @api.get_page_access_token('facebook', &post_processing)
|
|
48
|
+
expect(response["args"]).to eq(token)
|
|
49
|
+
expect(response["result"]).to eq(result)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context '#graph_call' do
|
|
55
|
+
describe "the appsecret_proof option" do
|
|
56
|
+
let(:path) { '/path' }
|
|
57
|
+
|
|
58
|
+
it "is enabled by default if an app secret is present" do
|
|
59
|
+
api = Koala::Facebook::API.new(@token, "mysecret")
|
|
60
|
+
expect(api).to receive(:api).with(path, {}, 'get', :appsecret_proof => true).and_return(dummy_response)
|
|
61
|
+
api.graph_call(path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "can be disabled manually" do
|
|
65
|
+
api = Koala::Facebook::API.new(@token, "mysecret")
|
|
66
|
+
expect(api).to receive(:api).with(path, {}, 'get', hash_not_including(appsecret_proof: true)).and_return(dummy_response)
|
|
67
|
+
api.graph_call(path, {}, "get", appsecret_proof: false)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "isn't included if no app secret is present" do
|
|
71
|
+
expect(@api).to receive(:api).with(path, {}, 'get', {}).and_return(dummy_response)
|
|
72
|
+
@api.graph_call(path)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Koala::Facebook::API::GraphCollection do
|
|
4
|
+
let(:paging){ {"paging" => true} }
|
|
5
|
+
|
|
6
|
+
before(:each) do
|
|
7
|
+
@headers = {'Content-Type' => 'application/json'}
|
|
8
|
+
@data = {
|
|
9
|
+
"data" => [1, 2, 'three'],
|
|
10
|
+
"paging" => paging,
|
|
11
|
+
"summary" => [3]
|
|
12
|
+
}
|
|
13
|
+
@result = Koala::HTTPService::Response.new(200, @data.to_json, @headers)
|
|
14
|
+
@api = Koala::Facebook::API.new("123")
|
|
15
|
+
@collection = Koala::Facebook::API::GraphCollection.new(@result, @api)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "subclasses Array" do
|
|
19
|
+
expect(Koala::Facebook::API::GraphCollection.ancestors).to include(Array)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "creates an array-like object" do
|
|
23
|
+
expect(Koala::Facebook::API::GraphCollection.new(@result, @api)).to be_an(Array)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "contains the result data" do
|
|
27
|
+
@data["data"].each_with_index {|r, i| expect(@collection[i]).to eq(r)}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "has a read-only paging attribute" do
|
|
31
|
+
expect(@collection.methods.map(&:to_sym)).to include(:paging)
|
|
32
|
+
expect(@collection.methods.map(&:to_sym)).not_to include(:paging=)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "sets paging to results['paging']" do
|
|
36
|
+
expect(@collection.paging).to eq(@data["paging"])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "sets summary to results['summary']" do
|
|
40
|
+
expect(@collection.summary).to eq(@data["summary"])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "sets raw_response to the original results" do
|
|
44
|
+
expect(@collection.raw_response).to eq(@result.data)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "sets the API to the provided API" do
|
|
48
|
+
expect(@collection.api).to eq(@api)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "sets the headers correctly" do
|
|
52
|
+
expect(@collection.headers).to eq(@headers)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "when getting a whole page" do
|
|
56
|
+
before(:each) do
|
|
57
|
+
@second_page = {
|
|
58
|
+
"data" => ["second", "page", "data"],
|
|
59
|
+
"paging" => {}
|
|
60
|
+
}
|
|
61
|
+
@base = double("base")
|
|
62
|
+
@args = {"a" => 1}
|
|
63
|
+
@page_of_results = double("page of results")
|
|
64
|
+
@result = Koala::HTTPService::Response.new(200, @second_page.to_json, {})
|
|
65
|
+
@result.data
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should return the previous page of results" do
|
|
69
|
+
expect(@collection).to receive(:previous_page_params).and_return([@base, @args])
|
|
70
|
+
expect(@api).to receive(:api).with(@base, @args, anything, anything).and_return(@result)
|
|
71
|
+
expect(Koala::Facebook::API::GraphCollection).to receive(:new).with(@result, @api).and_return(@page_of_results)
|
|
72
|
+
expect(@collection.previous_page).to eq(@page_of_results)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should return the next page of results" do
|
|
76
|
+
expect(@collection).to receive(:next_page_params).and_return([@base, @args])
|
|
77
|
+
expect(@api).to receive(:api).with(@base, @args, anything, anything).and_return(@result)
|
|
78
|
+
expect(Koala::Facebook::API::GraphCollection).to receive(:new).with(@result, @api).and_return(@page_of_results)
|
|
79
|
+
|
|
80
|
+
expect(@collection.next_page).to eq(@page_of_results)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should return nil it there are no other pages" do
|
|
84
|
+
%w{next previous}.each do |this|
|
|
85
|
+
expect(@collection).to receive("#{this}_page_params".to_sym).and_return(nil)
|
|
86
|
+
expect(@collection.send("#{this}_page")).to eq(nil)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "when parsing page paramters" do
|
|
92
|
+
describe "#parse_page_url" do
|
|
93
|
+
it "should pass the url to the class method" do
|
|
94
|
+
url = double("url")
|
|
95
|
+
expect(Koala::Facebook::API::GraphCollection).to receive(:parse_page_url).with(url)
|
|
96
|
+
@collection.parse_page_url(url)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should return the result of the class method" do
|
|
100
|
+
parsed_content = double("parsed_content")
|
|
101
|
+
allow(Koala::Facebook::API::GraphCollection).to receive(:parse_page_url).and_return(parsed_content)
|
|
102
|
+
expect(@collection.parse_page_url(double("url"))).to eq(parsed_content)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe ".parse_page_url" do
|
|
107
|
+
it "should return the base as the first array entry" do
|
|
108
|
+
base = "url_path"
|
|
109
|
+
expect(Koala::Facebook::API::GraphCollection.parse_page_url("http://facebook.com/#{base}?anything").first).to eq(base)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "should return the arguments as a hash as the last array entry" do
|
|
113
|
+
args_hash = {"one" => "val_one", "two" => "val_two"}
|
|
114
|
+
expect(Koala::Facebook::API::GraphCollection.parse_page_url("http://facebook.com/anything?#{args_hash.map {|k,v| "#{k}=#{v}" }.join("&")}").last).to eq(args_hash)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "works with non-.com addresses" do
|
|
118
|
+
base = "url_path"
|
|
119
|
+
args_hash = {"one" => "val_one", "two" => "val_two"}
|
|
120
|
+
expect(Koala::Facebook::API::GraphCollection.parse_page_url("http://facebook.com/#{base}?#{args_hash.map {|k,v| "#{k}=#{v}" }.join("&")}")).to eq([base, args_hash])
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "works with addresses with irregular characters" do
|
|
124
|
+
access_token = "appid123a|fdcba"
|
|
125
|
+
base, args_hash = Koala::Facebook::API::GraphCollection.parse_page_url("http://facebook.com/foo?token=#{access_token}")
|
|
126
|
+
expect(args_hash["token"]).to eq(access_token)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe ".evaluate" do
|
|
132
|
+
it "returns the body of the original response if it's provided a Response with a non-hash data key" do
|
|
133
|
+
result = double('fake response')
|
|
134
|
+
allow(result).to receive(:is_a?).with(Hash).and_return(false)
|
|
135
|
+
allow(result).to receive(:data).and_return([])
|
|
136
|
+
expect(Koala::Facebook::API::GraphCollection.evaluate(result, @api)).to eq([])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "returns the original result if it's provided a nil result" do
|
|
140
|
+
result = nil
|
|
141
|
+
expect(Koala::Facebook::API::GraphCollection.evaluate(result, @api)).to eq(result)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "returns the original result body if the result doesn't have a data key" do
|
|
145
|
+
paging = {"paging" => {}}
|
|
146
|
+
result = Koala::HTTPService::Response.new(200, paging.to_json, {})
|
|
147
|
+
expect(Koala::Facebook::API::GraphCollection.evaluate(result, @api)).to eq(paging)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "returns the original result if the result's data key isn't an array" do
|
|
151
|
+
body = {"data" => {}, "paging" => {}}
|
|
152
|
+
result = Koala::HTTPService::Response.new(200, body.to_json, {})
|
|
153
|
+
expect(Koala::Facebook::API::GraphCollection.evaluate(result, @api)).to eq(body)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "returns a new GraphCollection of the result if it has an array data key and a paging key" do
|
|
157
|
+
body = {"data" => [], "paging" => {}}
|
|
158
|
+
result = Koala::HTTPService::Response.new(200, body.to_json, {})
|
|
159
|
+
expected = :foo
|
|
160
|
+
expect(Koala::Facebook::API::GraphCollection).to receive(:new).with(result, @api).and_return(expected)
|
|
161
|
+
expect(Koala::Facebook::API::GraphCollection.evaluate(result, @api)).to eq(expected)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe "#next_page" do
|
|
166
|
+
let(:paging){ {"next" => "http://example.com/abc?a=2&b=3"} }
|
|
167
|
+
|
|
168
|
+
it "should get next page" do
|
|
169
|
+
expect(@api).to receive(:get_page).with(["abc", {"a" => "2", "b" => "3"}])
|
|
170
|
+
@collection.next_page
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "should get next page with extra parameters" do
|
|
174
|
+
expect(@api).to receive(:get_page).with(["abc", {"a" => "2", "b" => "3", "c" => "4"}])
|
|
175
|
+
@collection.next_page("c" => "4")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe "#previous_page" do
|
|
180
|
+
let(:paging){ {"previous" => "http://example.com/?a=2&b=3"} }
|
|
181
|
+
|
|
182
|
+
it "should get previous page" do
|
|
183
|
+
expect(@api).to receive(:get_page).with(["", {"a" => "2", "b" => "3"}])
|
|
184
|
+
@collection.previous_page
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "should get previous page with extra parameters" do
|
|
188
|
+
expect(@api).to receive(:get_page).with(["", {"a" => "2", "b" => "3", "c" => "4"}])
|
|
189
|
+
@collection.previous_page("c" => "4")
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
module Facebook
|
|
5
|
+
RSpec.describe GraphErrorChecker do
|
|
6
|
+
it "defines a set of AUTHENTICATION_ERROR_CODES" do
|
|
7
|
+
expect(GraphErrorChecker::AUTHENTICATION_ERROR_CODES).to match_array([102, 190, 450, 452, 2500])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "defines a set of DEBUG_HEADERS" do
|
|
11
|
+
expect(GraphErrorChecker::DEBUG_HEADERS).to match_array([
|
|
12
|
+
"x-fb-rev",
|
|
13
|
+
"x-fb-debug",
|
|
14
|
+
"x-fb-trace-id",
|
|
15
|
+
"x-business-use-case-usage",
|
|
16
|
+
"x-ad-account-usage",
|
|
17
|
+
"x-app-usage"
|
|
18
|
+
])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#error_if_appropriate" do
|
|
22
|
+
shared_examples_for :returning_no_error do |status|
|
|
23
|
+
it "returns no error" do
|
|
24
|
+
expect(GraphErrorChecker.new(status, "{}", {}).error_if_appropriate).to be_nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "ignores error data even if present" do
|
|
28
|
+
checker = GraphErrorChecker.new(
|
|
29
|
+
status,
|
|
30
|
+
{"error" => {"some" => "error"}},
|
|
31
|
+
{"x-fb-rev" => "data"}
|
|
32
|
+
)
|
|
33
|
+
expect(checker.error_if_appropriate).to be_nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "if the status is 2xx" do
|
|
38
|
+
it_should_behave_like :returning_no_error, 202
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "if the status is 3xx" do
|
|
42
|
+
it_should_behave_like :returning_no_error, 302
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
shared_examples_for :returns_an_error do |status|
|
|
46
|
+
let(:body) { "{}" }
|
|
47
|
+
let(:headers) { {} }
|
|
48
|
+
let(:error) { GraphErrorChecker.new(status, body, headers).error_if_appropriate }
|
|
49
|
+
it "returns a ClientError for a generic error" do
|
|
50
|
+
expect(error).to be_a(ClientError)
|
|
51
|
+
expect(error.response_body).to eq(body)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "returns a ClientError even if the body can't be parsed as JSON" do
|
|
55
|
+
body.replace("hello from Chicago")
|
|
56
|
+
expect(error).to be_a(ClientError)
|
|
57
|
+
expect(error.response_body).to eq(body)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "returns a ClientError if the body is empty" do
|
|
61
|
+
body.replace("")
|
|
62
|
+
expect(error).to be_a(ClientError)
|
|
63
|
+
expect(error.response_body).to eq(body)
|
|
64
|
+
end
|
|
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
|
+
|
|
72
|
+
it "adds error data from the body" do
|
|
73
|
+
error_data = {
|
|
74
|
+
"type" => "FB error type",
|
|
75
|
+
"code" => "FB error code",
|
|
76
|
+
"error_subcode" => "FB error subcode",
|
|
77
|
+
"message" => "An error occurred!",
|
|
78
|
+
"error_user_msg" => "A user msg",
|
|
79
|
+
"error_user_title" => "usr title",
|
|
80
|
+
"fbtrace_id" => "fbtrace_id"
|
|
81
|
+
}
|
|
82
|
+
body.replace({"error" => error_data}.to_json)
|
|
83
|
+
|
|
84
|
+
expect(error.fb_error_trace_id).to eq(error_data["fbtrace_id"])
|
|
85
|
+
expect(error.fb_error_type).to eq(error_data["type"])
|
|
86
|
+
expect(error.fb_error_code).to eq(error_data["code"])
|
|
87
|
+
expect(error.fb_error_subcode).to eq(error_data["error_subcode"])
|
|
88
|
+
expect(error.fb_error_message).to eq(error_data["message"])
|
|
89
|
+
expect(error.fb_error_user_msg).to eq(error_data["error_user_msg"])
|
|
90
|
+
expect(error.fb_error_user_title).to eq(error_data["error_user_title"])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "adds the FB debug headers to the errors" do
|
|
94
|
+
headers.merge!(
|
|
95
|
+
"x-fb-debug" => double("fb debug"),
|
|
96
|
+
"x-fb-rev" => double("fb rev"),
|
|
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
|
|
101
|
+
)
|
|
102
|
+
expect(error.fb_error_debug_trace_id).to eq(headers["x-fb-trace-id"])
|
|
103
|
+
expect(error.fb_error_debug).to eq(headers["x-fb-debug"])
|
|
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: expected object key, got 'invalid:json}' at line 1 column 2 while parsing x-app-usage = {invalid:json}")
|
|
116
|
+
expect(error.fb_app_usage).to eq(nil)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context "it returns an AuthenticationError" do
|
|
120
|
+
it "if FB says it's an OAuthException and it has no code" do
|
|
121
|
+
body.replace({"error" => {"type" => "OAuthException"}}.to_json)
|
|
122
|
+
expect(error).to be_an(AuthenticationError)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
GraphErrorChecker::AUTHENTICATION_ERROR_CODES.each do |error_code|
|
|
126
|
+
it "if FB says it's an OAuthException and it has code #{error_code}" do
|
|
127
|
+
body.replace({"error" => {"type" => "OAuthException", "code" => error_code}}.to_json)
|
|
128
|
+
expect(error).to be_an(AuthenticationError)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Note: I'm not sure why this behavior was implemented, it may be incorrect. To
|
|
134
|
+
# investigate.
|
|
135
|
+
it "doesn't return an AuthenticationError if FB says it's an OAuthException but the code doesn't match" do
|
|
136
|
+
body.replace({"error" => {"type" => "OAuthException", "code" => 2499}}.to_json)
|
|
137
|
+
expect(error).to be_an(ClientError)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "if the status is 4xx" do
|
|
142
|
+
it_should_behave_like :returns_an_error, 400
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
module HTTPService
|
|
5
|
+
RSpec.describe Request do
|
|
6
|
+
let(:path) { "/foo" }
|
|
7
|
+
let(:args) { {"a" => 2, "b" => 3, "access_token" => "a_token"} }
|
|
8
|
+
let(:verb) { "get" }
|
|
9
|
+
let(:options) { {an: :option} }
|
|
10
|
+
let(:request) { Request.new(path: path, args: args, verb: verb, options: options) }
|
|
11
|
+
|
|
12
|
+
shared_context post: true do
|
|
13
|
+
let(:verb) { "post" }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
shared_context delete: true do
|
|
17
|
+
let(:verb) { "delete" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
shared_context put: true do
|
|
21
|
+
let(:verb) { "put" }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "raises an ArgumentError if no verb is supplied" do
|
|
25
|
+
expect { Request.new(path: path) }.to raise_exception(ArgumentError)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "raises an ArgumentError if no path is supplied" do
|
|
29
|
+
expect { Request.new(verb: verb) }.to raise_exception(ArgumentError)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#verb" do
|
|
33
|
+
it "returns get for get" do
|
|
34
|
+
expect(Request.new(path: path, verb: "get").verb).to eq("get")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns post for post" do
|
|
38
|
+
expect(Request.new(path: path, verb: "post").verb).to eq("post")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "returns post for put" do
|
|
42
|
+
expect(Request.new(path: path, verb: "put").verb).to eq("post")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "returns post for delete" do
|
|
46
|
+
expect(Request.new(path: path, verb: "delete").verb).to eq("post")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "#path" do
|
|
51
|
+
context "if there's no API version" do
|
|
52
|
+
it "returns the path" do
|
|
53
|
+
expect(request.path).to eq(path)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "if there's an API version" do
|
|
58
|
+
shared_examples_for :including_the_version do
|
|
59
|
+
it "prefixes the version appropriately when the path starts with /" do
|
|
60
|
+
expect(Request.new(path: "/foo", verb: "get", options: options).path).to eq("/#{version}/foo")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "prefixes the version appropriately when the path doesn't start with /" do
|
|
64
|
+
expect(Request.new(path: "foo", verb: "get", options: options).path).to eq("/#{version}/foo")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "set by Koala.config" do
|
|
69
|
+
let(:version) { "v2.7" }
|
|
70
|
+
|
|
71
|
+
before :each do
|
|
72
|
+
Koala.configure do |config|
|
|
73
|
+
config.api_version = version
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it_should_behave_like :including_the_version
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context "set in options" do
|
|
81
|
+
let(:version) { "v2.8" }
|
|
82
|
+
|
|
83
|
+
let(:options) { {api_version: version} }
|
|
84
|
+
|
|
85
|
+
it_should_behave_like :including_the_version
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context "for versions without a ." do
|
|
89
|
+
let(:version) { "v21" }
|
|
90
|
+
|
|
91
|
+
let(:options) { {api_version: version} }
|
|
92
|
+
|
|
93
|
+
it_should_behave_like :including_the_version
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe "#post_args" do
|
|
99
|
+
it "returns {} for get" do
|
|
100
|
+
expect(request.post_args).to eq({})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "returns args for post", :post do
|
|
104
|
+
expect(request.post_args).to eq(args)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "returns args + method: delete for delete", :delete do
|
|
108
|
+
expect(request.post_args).to eq(args.merge(method: "delete"))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "returns args + method: put for put", :put do
|
|
112
|
+
expect(request.post_args).to eq(args.merge(method: "put"))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "turns any UploadableIOs to UploadIOs" do
|
|
116
|
+
# technically this is done for all requests, but you don't send GET requests with files
|
|
117
|
+
upload_io = double("UploadIO")
|
|
118
|
+
u = Koala::HTTPService::UploadableIO.new("/path/to/stuff", "img/jpg")
|
|
119
|
+
allow(u).to receive(:to_upload_io).and_return(upload_io)
|
|
120
|
+
request = Request.new(path: path, verb: "post", args: {an_upload: u})
|
|
121
|
+
expect(request.post_args[:an_upload]).to eq(upload_io)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "#json?" do
|
|
126
|
+
it "returns false if the option for JSON isn't set" do
|
|
127
|
+
expect(request).not_to be_json
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context "if JSON is set" do
|
|
131
|
+
let(:options) { {format: :json} }
|
|
132
|
+
|
|
133
|
+
it "returns true if it is" do
|
|
134
|
+
expect(request).to be_json
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe "#options" do
|
|
140
|
+
it "returns the options provided" do
|
|
141
|
+
expect(request.options).to include(options)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "merges in any global options" do
|
|
145
|
+
global_options = {some: :opts}
|
|
146
|
+
HTTPService.http_options = global_options
|
|
147
|
+
results = request.options
|
|
148
|
+
HTTPService.http_options = {}
|
|
149
|
+
expect(results).to include(global_options)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "overwrites any global options with equivalent ones" do
|
|
153
|
+
options = {foo: :bar}
|
|
154
|
+
HTTPService.http_options = {foo: 2}
|
|
155
|
+
results = Request.new(path: path, args: args, verb: verb, options: options).options
|
|
156
|
+
HTTPService.http_options = {}
|
|
157
|
+
expect(results).to include(options)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe "get parameters" do
|
|
161
|
+
it "includes the parameters for a get " do
|
|
162
|
+
expect(request.options[:params]).to eq(args)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "returns {} for post", :post do
|
|
166
|
+
expect(request.options[:params]).to eq({})
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "returns {} for delete", :delete do
|
|
170
|
+
expect(request.options[:params]).to eq({})
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "returns {} for put", :put do
|
|
174
|
+
expect(request.options[:params]).to eq({})
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe "ssl options" do
|
|
179
|
+
it "includes the default SSL options even if there's no access token" do
|
|
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).to include(use_ssl: true, ssl: {verify: true})
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context "if there is an access_token" do
|
|
185
|
+
it "includes the default SSL options" do
|
|
186
|
+
expect(request.options).to include(use_ssl: true, ssl: {verify: true})
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "incorporates any provided SSL options" do
|
|
190
|
+
new_ssl_options = {an: :option2}
|
|
191
|
+
request_options = Request.new(path: path, args: args, verb: verb, options: options.merge(ssl: new_ssl_options)).options
|
|
192
|
+
expect(request_options[:ssl]).to include(new_ssl_options)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "overrides default SSL options with what's provided" do
|
|
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
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "server" do
|
|
204
|
+
describe "with no options" do
|
|
205
|
+
it "returns the graph server by default" do
|
|
206
|
+
expect(request.server).to eq("https://graph.facebook.com")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "uses another base server if specified" do
|
|
210
|
+
Koala.config.graph_server = "foo"
|
|
211
|
+
expect(request.server).to eq("https://foo")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "if there is no access token" do
|
|
215
|
+
let(:args) { {"a" => "b"} }
|
|
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
|
+
|
|
225
|
+
it "uses http" do
|
|
226
|
+
expect(request.server).to eq("http://graph.facebook.com")
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context "if options[:beta]" do
|
|
231
|
+
let(:options) { {beta: true} }
|
|
232
|
+
|
|
233
|
+
it "returns https if options[:beta]" do
|
|
234
|
+
expect(request.server).to eq("https://graph.beta.facebook.com")
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
context "if options[:video]" do
|
|
239
|
+
let(:options) { {video: true} }
|
|
240
|
+
|
|
241
|
+
it "returns https if options[:video]" do
|
|
242
|
+
expect(request.server).to eq("https://graph-video.facebook.com")
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module Koala::HTTPService
|
|
4
|
+
RSpec.describe Response do
|
|
5
|
+
describe "#data" do
|
|
6
|
+
it "returns nothing if the body is empty" do
|
|
7
|
+
expect(Response.new(200, "", {}).data).to be_nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "parses the JSON if there's a body" do
|
|
11
|
+
result = {"foo" => "bar"}
|
|
12
|
+
expect(Response.new(200, result.to_json, {}).data).to eq(result)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "allows raw values" do
|
|
16
|
+
expect(Response.new(200, "true", {}).data).to be true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "raises a ParserError if it's invalid JSON" do
|
|
20
|
+
expect { Response.new(200, "{", {}).data }.to raise_exception(JSON::ParserError)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|