api-model 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_models/banana'
3
+ require 'support/mock_models/multiple_hosts'
4
+
5
+ describe ApiModel, "Configuration" do
6
+
7
+ after(:each) do
8
+ Banana.reset_api_configuration
9
+ end
10
+
11
+ describe "api_host" do
12
+ it "should set the api host for all classes which inherit ApiModel::Base" do
13
+ ApiModel::Base.api_config do |config|
14
+ config.host = "foobarbaz.com"
15
+ end
16
+
17
+ Banana.api_model_configuration.host.should eq "foobarbaz.com"
18
+ end
19
+
20
+ it "should not override different classes configurations" do
21
+ MultipleHostsFoo.api_model_configuration.host.should eq("http://foo.com")
22
+ MultipleHostsBar.api_model_configuration.host.should eq("http://bar.com")
23
+ MultipleHostsNone.api_model_configuration.host.should be_nil
24
+ end
25
+ end
26
+
27
+ describe "json_root" do
28
+ it 'should be possible to set on a class' do
29
+ Banana.api_config do |config|
30
+ config.json_root = "foo_bar"
31
+ end
32
+
33
+ Banana.api_model_configuration.json_root.should eq "foo_bar"
34
+ end
35
+ end
36
+
37
+ describe "headers" do
38
+ it 'should create default headers for content type and accepts' do
39
+ headers = Banana.api_model_configuration.headers
40
+ headers["Content-Type"].should eq "application/json; charset=utf-8"
41
+ headers["Accept"].should eq "application/json"
42
+ end
43
+
44
+ it 'should be possible to set new headers' do
45
+ ApiModel::Base.api_config { |config| config.headers = { foo: "bar" } }
46
+ Banana.api_model_configuration.headers[:foo].should eq "bar"
47
+ end
48
+
49
+ it 'should retain the default headers when you add a new one' do
50
+ ApiModel::Base.api_config { |config| config.headers = { foo: "bar" } }
51
+
52
+ headers = Banana.api_model_configuration.headers
53
+ headers.should have_key "Accept"
54
+ headers.should have_key "Content-Type"
55
+ end
56
+
57
+ it 'should be possible to override default headers' do
58
+ ApiModel::Base.api_config { |config| config.headers = { "Accept" => "image/gif" } }
59
+ Banana.api_model_configuration.headers["Accept"].should eq "image/gif"
60
+ end
61
+ end
62
+
63
+ it 'should not unset other config values when you set a new one' do
64
+ ApiModel::Base.api_config { |c| c.host = "foo.com" }
65
+ Banana.api_config { |c| c.json_root = "banana" }
66
+
67
+ Banana.api_model_configuration.host.should eq "foo.com"
68
+ Banana.api_model_configuration.json_root.should eq "banana"
69
+ end
70
+
71
+ it 'should override config values from the superclass if it is changed' do
72
+ ApiModel::Base.api_config { |c| c.host = "will-go.com" }
73
+ Banana.api_config { |c| c.host = "new-host.com" }
74
+
75
+ Banana.api_model_configuration.host.should eq "new-host.com"
76
+ end
77
+
78
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_models/blog_post'
3
+
4
+ describe ApiModel::HttpRequest do
5
+
6
+ describe "default attributes" do
7
+ subject { ApiModel::HttpRequest.new }
8
+
9
+ it "should default #method to :get" do
10
+ subject.method.should eq :get
11
+ end
12
+
13
+ it "should default #options to a hash with headers" do
14
+ subject.options.should be_a Hash
15
+ subject.options[:headers].should_not be_nil
16
+ end
17
+ end
18
+
19
+ describe "callbacks" do
20
+ class ApiModel::HttpRequest
21
+ before_run :do_something_before_run
22
+ def do_something_before_run; end
23
+ end
24
+
25
+ it 'should be possible to set callbacks on the run method' do
26
+ ApiModel::HttpRequest.any_instance.should_receive(:do_something_before_run).once
27
+ VCR.use_cassette('posts') { BlogPost.get_json "http://api-model-specs.com/single_post"}
28
+ end
29
+ end
30
+
31
+ describe "using api_host" do
32
+ let(:blog_post) do
33
+ BlogPost.api_config do |config|
34
+ config.host = "http://api-model-specs.com"
35
+ end
36
+
37
+ VCR.use_cassette('posts') do
38
+ BlogPost.get_json "/single_post"
39
+ end
40
+ end
41
+
42
+ it "should be used with #path to generate a #full_path" do
43
+ blog_post.http_response.api_call.request.url.should eq "http://api-model-specs.com/single_post"
44
+ end
45
+ end
46
+
47
+ describe "headers" do
48
+ let :request_headers do
49
+ BlogPost.api_config { |config| config.host = "http://api-model-specs.com" }
50
+ blog_post = VCR.use_cassette('posts') { BlogPost.get_json "/single_post" }
51
+ blog_post.http_response.api_call.request.options[:headers]
52
+ end
53
+
54
+ it 'should use the default content type header' do
55
+ request_headers["Content-Type"].should eq ApiModel::Configuration.new.headers["Content-Type"]
56
+ end
57
+
58
+ it 'should use the default accept header' do
59
+ request_headers["Accept"].should eq ApiModel::Configuration.new.headers["Accept"]
60
+ end
61
+ end
62
+
63
+ describe "sending a GET request" do
64
+ let(:request) { ApiModel::HttpRequest.new path: "http://api-model-specs.com/posts", method: :get }
65
+
66
+ it "should use typhoeus to send a request" do
67
+ VCR.use_cassette('posts') do
68
+ request.run
69
+ end
70
+
71
+ request.api_call.success?.should eq true
72
+ end
73
+ end
74
+ end
@@ -3,6 +3,10 @@ require 'support/mock_models/banana'
3
3
 
4
4
  describe ApiModel::Initializer do
5
5
 
6
+ Banana.class_eval do
7
+ include ApiModel::Initializer
8
+ end
9
+
6
10
  let(:banana) { Banana.new color: "yellow", size: "large" }
7
11
 
8
12
  it "should set attributes when initializing with a hash" do
@@ -16,16 +20,12 @@ describe ApiModel::Initializer do
16
20
  expect(banana.size).to eq "small"
17
21
  end
18
22
 
19
- it "should not blog up if update_attributes is called with nil" do
23
+ it "should not blow up if update_attributes is called with nil" do
20
24
  expect {
21
25
  banana.update_attributes nil
22
26
  }.to_not raise_error
23
27
  end
24
28
 
25
- it "should run callbacks on initialize" do
26
- banana.ripe.should eq true
27
- end
28
-
29
29
  it "should log if an attempt was made to set an attribute which is not defined" do
30
30
  ApiModel::Log.should_receive(:debug).with "Could not set foo on Banana"
31
31
  Banana.new foo: "bar"
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_models/blog_post'
3
+ require 'support/mock_models/user'
4
+
5
+ describe ApiModel::Response do
6
+
7
+ let(:valid_response) do
8
+ VCR.use_cassette('posts') do
9
+ ApiModel::HttpRequest.new(path: "http://api-model-specs.com/single_post", method: :get, builder: BlogPost).run
10
+ end
11
+ end
12
+
13
+ describe "parsing the json body" do
14
+ it "should produce a hash given valid json" do
15
+ valid_response.json_response_body.should be_a(Hash)
16
+ valid_response.json_response_body["name"].should eq "foo"
17
+ end
18
+
19
+ it "should catch errors from parsing invalid json" do
20
+ valid_response.stub_chain(:http_response, :api_call, :body).and_return "blah"
21
+ ApiModel::Log.should_receive(:info).with "Could not parse JSON response: blah"
22
+
23
+ expect {
24
+ valid_response.json_response_body
25
+ }.to_not raise_error
26
+ end
27
+ end
28
+
29
+ describe "using a custom json root on the response body" do
30
+ let :users do
31
+ User.api_config do |c|
32
+ c.json_root = "users"
33
+ end
34
+ VCR.use_cassette('users') { User.get_json "http://api-model-specs.com/users" }
35
+ end
36
+
37
+ it 'should use the json root to build from' do
38
+ users.should be_a Array
39
+ users.size.should eq 3
40
+
41
+ users.each do |user|
42
+ user.should be_a User
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "using a multi-level json root on the response body" do
48
+ let :user_search do
49
+ VCR.use_cassette('users') { User.get_json "http://api-model-specs.com/search" }
50
+ end
51
+
52
+ it 'should use the deep json root to build from' do
53
+ User.api_config { |c| c.json_root = "search.results.users" }
54
+
55
+ user_search.should be_a Array
56
+ user_search.size.should eq 3
57
+
58
+ user_search.each do |user|
59
+ user.should be_a User
60
+ end
61
+ end
62
+
63
+ it 'should raise a ApiModel::ResponseBuilderError exception if the hash does not contain the key' do
64
+ User.api_config { |c| c.json_root = "search.results.users.foo" }
65
+
66
+ expect {
67
+ user_search
68
+ }.to raise_error(ApiModel::ResponseBuilderError)
69
+ end
70
+ end
71
+
72
+ describe "#build" do
73
+ it "should use the builder.build method if present" do
74
+ builder = double
75
+ builder.should_receive(:build).with something: "foo"
76
+
77
+ valid_response.build builder, something: "foo"
78
+ end
79
+
80
+ it "should use builder.new if there's no builder.build method" do
81
+ builder = double
82
+ builder.should_receive(:new).with something_else: "hi"
83
+
84
+ valid_response.build builder, something_else: "hi"
85
+ end
86
+ end
87
+
88
+ describe "#build_objects" do
89
+ let(:single_object) do
90
+ valid_response.stub(:json_response_body).and_return name: "foo"
91
+ valid_response.build_objects
92
+ end
93
+
94
+ let(:array_of_objects) do
95
+ valid_response.stub(:json_response_body).and_return [{name: "foo"}, {name: "bar"}]
96
+ valid_response.build_objects
97
+ end
98
+
99
+ let(:empty_response) do
100
+ valid_response.stub(:json_response_body).and_return nil
101
+ valid_response.build_objects
102
+ end
103
+
104
+ it "should build a single object" do
105
+ single_object.should be_a(BlogPost)
106
+ single_object.name.should eq "foo"
107
+ end
108
+
109
+ it "should build an array of objects" do
110
+ array_of_objects[0].should be_a(BlogPost)
111
+ array_of_objects[0].name.should eq "foo"
112
+
113
+ array_of_objects[1].should be_a(BlogPost)
114
+ array_of_objects[1].name.should eq "bar"
115
+ end
116
+
117
+ it "should include the ApiModel::HttpRequest object" do
118
+ single_object.http_response.should be_a(ApiModel::HttpRequest)
119
+ end
120
+
121
+ it "should include the #json_response_body" do
122
+ single_object.json_response_body.should eq name: "foo"
123
+ end
124
+
125
+ it 'should return nil if the api returns an empty body' do
126
+ empty_response.should be_nil
127
+ end
128
+ end
129
+
130
+ describe "passing core methods down to the built class" do
131
+ ApiModel::Response::FALL_THROUGH_METHODS.each do |fall_trhough_method|
132
+ it "should pass ##{fall_trhough_method} on the built object class" do
133
+ allow_message_expectations_on_nil
134
+ valid_response.objects.should_receive(fall_trhough_method)
135
+ valid_response.send fall_trhough_method
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "raising exceptions" do
141
+ describe "for requests which return a 401" do
142
+ let :api_request do
143
+ VCR.use_cassette('errors') do
144
+ BlogPost.get_json "http://api-model-specs.com/needs_auth"
145
+ end
146
+ end
147
+
148
+ it 'should raise an ApiModel::UnauthenticatedError if raise_on_unauthenticated is true' do
149
+ BlogPost.api_config { |c| c.raise_on_unauthenticated = true }
150
+ expect {
151
+ api_request
152
+ }.to raise_error(ApiModel::UnauthenticatedError)
153
+ end
154
+
155
+ it 'should not raise an ApiModel::UnauthenticatedError if raise_on_unauthenticated is false' do
156
+ BlogPost.api_config { |c| c.raise_on_unauthenticated = false }
157
+ expect {
158
+ api_request
159
+ }.to_not raise_error
160
+ end
161
+ end
162
+
163
+ describe "for requests which return a 404" do
164
+ let :api_request do
165
+ VCR.use_cassette('errors') do
166
+ BlogPost.get_json "http://api-model-specs.com/not_found"
167
+ end
168
+ end
169
+
170
+ it 'should raise an ApiModel::NotFoundError if raise_on_not_found is true' do
171
+ BlogPost.api_config { |c| c.raise_on_not_found = true }
172
+ expect {
173
+ api_request
174
+ }.to raise_error(ApiModel::NotFoundError)
175
+ end
176
+
177
+ it 'should not raise an ApiModel::NotFoundError if raise_on_not_found is false' do
178
+ BlogPost.api_config { |c| c.raise_on_not_found = false }
179
+ expect {
180
+ api_request
181
+ }.to_not raise_error
182
+ end
183
+ end
184
+ end
185
+
186
+ end
@@ -8,4 +8,13 @@ require 'api-model'
8
8
  VCR.configure do |c|
9
9
  c.cassette_library_dir = 'spec/support/fixtures'
10
10
  c.hook_into :webmock # or :fakeweb
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+
15
+ # Reset any config changes after each spec
16
+ config.after(:each) do
17
+ ApiModel::Base.reset_api_configuration
18
+ end
19
+
11
20
  end
@@ -0,0 +1,84 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://cars.com/one_convertable
6
+ headers:
7
+ User-Agent:
8
+ - Typhoeus - https://github.com/typhoeus/typhoeus
9
+ response:
10
+ status:
11
+ code: 200
12
+ message: OK
13
+ headers:
14
+ Server:
15
+ - nginx/1.4.1
16
+ Date:
17
+ - Thu, 28 Nov 2013 16:02:56 GMT
18
+ Content-Type:
19
+ - text/plain; charset=utf-8
20
+ Content-Length:
21
+ - '248'
22
+ Connection:
23
+ - keep-alive
24
+ body:
25
+ encoding: UTF-8
26
+ string: "{\"numberOfDoors\":2,\"top_speed\":60}"
27
+ http_version:
28
+ recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
29
+
30
+ - request:
31
+ method: get
32
+ uri: http://cars.com/fast_ones
33
+ headers:
34
+ User-Agent:
35
+ - Typhoeus - https://github.com/typhoeus/typhoeus
36
+ response:
37
+ status:
38
+ code: 200
39
+ message: OK
40
+ headers:
41
+ Server:
42
+ - nginx/1.4.1
43
+ Date:
44
+ - Thu, 28 Nov 2013 16:02:56 GMT
45
+ Content-Type:
46
+ - text/plain; charset=utf-8
47
+ Content-Length:
48
+ - '248'
49
+ Connection:
50
+ - keep-alive
51
+ body:
52
+ encoding: UTF-8
53
+ string: "[{\"numberOfDoors\":2,\"top_speed\":60},{\"numberOfDoors\":4,\"top_speed\":30,\"name\":\"Ford\"}]"
54
+ http_version:
55
+ recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
56
+
57
+ - request:
58
+ method: get
59
+ uri: http://cars.com/new_model
60
+ headers:
61
+ User-Agent:
62
+ - Typhoeus - https://github.com/typhoeus/typhoeus
63
+ response:
64
+ status:
65
+ code: 200
66
+ message: OK
67
+ headers:
68
+ Server:
69
+ - nginx/1.4.1
70
+ Date:
71
+ - Thu, 28 Nov 2013 16:02:56 GMT
72
+ Content-Type:
73
+ - text/plain; charset=utf-8
74
+ Content-Length:
75
+ - '248'
76
+ Connection:
77
+ - keep-alive
78
+ body:
79
+ encoding: UTF-8
80
+ string: "{\"numberOfDoors\":2,\"top_speed\":60,\"shiney\":true}"
81
+ http_version:
82
+ recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
83
+
84
+ recorded_with: VCR 2.8.0