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.
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