api_client 0.1.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.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/Rakefile +1 -0
- data/api_client.gemspec +24 -0
- data/examples/digg.rb +31 -0
- data/examples/flickr.rb +37 -0
- data/examples/github.rb +52 -0
- data/examples/highrise.rb +41 -0
- data/examples/twitter.rb +34 -0
- data/examples/twitter_oauth.rb +36 -0
- data/lib/api_client.rb +45 -0
- data/lib/api_client/base.rb +52 -0
- data/lib/api_client/connection/abstract.rb +73 -0
- data/lib/api_client/connection/basic.rb +105 -0
- data/lib/api_client/connection/middlewares/request/logger.rb +17 -0
- data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
- data/lib/api_client/connection/oauth.rb +18 -0
- data/lib/api_client/errors.rb +16 -0
- data/lib/api_client/mixins/configuration.rb +24 -0
- data/lib/api_client/mixins/connection_hooks.rb +24 -0
- data/lib/api_client/mixins/delegation.rb +23 -0
- data/lib/api_client/mixins/inheritance.rb +19 -0
- data/lib/api_client/mixins/instantiation.rb +35 -0
- data/lib/api_client/mixins/scoping.rb +49 -0
- data/lib/api_client/resource/base.rb +63 -0
- data/lib/api_client/resource/scope.rb +73 -0
- data/lib/api_client/scope.rb +101 -0
- data/lib/api_client/utils.rb +18 -0
- data/lib/api_client/version.rb +3 -0
- data/spec/api_client/base/connection_hook_spec.rb +18 -0
- data/spec/api_client/base/delegation_spec.rb +15 -0
- data/spec/api_client/base/inheritance_spec.rb +44 -0
- data/spec/api_client/base/instantiation_spec.rb +54 -0
- data/spec/api_client/base/parsing_spec.rb +36 -0
- data/spec/api_client/base/scoping_spec.rb +60 -0
- data/spec/api_client/base_spec.rb +17 -0
- data/spec/api_client/connection/abstract_spec.rb +21 -0
- data/spec/api_client/connection/basic_spec.rb +135 -0
- data/spec/api_client/connection/oauth_spec.rb +27 -0
- data/spec/api_client/connection/request/logger_spec.rb +19 -0
- data/spec/api_client/connection/request/oauth_spec.rb +26 -0
- data/spec/api_client/resource/base_spec.rb +78 -0
- data/spec/api_client/resource/scope_spec.rb +96 -0
- data/spec/api_client/scope_spec.rb +170 -0
- data/spec/api_client/utils_spec.rb +32 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/fake_logger.rb +15 -0
- data/spec/support/matchers.rb +5 -0
- metadata +148 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Connection::Oauth do
|
4
|
+
|
5
|
+
it "adds basic middlewares to faraday" do
|
6
|
+
instance = ApiClient::Connection::Oauth.new("http://google.com")
|
7
|
+
instance.handler.builder.handlers.collect(&:name).should == [
|
8
|
+
"ApiClient::Connection::Middlewares::Request::OAuth",
|
9
|
+
"Faraday::Request::UrlEncoded",
|
10
|
+
"Faraday::Adapter::NetHttp"
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "adds the logger middlewares to faraday if ApiClient.logger is available" do
|
15
|
+
logger = mock
|
16
|
+
ApiClient.stub(:logger).and_return(logger)
|
17
|
+
instance = ApiClient::Connection::Oauth.new("http://google.com")
|
18
|
+
instance.handler.builder.handlers.collect(&:name).should == [
|
19
|
+
"ApiClient::Connection::Middlewares::Request::Logger",
|
20
|
+
"ApiClient::Connection::Middlewares::Request::OAuth",
|
21
|
+
"Faraday::Request::UrlEncoded",
|
22
|
+
"Faraday::Adapter::NetHttp"
|
23
|
+
]
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Connection::Middlewares::Request::Logger do
|
4
|
+
|
5
|
+
it "adds a oauth header to the request" do
|
6
|
+
app = mock
|
7
|
+
logger = FakeLogger.new
|
8
|
+
instance = ApiClient::Connection::Middlewares::Request::Logger.new(app, logger)
|
9
|
+
env = {
|
10
|
+
:url => "http://api.twitter.com",
|
11
|
+
:request_headers => {},
|
12
|
+
:method => 'get'
|
13
|
+
}
|
14
|
+
app.should_receive(:call).with(env)
|
15
|
+
instance.call(env)
|
16
|
+
logger.history.first.should == "GET http://api.twitter.com: 0.0000 seconds"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Connection::Middlewares::Request::OAuth do
|
4
|
+
|
5
|
+
it "adds a oauth header to the request" do
|
6
|
+
app = mock
|
7
|
+
options = {
|
8
|
+
:token => 'TOKEN',
|
9
|
+
:token_secret => 'SECRET',
|
10
|
+
:consumer_key => 'CONSUMER_KEY',
|
11
|
+
:consumer_secret => 'CONSUMER_SECRET'
|
12
|
+
}
|
13
|
+
instance = ApiClient::Connection::Middlewares::Request::OAuth.new(app, options)
|
14
|
+
env = {
|
15
|
+
:url => "http://api.twitter.com",
|
16
|
+
:request_headers => {}
|
17
|
+
}
|
18
|
+
app.should_receive(:call).with(env)
|
19
|
+
|
20
|
+
instance.call(env)
|
21
|
+
env[:request_headers]['Authorization'].should match("OAuth")
|
22
|
+
env[:request_headers]['User-Agent'].should match("ApiClient gem")
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Resource::Base do
|
4
|
+
|
5
|
+
describe '.scope' do
|
6
|
+
|
7
|
+
it "is an instance of ApiClient::Resource::Scope" do
|
8
|
+
ApiClient::Resource::Base.scope.should be_an_instance_of(ApiClient::Resource::Scope)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
describe "persistence" do
|
15
|
+
|
16
|
+
before do
|
17
|
+
@instance = ApiClient::Resource::Base.new
|
18
|
+
@instance.id = 42
|
19
|
+
@instance.name = "Mike"
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#persisted?' do
|
23
|
+
|
24
|
+
it "returns true if id is present, false otherwise" do
|
25
|
+
@instance.id = 42
|
26
|
+
@instance.persisted?.should == true
|
27
|
+
@instance.id = nil
|
28
|
+
@instance.persisted?.should == false
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#save' do
|
34
|
+
|
35
|
+
it "creates a record if not persisted" do
|
36
|
+
@instance.id = nil
|
37
|
+
@instance.should_receive(:remote_create)
|
38
|
+
@instance.save
|
39
|
+
end
|
40
|
+
|
41
|
+
it "updates a record if not persisted" do
|
42
|
+
@instance.id = 42
|
43
|
+
@instance.should_receive(:remote_update)
|
44
|
+
@instance.save
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#destroy" do
|
50
|
+
|
51
|
+
it "delegates the destroy to the class" do
|
52
|
+
ApiClient::Resource::Base.should_receive(:destroy).with(42)
|
53
|
+
@instance.destroy
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#remote_update" do
|
59
|
+
|
60
|
+
it "delegates the update to the class" do
|
61
|
+
ApiClient::Resource::Base.should_receive(:update).with(42, "name" => "Mike")
|
62
|
+
@instance.remote_update
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#remote_create" do
|
68
|
+
|
69
|
+
it "delegates the create to the class" do
|
70
|
+
ApiClient::Resource::Base.should_receive(:create).with("name" => "Mike")
|
71
|
+
@instance.remote_create
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Resource::Scope do
|
4
|
+
|
5
|
+
describe "restful requests" do
|
6
|
+
|
7
|
+
class Restful < ApiClient::Resource::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
class Restful2 < ApiClient::Resource::Base
|
11
|
+
namespace false
|
12
|
+
end
|
13
|
+
|
14
|
+
class Restful3 < ApiClient::Resource::Base
|
15
|
+
prefix "v1"
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
@instance = ApiClient::Resource::Scope.new(Restful)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "performs a find to fetch one record" do
|
23
|
+
response = { "restful" => { "id" => 42 }}
|
24
|
+
@instance.should_receive(:get).with('/restfuls/1.json').and_return(response)
|
25
|
+
result = @instance.find(1)
|
26
|
+
result.should be_an_instance_of(Restful)
|
27
|
+
result.id.should == 42
|
28
|
+
end
|
29
|
+
|
30
|
+
it "performs a find to fetch one record with a prefix if provided" do
|
31
|
+
@instance = ApiClient::Resource::Scope.new(Restful3)
|
32
|
+
response = { "restful3" => { "id" => 42 }}
|
33
|
+
@instance.should_receive(:get).with('/v1/restful3s/1.json').and_return(response)
|
34
|
+
result = @instance.find(1)
|
35
|
+
result.should be_an_instance_of(Restful3)
|
36
|
+
result.id.should == 42
|
37
|
+
end
|
38
|
+
|
39
|
+
it "performs a find_all to fetch many records" do
|
40
|
+
response = [{ "restful" => { "id" => 42 } }, { "restful" => { "id" => 112 } }]
|
41
|
+
@instance.should_receive(:get).with('/restfuls.json', {}).and_return(response)
|
42
|
+
result = @instance.find_all
|
43
|
+
|
44
|
+
result.should be_an_instance_of(Array)
|
45
|
+
result.first.should be_an_instance_of(Restful)
|
46
|
+
result.first.id.should == 42
|
47
|
+
result.last.should be_an_instance_of(Restful)
|
48
|
+
result.last.id.should == 112
|
49
|
+
end
|
50
|
+
|
51
|
+
it "performs a create to create a new record" do
|
52
|
+
response = { "restful" => { "id" => 42, "name" => "Foo" }}
|
53
|
+
@instance.should_receive(:post).with('/restfuls.json', {"restful" => {:name => "Foo"} }).and_return(response)
|
54
|
+
result = @instance.create(:name => "Foo")
|
55
|
+
result.should be_an_instance_of(Restful)
|
56
|
+
result.id.should == 42
|
57
|
+
end
|
58
|
+
|
59
|
+
it "performs a create to create a new record skipping the namespace if it is not present" do
|
60
|
+
@instance = ApiClient::Resource::Scope.new(Restful2)
|
61
|
+
response = { "id" => 42, "name" => "Foo" }
|
62
|
+
@instance.should_receive(:post).with('/restful2s.json', {:name => "Foo"} ).and_return(response)
|
63
|
+
result = @instance.create(:name => "Foo")
|
64
|
+
result.should be_an_instance_of(Restful2)
|
65
|
+
result.id.should == 42
|
66
|
+
end
|
67
|
+
|
68
|
+
it "performs a update to update an existing record" do
|
69
|
+
response = { "restful" => { "id" => 42, "name" => "Foo" }}
|
70
|
+
@instance.should_receive(:put).with('/restfuls/42.json', {"restful" => {:name => "Foo"} }).and_return(response)
|
71
|
+
result = @instance.update(42, :name => "Foo")
|
72
|
+
result.should be_an_instance_of(Restful)
|
73
|
+
result.id.should == 42
|
74
|
+
end
|
75
|
+
|
76
|
+
it "performs a update to update an existing record skipping the namespace if it is not present" do
|
77
|
+
@instance = ApiClient::Resource::Scope.new(Restful2)
|
78
|
+
response = { "id" => 42, "name" => "Foo" }
|
79
|
+
@instance.should_receive(:put).with('/restful2s/42.json', {:name => "Foo"} ).and_return(response)
|
80
|
+
result = @instance.update(42, :name => "Foo")
|
81
|
+
result.should be_an_instance_of(Restful2)
|
82
|
+
result.id.should == 42
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
it "performs a destroy to remove a record" do
|
88
|
+
response = { "restful" => { "id" => 42, "name" => "Foo" }}
|
89
|
+
@instance.should_receive(:delete).with('/restfuls/42.json').and_return(response)
|
90
|
+
result = @instance.destroy(42)
|
91
|
+
result.should == true
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Scope do
|
4
|
+
|
5
|
+
describe 'default_scopes' do
|
6
|
+
|
7
|
+
it "runs the default scopes defined in the scopeable" do
|
8
|
+
class DefaultScopeTest < ApiClient::Base
|
9
|
+
always do
|
10
|
+
params :foo => 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
instance = ApiClient::Scope.new(DefaultScopeTest)
|
14
|
+
instance.params.should == { :foo => 1 }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#params" do
|
19
|
+
|
20
|
+
it "reads/writes the params and chains nicely" do
|
21
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
22
|
+
instance.params(:foo => 1).params(:moo => 10).should == instance
|
23
|
+
instance.params.should == { :foo => 1, :moo => 10 }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#headers" do
|
29
|
+
|
30
|
+
it "reads/writes the headers and chains nicely" do
|
31
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
32
|
+
instance.headers(:foo => 1).headers(:moo => 10).should == instance
|
33
|
+
instance.headers.should == { :foo => 1, :moo => 10 }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#options" do
|
39
|
+
|
40
|
+
it "reads/writes the headers and chains nicely" do
|
41
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
42
|
+
instance.options(:foo => 1).options(:moo => 10).should == instance
|
43
|
+
instance.options.should == { :foo => 1, :moo => 10 }
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "connection" do
|
49
|
+
|
50
|
+
it "retuns the connection based on the adapter" do
|
51
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
52
|
+
instance.connection.should be_an_instance_of ApiClient::Connection::Basic
|
53
|
+
end
|
54
|
+
|
55
|
+
it "raises an error if adapter was not found" do
|
56
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
57
|
+
lambda {
|
58
|
+
instance.adapter("foo").connection
|
59
|
+
}.should raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
it "executes connection hooks" do
|
63
|
+
AConnectionHook = mock
|
64
|
+
class ScopeConnectionHooksTest < ApiClient::Base
|
65
|
+
end
|
66
|
+
ScopeConnectionHooksTest.connection_hooks = [AConnectionHook]
|
67
|
+
instance = ApiClient::Scope.new(ScopeConnectionHooksTest)
|
68
|
+
AConnectionHook.should_receive(:call)
|
69
|
+
instance.connection
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "requests" do
|
75
|
+
|
76
|
+
before do
|
77
|
+
@path = "somepath"
|
78
|
+
@params = { :foo => 1 }
|
79
|
+
@headers = { 'token' => 'A' }
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_request(method)
|
83
|
+
connection = mock
|
84
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
85
|
+
instance.stub(:connection).and_return(connection)
|
86
|
+
response = Faraday::Response.new(:body => '{"a": "1"}')
|
87
|
+
connection.should_receive(method).with(@path, @params, @headers).and_return(response)
|
88
|
+
instance.params(@params).headers(@headers).send(method, @path)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can make any request" do
|
92
|
+
connection = mock
|
93
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
94
|
+
instance.stub(:connection).and_return(connection)
|
95
|
+
response = Faraday::Response.new(:body => '{"a": "1"}')
|
96
|
+
connection.should_receive(:get).with(@path, @params, @headers).and_return(response)
|
97
|
+
result = instance.params(@params).headers(@headers).request(:get, @path)
|
98
|
+
result.should == {"a"=> "1"}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "can make any request and get a raw response" do
|
102
|
+
connection = mock
|
103
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
104
|
+
instance.stub(:connection).and_return(connection)
|
105
|
+
response = Faraday::Response.new(:body => '{"a": "1"}')
|
106
|
+
connection.should_receive(:get).with(@path, @params, @headers).and_return(response)
|
107
|
+
result = instance.params(@params).headers(@headers).request(:get, @path, :raw => true)
|
108
|
+
result.should == response
|
109
|
+
end
|
110
|
+
|
111
|
+
it "makes a GET request" do
|
112
|
+
result = test_request :get
|
113
|
+
result.should == {"a"=> "1"}
|
114
|
+
end
|
115
|
+
|
116
|
+
it "makes a POST request" do
|
117
|
+
result = test_request :post
|
118
|
+
result.should == {"a"=> "1"}
|
119
|
+
end
|
120
|
+
|
121
|
+
it "makes a PUT request" do
|
122
|
+
result = test_request :put
|
123
|
+
result.should == {"a"=> "1"}
|
124
|
+
end
|
125
|
+
|
126
|
+
it "makes a PUT request" do
|
127
|
+
result = test_request :delete
|
128
|
+
result.should == {"a"=> "1"}
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "fetch" do
|
132
|
+
|
133
|
+
it "performs a get and builds an object" do
|
134
|
+
connection = mock
|
135
|
+
instance = ApiClient::Scope.new(ApiClient::Base)
|
136
|
+
instance.stub(:connection).and_return(connection)
|
137
|
+
response = Faraday::Response.new(:body => '{"id": 42}')
|
138
|
+
connection.should_receive(:get).with(@path, @params, @headers).and_return(response)
|
139
|
+
result = instance.params(@params).headers(@headers).fetch(@path)
|
140
|
+
result.should be_an_instance_of(ApiClient::Base)
|
141
|
+
result.id.should == 42
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "dynamic delegation of scopeable singleton methods" do
|
149
|
+
|
150
|
+
it "dynamically delegates and properly scopes" do
|
151
|
+
class DynamicDelegationTest < ApiClient::Base
|
152
|
+
def self.some_method
|
153
|
+
self.scope.params
|
154
|
+
end
|
155
|
+
end
|
156
|
+
scope = ApiClient::Scope.new(DynamicDelegationTest)
|
157
|
+
scope.params(:param => "aaa").some_method.should == { :param => "aaa" }
|
158
|
+
end
|
159
|
+
|
160
|
+
it "raises an error if scopeable does not implement the method" do
|
161
|
+
scope = ApiClient::Scope.new(ApiClient::Base)
|
162
|
+
lambda {
|
163
|
+
scope.some_method_the_class_does_not_have
|
164
|
+
}.should raise_error(NoMethodError)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Utils do
|
4
|
+
|
5
|
+
describe '.deep_merge' do
|
6
|
+
|
7
|
+
it "merges two hashes updating the first one" do
|
8
|
+
hash_a = { :a => 1, :b => 2 }
|
9
|
+
hash_b = { :b => 3, :c => 45 }
|
10
|
+
ApiClient::Utils.deep_merge hash_a, hash_b
|
11
|
+
hash_a.should == { :a => 1, :b => 3, :c=>45 }
|
12
|
+
hash_b.should == { :b => 3, :c => 45 }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "deeply merges two hashes recursively" do
|
16
|
+
hash_a = { :a => { :foo => 2, :boo => { :wat => 'wat' } }, :b => 2 }
|
17
|
+
hash_b = { :b => 3, :c => 45, :a => { :boo => { :wat => "WAT????" } } }
|
18
|
+
ApiClient::Utils.deep_merge hash_a, hash_b
|
19
|
+
hash_a.should == { :a => { :foo => 2, :boo => { :wat => 'WAT????' } }, :b => 3, :c => 45 }
|
20
|
+
end
|
21
|
+
|
22
|
+
it "require correct key type" do
|
23
|
+
hash_a = { :a => 1 }
|
24
|
+
hash_b = { 'a' => 2 }
|
25
|
+
ApiClient::Utils.deep_merge hash_a, hash_b
|
26
|
+
hash_a.should == { :a => 1, 'a' => 2 }
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|