api_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE +22 -0
  5. data/README.md +13 -0
  6. data/Rakefile +1 -0
  7. data/api_client.gemspec +24 -0
  8. data/examples/digg.rb +31 -0
  9. data/examples/flickr.rb +37 -0
  10. data/examples/github.rb +52 -0
  11. data/examples/highrise.rb +41 -0
  12. data/examples/twitter.rb +34 -0
  13. data/examples/twitter_oauth.rb +36 -0
  14. data/lib/api_client.rb +45 -0
  15. data/lib/api_client/base.rb +52 -0
  16. data/lib/api_client/connection/abstract.rb +73 -0
  17. data/lib/api_client/connection/basic.rb +105 -0
  18. data/lib/api_client/connection/middlewares/request/logger.rb +17 -0
  19. data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
  20. data/lib/api_client/connection/oauth.rb +18 -0
  21. data/lib/api_client/errors.rb +16 -0
  22. data/lib/api_client/mixins/configuration.rb +24 -0
  23. data/lib/api_client/mixins/connection_hooks.rb +24 -0
  24. data/lib/api_client/mixins/delegation.rb +23 -0
  25. data/lib/api_client/mixins/inheritance.rb +19 -0
  26. data/lib/api_client/mixins/instantiation.rb +35 -0
  27. data/lib/api_client/mixins/scoping.rb +49 -0
  28. data/lib/api_client/resource/base.rb +63 -0
  29. data/lib/api_client/resource/scope.rb +73 -0
  30. data/lib/api_client/scope.rb +101 -0
  31. data/lib/api_client/utils.rb +18 -0
  32. data/lib/api_client/version.rb +3 -0
  33. data/spec/api_client/base/connection_hook_spec.rb +18 -0
  34. data/spec/api_client/base/delegation_spec.rb +15 -0
  35. data/spec/api_client/base/inheritance_spec.rb +44 -0
  36. data/spec/api_client/base/instantiation_spec.rb +54 -0
  37. data/spec/api_client/base/parsing_spec.rb +36 -0
  38. data/spec/api_client/base/scoping_spec.rb +60 -0
  39. data/spec/api_client/base_spec.rb +17 -0
  40. data/spec/api_client/connection/abstract_spec.rb +21 -0
  41. data/spec/api_client/connection/basic_spec.rb +135 -0
  42. data/spec/api_client/connection/oauth_spec.rb +27 -0
  43. data/spec/api_client/connection/request/logger_spec.rb +19 -0
  44. data/spec/api_client/connection/request/oauth_spec.rb +26 -0
  45. data/spec/api_client/resource/base_spec.rb +78 -0
  46. data/spec/api_client/resource/scope_spec.rb +96 -0
  47. data/spec/api_client/scope_spec.rb +170 -0
  48. data/spec/api_client/utils_spec.rb +32 -0
  49. data/spec/spec_helper.rb +13 -0
  50. data/spec/support/fake_logger.rb +15 -0
  51. data/spec/support/matchers.rb +5 -0
  52. 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