ddy_remote_resource 0.4.2

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +3 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +182 -0
  10. data/Rakefile +7 -0
  11. data/lib/extensions/ethon/easy/queryable.rb +36 -0
  12. data/lib/remote_resource.rb +64 -0
  13. data/lib/remote_resource/base.rb +126 -0
  14. data/lib/remote_resource/builder.rb +53 -0
  15. data/lib/remote_resource/collection.rb +31 -0
  16. data/lib/remote_resource/connection.rb +24 -0
  17. data/lib/remote_resource/connection_options.rb +41 -0
  18. data/lib/remote_resource/http_errors.rb +33 -0
  19. data/lib/remote_resource/querying/finder_methods.rb +34 -0
  20. data/lib/remote_resource/querying/persistence_methods.rb +38 -0
  21. data/lib/remote_resource/request.rb +106 -0
  22. data/lib/remote_resource/response.rb +69 -0
  23. data/lib/remote_resource/response_handeling.rb +48 -0
  24. data/lib/remote_resource/rest.rb +29 -0
  25. data/lib/remote_resource/url_naming.rb +34 -0
  26. data/lib/remote_resource/url_naming_determination.rb +39 -0
  27. data/lib/remote_resource/version.rb +3 -0
  28. data/remote_resource.gemspec +32 -0
  29. data/spec/lib/extensions/ethon/easy/queryable_spec.rb +135 -0
  30. data/spec/lib/remote_resource/base_spec.rb +388 -0
  31. data/spec/lib/remote_resource/builder_spec.rb +245 -0
  32. data/spec/lib/remote_resource/collection_spec.rb +148 -0
  33. data/spec/lib/remote_resource/connection_options_spec.rb +124 -0
  34. data/spec/lib/remote_resource/connection_spec.rb +61 -0
  35. data/spec/lib/remote_resource/querying/finder_methods_spec.rb +105 -0
  36. data/spec/lib/remote_resource/querying/persistence_methods_spec.rb +174 -0
  37. data/spec/lib/remote_resource/request_spec.rb +594 -0
  38. data/spec/lib/remote_resource/response_spec.rb +196 -0
  39. data/spec/lib/remote_resource/rest_spec.rb +98 -0
  40. data/spec/lib/remote_resource/url_naming_determination_spec.rb +225 -0
  41. data/spec/lib/remote_resource/url_naming_spec.rb +72 -0
  42. data/spec/lib/remote_resource/version_spec.rb +8 -0
  43. data/spec/spec_helper.rb +4 -0
  44. metadata +242 -0
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteResource::Connection do
4
+
5
+ module RemoteResource
6
+ class ConnectionDummy
7
+ include RemoteResource::Base
8
+
9
+ self.site = 'https://foobar.com'
10
+
11
+ end
12
+ end
13
+
14
+ let(:dummy_class) { RemoteResource::ConnectionDummy }
15
+ let(:dummy) { dummy_class.new }
16
+
17
+ describe '.connection' do
18
+ it 'uses Typhoeus::Request' do
19
+ expect(dummy_class.connection).to eql Typhoeus::Request
20
+ end
21
+ end
22
+
23
+ describe '.content_type' do
24
+ let!(:original_content_type) { dummy_class.content_type }
25
+
26
+ context 'when content_type is set' do
27
+ it 'returns the given content_type' do
28
+ dummy_class.content_type = '.html'
29
+
30
+ expect(dummy_class.content_type).to eql '.html'
31
+
32
+ dummy_class.content_type = original_content_type
33
+ end
34
+ end
35
+
36
+ context 'when NO content_type is set' do
37
+ it 'returns the default content_type' do
38
+ expect(dummy_class.content_type).to eql '.json'
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.headers' do
44
+ context 'when .extra_headers are set' do
45
+ it 'returns the default headers merged with the set .extra_headers' do
46
+ dummy_class.extra_headers = { "Foo" => "Bar" }
47
+
48
+ expect(dummy_class.headers).to eql({ "Accept" => "application/json", "Foo" => "Bar" })
49
+
50
+ dummy_class.extra_headers = nil
51
+ end
52
+ end
53
+
54
+ context 'when NO .extra_headers are set' do
55
+ it 'returns the default headers' do
56
+ expect(dummy_class.headers).to eql({ "Accept" => "application/json" })
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteResource::Querying::FinderMethods do
4
+
5
+ module RemoteResource
6
+ module Querying
7
+ class FinderMethodsDummy
8
+ include RemoteResource::Base
9
+
10
+ self.site = 'https://foobar.com'
11
+
12
+ end
13
+ end
14
+ end
15
+
16
+ let(:dummy_class) { RemoteResource::Querying::FinderMethodsDummy }
17
+ let(:dummy) { dummy_class.new }
18
+
19
+ describe '.find' do
20
+ let(:response) { instance_double(RemoteResource::Response) }
21
+
22
+ before do
23
+ allow(dummy_class).to receive(:build_resource_from_response) { dummy }
24
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
25
+ end
26
+
27
+ it 'performs a RemoteResource::Request with the connection_options no_params' do
28
+ expect(RemoteResource::Request).to receive(:new).with(dummy_class, :get, { id: '12' }, { no_params: true }).and_call_original
29
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
30
+ dummy_class.find '12'
31
+ end
32
+
33
+ it 'builds the resource from the RemoteResource::Response' do
34
+ expect(dummy_class).to receive(:build_resource_from_response).with response
35
+ dummy_class.find '12'
36
+ end
37
+ end
38
+
39
+ describe '.find_by' do
40
+ let(:response) { instance_double(RemoteResource::Response) }
41
+ let(:params) do
42
+ { id: '12' }
43
+ end
44
+
45
+ before do
46
+ allow(dummy_class).to receive(:build_resource_from_response) { dummy }
47
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
48
+ end
49
+
50
+ it 'performs a RemoteResource::Request' do
51
+ expect(RemoteResource::Request).to receive(:new).with(dummy_class, :get, params, {}).and_call_original
52
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
53
+ dummy_class.find_by params
54
+ end
55
+
56
+ it 'builds the resource from the RemoteResource::Response' do
57
+ expect(dummy_class).to receive(:build_resource_from_response).with response
58
+ dummy_class.find_by params
59
+ end
60
+ end
61
+
62
+ describe '.all' do
63
+ let(:response) { instance_double(RemoteResource::Response) }
64
+
65
+ before do
66
+ allow(dummy_class).to receive(:build_collection_from_response) { dummy }
67
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
68
+ end
69
+
70
+ it 'performs a RemoteResource::Request with the connection_options collection' do
71
+ expect(RemoteResource::Request).to receive(:new).with(dummy_class, :get, {}, { collection: true }).and_call_original
72
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
73
+ dummy_class.all
74
+ end
75
+
76
+ it 'builds the resources from the RemoteResource::Response' do
77
+ expect(dummy_class).to receive(:build_collection_from_response).with response
78
+ dummy_class.all
79
+ end
80
+ end
81
+
82
+ describe '.where' do
83
+ let(:response) { instance_double(RemoteResource::Response) }
84
+ let(:params) do
85
+ { username: 'mies' }
86
+ end
87
+
88
+ before do
89
+ allow(dummy_class).to receive(:build_collection_from_response) { dummy }
90
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
91
+ end
92
+
93
+ it 'performs a RemoteResource::Request with the connection_options collection' do
94
+ expect(RemoteResource::Request).to receive(:new).with(dummy_class, :get, params, { collection: true }).and_call_original
95
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
96
+ dummy_class.where params
97
+ end
98
+
99
+ it 'builds the resources from the RemoteResource::Response' do
100
+ expect(dummy_class).to receive(:build_collection_from_response).with response
101
+ dummy_class.where params
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteResource::Querying::PersistenceMethods do
4
+
5
+ module RemoteResource
6
+ module Querying
7
+ class PersistenceMethodsDummy
8
+ include RemoteResource::Base
9
+
10
+ self.site = 'https://foobar.com'
11
+
12
+ attribute :name
13
+
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:dummy_class) { RemoteResource::Querying::PersistenceMethodsDummy }
19
+ let(:dummy) { dummy_class.new }
20
+
21
+ describe '.create' do
22
+ let(:response) { instance_double(RemoteResource::Response) }
23
+ let(:attributes) do
24
+ { name: 'Mies' }
25
+ end
26
+
27
+ before do
28
+ allow_any_instance_of(dummy_class).to receive(:handle_response)
29
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
30
+ end
31
+
32
+ it 'instantiates the resource with the attributes' do
33
+ expect(dummy_class).to receive(:new).with(attributes).and_call_original
34
+ dummy_class.create attributes
35
+ end
36
+
37
+ it 'performs a RemoteResource::Request' do
38
+ expect(RemoteResource::Request).to receive(:new).with(dummy_class, :post, attributes, {}).and_call_original
39
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
40
+ dummy_class.create attributes
41
+ end
42
+
43
+ it 'handles the RemoteResource::Response' do
44
+ expect_any_instance_of(dummy_class).to receive(:handle_response).with response
45
+ dummy_class.create attributes
46
+ end
47
+ end
48
+
49
+ describe '#update_attributes' do
50
+ let(:dummy) { dummy_class.new id: 10 }
51
+
52
+ let(:attributes) do
53
+ { name: 'Noot' }
54
+ end
55
+
56
+ before do
57
+ allow(dummy).to receive(:create_or_update) { dummy }
58
+ allow(dummy).to receive(:success?)
59
+ end
60
+
61
+ it 'rebuilds the resource with the attributes' do
62
+ expect(dummy).to receive(:rebuild_resource).with(attributes).and_call_original
63
+ dummy.update_attributes attributes
64
+ end
65
+
66
+ context 'when the id is given in the attributes' do
67
+ let(:attributes) do
68
+ { id: 14, name: 'Noot' }
69
+ end
70
+
71
+ it 'calls #create_or_update with the attributes and given id' do
72
+ expect(dummy).to receive(:create_or_update).with(attributes, {})
73
+ dummy.update_attributes attributes
74
+ end
75
+ end
76
+
77
+ context 'when the id is NOT given in the attributes' do
78
+ it 'calls #create_or_update with the attributes and #id of resource' do
79
+ expect(dummy).to receive(:create_or_update).with(attributes.merge(id: dummy.id), {})
80
+ dummy.update_attributes attributes
81
+ end
82
+ end
83
+
84
+ context 'when the save was successful' do
85
+ it 'returns the resource' do
86
+ allow(dummy).to receive(:success?) { true }
87
+
88
+ expect(dummy.update_attributes attributes).to eql dummy
89
+ end
90
+ end
91
+
92
+ context 'when the save was NOT successful' do
93
+ it 'returns false' do
94
+ allow(dummy).to receive(:success?) { false }
95
+
96
+ expect(dummy.update_attributes attributes).to eql false
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '#save' do
102
+ let(:attributes) { dummy.attributes }
103
+
104
+ before do
105
+ allow(dummy).to receive(:create_or_update) { dummy }
106
+ allow(dummy).to receive(:success?)
107
+ end
108
+
109
+ it 'calls #create_or_update with the attributes' do
110
+ expect(dummy).to receive(:create_or_update).with(attributes, {})
111
+ dummy.save
112
+ end
113
+
114
+ context 'when the save was successful' do
115
+ it 'returns the resource' do
116
+ allow(dummy).to receive(:success?) { true }
117
+
118
+ expect(dummy.save).to eql dummy
119
+ end
120
+ end
121
+
122
+ context 'when the save was NOT successful' do
123
+ it 'returns false' do
124
+ allow(dummy).to receive(:success?) { false }
125
+
126
+ expect(dummy.save).to eql false
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#create_or_update' do
132
+ let(:response) { instance_double(RemoteResource::Response) }
133
+
134
+ before do
135
+ allow(dummy).to receive(:handle_response) { dummy }
136
+ allow_any_instance_of(RemoteResource::Request).to receive(:perform) { response }
137
+ end
138
+
139
+ context 'when the attributes contain an id' do
140
+ let(:attributes) do
141
+ { id: 10, name: 'Kees' }
142
+ end
143
+
144
+ it 'performs a RemoteResource::Request with rest_action :patch' do
145
+ expect(RemoteResource::Request).to receive(:new).with(dummy, :patch, attributes, {}).and_call_original
146
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
147
+ dummy.create_or_update attributes
148
+ end
149
+
150
+ it 'handles the RemoteResource::Response' do
151
+ expect(dummy).to receive(:handle_response).with response
152
+ dummy.create_or_update attributes
153
+ end
154
+ end
155
+
156
+ context 'when the attributes do NOT contain an id' do
157
+ let(:attributes) do
158
+ { name: 'Mies' }
159
+ end
160
+
161
+ it 'performs a RemoteResource::Request with rest_action :post' do
162
+ expect(RemoteResource::Request).to receive(:new).with(dummy, :post, attributes, {}).and_call_original
163
+ expect_any_instance_of(RemoteResource::Request).to receive(:perform)
164
+ dummy.create_or_update attributes
165
+ end
166
+
167
+ it 'handles the RemoteResource::Response' do
168
+ expect(dummy).to receive(:handle_response).with response
169
+ dummy.create_or_update attributes
170
+ end
171
+ end
172
+ end
173
+
174
+ end
@@ -0,0 +1,594 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteResource::Request do
4
+
5
+ module RemoteResource
6
+ class RequestDummy
7
+ include RemoteResource::Base
8
+
9
+ self.site = 'http://www.foobar.com'
10
+
11
+ attr_accessor :name
12
+
13
+ end
14
+ end
15
+
16
+ let(:dummy_class) { RemoteResource::RequestDummy }
17
+ let(:dummy) { dummy_class.new id: '12' }
18
+
19
+ let(:resource) { dummy_class }
20
+ let(:rest_action) { :get }
21
+ let(:connection_options) { {} }
22
+ let(:attributes) do
23
+ { name: 'Mies' }
24
+ end
25
+
26
+ let(:request) { described_class.new resource, rest_action, attributes, connection_options.dup }
27
+
28
+ specify { expect(described_class).to include RemoteResource::HTTPErrors }
29
+
30
+ describe '#connection' do
31
+ it 'uses the connection of the resource_klass' do
32
+ expect(request.connection).to eql Typhoeus::Request
33
+ end
34
+ end
35
+
36
+ describe '#connection_options' do
37
+ let(:threaded_connection_options_thread_name) { 'remote_resource.request_dummy.threaded_connection_options' }
38
+
39
+ before { Thread.current[threaded_connection_options_thread_name] = threaded_connection_options }
40
+ after { Thread.current[threaded_connection_options_thread_name] = nil }
41
+
42
+ context 'when the given connection_options contain other values than the resource threaded_connection_options or connection_options' do
43
+ let(:connection_options) do
44
+ {
45
+ site: 'http://www.barbaz.com',
46
+ collection: true,
47
+ path_prefix: '/api',
48
+ root_element: :bazbar
49
+ }
50
+ end
51
+
52
+ let(:threaded_connection_options) do
53
+ {
54
+ site: 'http://www.bazbazbaz.com',
55
+ path_prefix: '/registration',
56
+ path_postfix: '/promotion',
57
+ root_element: :bazbazbaz
58
+ }
59
+ end
60
+
61
+ it 'merges the given connection_options with the resource connection_options while taking precedence over the resource connection_options after the threaded_connection_options' do
62
+ expect(request.connection_options[:site]).to eql 'http://www.barbaz.com'
63
+ expect(request.connection_options[:collection]).to eql true
64
+ expect(request.connection_options[:path_prefix]).to eql '/api'
65
+ expect(request.connection_options[:path_postfix]).to eql '/promotion'
66
+ expect(request.connection_options[:root_element]).to eql :bazbar
67
+ end
68
+ end
69
+
70
+ context 'when the given connection_options do NOT contain other values than the resource threaded_connection_options or connection_options' do
71
+ let(:connection_options) do
72
+ {
73
+ collection: true,
74
+ path_prefix: '/api',
75
+ root_element: :bazbar
76
+ }
77
+ end
78
+
79
+ let(:threaded_connection_options) do
80
+ {
81
+ site: 'http://www.bazbazbaz.com',
82
+ path_prefix: '/api',
83
+ path_postfix: '/promotion'
84
+ }
85
+ end
86
+
87
+ it 'merges the given connection_options with the resource threaded_connection_options and connection_options' do
88
+ expect(request.connection_options[:site]).to eql 'http://www.bazbazbaz.com'
89
+ expect(request.connection_options[:collection]).to eql true
90
+ expect(request.connection_options[:path_prefix]).to eql '/api'
91
+ expect(request.connection_options[:path_postfix]).to eql '/promotion'
92
+ expect(request.connection_options[:root_element]).to eql :bazbar
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#original_connection_options' do
98
+ let(:threaded_connection_options_thread_name) { 'remote_resource.request_dummy.threaded_connection_options' }
99
+
100
+ before { Thread.current[threaded_connection_options_thread_name] = threaded_connection_options }
101
+ after { Thread.current[threaded_connection_options_thread_name] = nil }
102
+
103
+ context 'when the given connection_options (original_connection_options) contain other values than the resource threaded_connection_options' do
104
+ let(:connection_options) do
105
+ {
106
+ site: 'http://www.barbaz.com',
107
+ collection: true,
108
+ path_prefix: '/api',
109
+ root_element: :bazbar
110
+ }
111
+ end
112
+
113
+ let(:threaded_connection_options) do
114
+ {
115
+ site: 'http://www.bazbazbaz.com',
116
+ path_prefix: '/registration',
117
+ path_postfix: '/promotion',
118
+ root_element: :bazbazbaz
119
+ }
120
+ end
121
+
122
+ it 'merges the given connection_options (original_connection_options) with the resource threaded_connection_options while taking precedence over the resource threaded_connection_options' do
123
+ expect(request.original_connection_options[:site]).to eql 'http://www.barbaz.com'
124
+ expect(request.original_connection_options[:collection]).to eql true
125
+ expect(request.original_connection_options[:path_prefix]).to eql '/api'
126
+ expect(request.original_connection_options[:path_postfix]).to eql '/promotion'
127
+ expect(request.original_connection_options[:root_element]).to eql :bazbar
128
+ end
129
+ end
130
+
131
+ context 'when the given connection_options (original_connection_options) do NOT contain other values than the resource threaded_connection_options' do
132
+ let(:connection_options) do
133
+ {
134
+ collection: true,
135
+ path_prefix: '/api',
136
+ root_element: :bazbar
137
+ }
138
+ end
139
+
140
+ let(:threaded_connection_options) do
141
+ {
142
+ site: 'http://www.bazbazbaz.com',
143
+ path_prefix: '/api',
144
+ path_postfix: '/promotion'
145
+ }
146
+ end
147
+
148
+ it 'merges the given connection_options (original_connection_options) with the resource threaded_connection_options' do
149
+ expect(request.original_connection_options[:site]).to eql 'http://www.bazbazbaz.com'
150
+ expect(request.original_connection_options[:collection]).to eql true
151
+ expect(request.original_connection_options[:path_prefix]).to eql '/api'
152
+ expect(request.original_connection_options[:path_postfix]).to eql '/promotion'
153
+ expect(request.original_connection_options[:root_element]).to eql :bazbar
154
+ end
155
+ end
156
+ end
157
+
158
+ describe '#perform' do
159
+ let(:connection) { Typhoeus::Request }
160
+ let(:determined_request_url) { 'http://www.foobar.com/request_dummy.json' }
161
+ let(:determined_params) { attributes }
162
+ let(:determined_attributes) { attributes }
163
+ let(:determined_headers) { { "Accept"=>"application/json" } }
164
+
165
+ let(:typhoeus_request) { Typhoeus::Request.new determined_request_url }
166
+ let(:typhoeus_response) do
167
+ response = Typhoeus::Response.new
168
+ response.request = typhoeus_request
169
+ response
170
+ end
171
+
172
+ let(:determined_connection_options) { request.connection_options }
173
+
174
+ before do
175
+ allow_any_instance_of(Typhoeus::Request).to receive(:run) { typhoeus_response }
176
+ allow(typhoeus_response).to receive(:response_code)
177
+ allow(typhoeus_response).to receive(:success?) { true }
178
+ end
179
+
180
+ shared_examples 'a conditional construct for the response' do
181
+ context 'when the response is successful' do
182
+ it 'makes a RemoteResource::Response object with the Typhoeus::Response object and the connection_options' do
183
+ expect(RemoteResource::Response).to receive(:new).with(typhoeus_response, determined_connection_options).and_call_original
184
+ request.perform
185
+ end
186
+
187
+ it 'returns a RemoteResource::Response object' do
188
+ expect(request.perform).to be_a RemoteResource::Response
189
+ end
190
+ end
191
+
192
+ context 'when the response_code of the response is 422' do
193
+ before { allow(typhoeus_response).to receive(:response_code) { 422 } }
194
+
195
+ it 'makes a RemoteResource::Response object with the Typhoeus::Response object and the connection_options' do
196
+ expect(RemoteResource::Response).to receive(:new).with(typhoeus_response, determined_connection_options).and_call_original
197
+ request.perform
198
+ end
199
+
200
+ it 'returns a RemoteResource::Response object' do
201
+ expect(request.perform).to be_a RemoteResource::Response
202
+ end
203
+ end
204
+
205
+ context 'when the response is NOT successful' do
206
+ before { allow(typhoeus_response).to receive(:success?) { false } }
207
+
208
+ it 'calls #raise_http_errors to raise an error' do
209
+ expect(request).to receive(:raise_http_errors).with typhoeus_response
210
+ request.perform
211
+ end
212
+ end
213
+ end
214
+
215
+ context 'when the rest_action is :get' do
216
+ let(:rest_action) { 'get' }
217
+
218
+ it 'makes a GET request with the attributes as params' do
219
+ expect(connection).to receive(:get).with(determined_request_url, params: determined_params, headers: determined_headers).and_call_original
220
+ request.perform
221
+ end
222
+
223
+ it_behaves_like 'a conditional construct for the response'
224
+ end
225
+
226
+ context 'when the rest_action is :put' do
227
+ let(:rest_action) { 'put' }
228
+
229
+ it 'makes a PUT request with the attributes as body' do
230
+ expect(connection).to receive(:put).with(determined_request_url, body: determined_attributes, headers: determined_headers).and_call_original
231
+ request.perform
232
+ end
233
+
234
+ it_behaves_like 'a conditional construct for the response'
235
+ end
236
+
237
+ context 'when the rest_action is :put' do
238
+ let(:rest_action) { 'put' }
239
+
240
+ it 'makes a PUT request with the attributes as body' do
241
+ expect(connection).to receive(:put).with(determined_request_url, body: determined_attributes, headers: determined_headers).and_call_original
242
+ request.perform
243
+ end
244
+
245
+ it_behaves_like 'a conditional construct for the response'
246
+ end
247
+
248
+ context 'when the rest_action is :patch' do
249
+ let(:rest_action) { 'patch' }
250
+
251
+ it 'makes a PATCH request with the attributes as body' do
252
+ expect(connection).to receive(:patch).with(determined_request_url, body: determined_attributes, headers: determined_headers).and_call_original
253
+ request.perform
254
+ end
255
+
256
+ it_behaves_like 'a conditional construct for the response'
257
+ end
258
+
259
+ context 'when the rest_action is :post' do
260
+ let(:rest_action) { 'post' }
261
+
262
+ it 'makes a POST request with the attributes as body' do
263
+ expect(connection).to receive(:post).with(determined_request_url, body: determined_attributes, headers: determined_headers).and_call_original
264
+ request.perform
265
+ end
266
+
267
+ it_behaves_like 'a conditional construct for the response'
268
+ end
269
+
270
+ context 'when the rest_action is unknown' do
271
+ let(:rest_action) { 'foo' }
272
+
273
+ it 'raises the RemoteResource::RESTActionUnknown error' do
274
+ expect{ request.perform }.to raise_error RemoteResource::RESTActionUnknown, "for action: 'foo'"
275
+ end
276
+ end
277
+ end
278
+
279
+ describe '#determined_request_url' do
280
+ context 'the attributes contain an id' do
281
+ let(:attributes) do
282
+ { id: 12, name: 'Mies' }
283
+ end
284
+
285
+ it 'uses the id for the request url' do
286
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummy/12.json'
287
+ end
288
+ end
289
+
290
+ context 'the attributes do NOT contain an id' do
291
+ it 'does NOT use the id for the request url' do
292
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummy.json'
293
+ end
294
+ end
295
+
296
+ context 'the given connection_options (original_connection_options) contain a base_url' do
297
+ let(:connection_options) do
298
+ { base_url: 'http://www.foo.com/api' }
299
+ end
300
+
301
+ it 'uses the base_url for the request url' do
302
+ expect(request.determined_request_url).to eql 'http://www.foo.com/api.json'
303
+ end
304
+ end
305
+
306
+ context 'the given connection_options (original_connection_options) do NOT contain a base_url' do
307
+ it 'does NOT use the base_url for the request url' do
308
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummy.json'
309
+ end
310
+ end
311
+
312
+ context 'the given connection_options contain a collection' do
313
+ let(:connection_options) do
314
+ { collection: true }
315
+ end
316
+
317
+ it 'uses the collection to determine the base_url for the request url' do
318
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummies.json'
319
+ end
320
+ end
321
+
322
+ context 'the connection_options contain a content_type' do
323
+ let(:connection_options) do
324
+ { content_type: '' }
325
+ end
326
+
327
+ it 'uses the content_type for the request url' do
328
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummy'
329
+ end
330
+ end
331
+
332
+ context 'the connection_options do NOT contain a content_type' do
333
+ it 'does NOT use the content_type for the request url' do
334
+ expect(request.determined_request_url).to eql 'http://www.foobar.com/request_dummy.json'
335
+ end
336
+ end
337
+ end
338
+
339
+ describe '#determined_params' do
340
+ context 'the connection_options contain no_params' do
341
+ let(:connection_options) do
342
+ {
343
+ params: { page: 5, limit: 15 },
344
+ no_params: true
345
+ }
346
+ end
347
+
348
+ it 'returns nil' do
349
+ expect(request.determined_params).to be_nil
350
+ end
351
+ end
352
+
353
+ context 'the connection_options do NOT contain a no_params' do
354
+ context 'and the connection_options contain no_attributes' do
355
+ let(:connection_options) do
356
+ {
357
+ params: { page: 5, limit: 15 },
358
+ no_params: false,
359
+ no_attributes: true
360
+ }
361
+ end
362
+
363
+ it 'returns the params' do
364
+ expect(request.determined_params).to eql({ page: 5, limit: 15 })
365
+ end
366
+ end
367
+
368
+ context 'and the connection_options do NOT contain no_attributes' do
369
+ let(:connection_options) do
370
+ {
371
+ params: { page: 5, limit: 15 },
372
+ no_params: false,
373
+ no_attributes: false
374
+ }
375
+ end
376
+
377
+ it 'returns the params merge with the attributes' do
378
+ expect(request.determined_params).to eql({ name: 'Mies', page: 5, limit: 15 })
379
+ end
380
+ end
381
+ end
382
+ end
383
+
384
+ describe '#determined_attributes' do
385
+ context 'the connection_options contain no_attributes' do
386
+ let(:connection_options) do
387
+ { no_attributes: true }
388
+ end
389
+
390
+ it 'returns an empty Hash' do
391
+ expect(request.determined_attributes).to eql({})
392
+ end
393
+ end
394
+
395
+ context 'the connection_options do NOT contain a no_attributes' do
396
+ it 'does NOT return an empty Hash' do
397
+ expect(request.determined_attributes).not_to eql({})
398
+ end
399
+ end
400
+
401
+ context 'the connection_options contain a root_element' do
402
+ let(:connection_options) do
403
+ { root_element: :foobar }
404
+ end
405
+
406
+ let(:packed_up_attributes) do
407
+ { 'foobar' => { name: 'Mies' } }
408
+ end
409
+
410
+ it 'packs up the attributes with the root_element' do
411
+ expect(request.determined_attributes).to eql packed_up_attributes
412
+ end
413
+ end
414
+
415
+ context 'the connection_options do NOT contain a root_element' do
416
+ it 'does NOT pack up the attributes with the root_element' do
417
+ expect(request.determined_attributes).to eql attributes
418
+ end
419
+ end
420
+ end
421
+
422
+ describe '#determined_headers' do
423
+ let(:global_headers) do
424
+ {
425
+ 'X-Locale' => 'en',
426
+ 'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
427
+ }
428
+ end
429
+
430
+ before { RemoteResource::Base.global_headers = global_headers }
431
+ after { RemoteResource::Base.global_headers = nil }
432
+
433
+ let(:headers) do
434
+ { 'Baz' => 'FooBar' }
435
+ end
436
+
437
+ let(:default_headers) do
438
+ dummy_class.default_headers
439
+ end
440
+
441
+ context 'the connection_options contain a default_headers' do
442
+ let(:default_headers) do
443
+ { 'Foo' => 'Bar' }
444
+ end
445
+
446
+ context 'and the given connection_options (original_connection_options) contain a headers' do
447
+ let(:connection_options) do
448
+ { default_headers: default_headers, headers: headers }
449
+ end
450
+
451
+ it 'uses the default_headers for the request headers' do
452
+ expect(request.determined_headers).to eql default_headers.merge(global_headers)
453
+ end
454
+ end
455
+
456
+ context 'and the given connection_options (original_connection_options) do NOT contain a headers' do
457
+ let(:connection_options) do
458
+ { default_headers: default_headers }
459
+ end
460
+
461
+ it 'uses the default_headers for the request headers' do
462
+ expect(request.determined_headers).to eql default_headers.merge(global_headers)
463
+ end
464
+ end
465
+ end
466
+
467
+ context 'the connection_options do NOT contain a default_headers' do
468
+ context 'and the given connection_options (original_connection_options) contain a headers' do
469
+ let(:connection_options) do
470
+ { headers: headers }
471
+ end
472
+
473
+ it 'uses the headers for the request headers' do
474
+ expect(request.determined_headers).to eql default_headers.merge(headers).merge(global_headers)
475
+ end
476
+ end
477
+
478
+ context 'and the given connection_options (original_connection_options) do NOT contain a headers' do
479
+ context 'and the resource contains a extra_headers' do
480
+ let(:extra_headers) do
481
+ { 'BarBaz' => 'Baz' }
482
+ end
483
+
484
+ it 'uses the headers of the resource for the request headers' do
485
+ dummy_class.extra_headers = extra_headers
486
+ dummy_class.connection_options.reload!
487
+
488
+ expect(request.determined_headers).to eql default_headers.merge(extra_headers).merge(global_headers)
489
+
490
+ dummy_class.extra_headers = nil
491
+ dummy_class.connection_options.reload!
492
+ end
493
+ end
494
+
495
+ context 'and the resource does NOT contain a extra_headers' do
496
+ it 'does NOT use the headers for the request headers' do
497
+ expect(request.determined_headers).to eql default_headers.merge(global_headers)
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ describe '#resource_klass' do
505
+ context 'when the resource is a RemoteResource class' do
506
+ let(:resource) { dummy_class }
507
+
508
+ it 'returns the resource' do
509
+ expect(request.send :resource_klass).to eql RemoteResource::RequestDummy
510
+ end
511
+ end
512
+
513
+ context 'when the resource is a RemoteResource object' do
514
+ let(:resource) { dummy }
515
+
516
+ it 'returns the Class of the resource' do
517
+ expect(request.send :resource_klass).to eql RemoteResource::RequestDummy
518
+ end
519
+ end
520
+ end
521
+
522
+ describe '#raise_http_errors' do
523
+ let(:response) { instance_double Typhoeus::Response }
524
+ let(:raise_http_errors) { request.send :raise_http_errors, response }
525
+
526
+ before { allow(response).to receive(:response_code) { response_code } }
527
+
528
+ context 'when the response code is 301, 302, 303 or 307' do
529
+ response_codes = [301, 302, 303, 307]
530
+ response_codes.each do |response_code|
531
+
532
+ it "raises a RemoteResource::HTTPRedirectionError with response code #{response_code}" do
533
+ allow(response).to receive(:response_code) { response_code }
534
+
535
+ expect{ raise_http_errors }.to raise_error RemoteResource::HTTPRedirectionError, "with HTTP response status: #{response_code} and response: #{response}"
536
+ end
537
+ end
538
+ end
539
+
540
+ context 'when the response code is in the 4xx range' do
541
+ response_codes_with_error_class = {
542
+ 400 => RemoteResource::HTTPBadRequest,
543
+ 401 => RemoteResource::HTTPUnauthorized,
544
+ 403 => RemoteResource::HTTPForbidden,
545
+ 404 => RemoteResource::HTTPNotFound,
546
+ 405 => RemoteResource::HTTPMethodNotAllowed,
547
+ 406 => RemoteResource::HTTPNotAcceptable,
548
+ 408 => RemoteResource::HTTPRequestTimeout,
549
+ 409 => RemoteResource::HTTPConflict,
550
+ 410 => RemoteResource::HTTPGone,
551
+ 418 => RemoteResource::HTTPTeapot,
552
+ 444 => RemoteResource::HTTPNoResponse,
553
+ 494 => RemoteResource::HTTPRequestHeaderTooLarge,
554
+ 495 => RemoteResource::HTTPCertError,
555
+ 496 => RemoteResource::HTTPNoCert,
556
+ 497 => RemoteResource::HTTPToHTTPS,
557
+ 499 => RemoteResource::HTTPClientClosedRequest,
558
+ }
559
+ response_codes_with_error_class.each do |response_code, error_class|
560
+
561
+ it "raises a #{error_class} with response code #{response_code}" do
562
+ allow(response).to receive(:response_code) { response_code }
563
+
564
+ expect{ raise_http_errors }.to raise_error error_class, "with HTTP response status: #{response_code} and response: #{response}"
565
+ end
566
+ end
567
+ end
568
+
569
+ context 'when the response code is in the 4xx range and no other error is raised' do
570
+ let(:response_code) { 430 }
571
+
572
+ it 'raises a RemoteResource::HTTPClientError' do
573
+ expect{ raise_http_errors }.to raise_error RemoteResource::HTTPClientError, "with HTTP response status: #{response_code} and response: #{response}"
574
+ end
575
+ end
576
+
577
+ context 'when the response code is in the 5xx range and no other error is raised' do
578
+ let(:response_code) { 501 }
579
+
580
+ it 'raises a RemoteResource::HTTPServerError' do
581
+ expect{ raise_http_errors }.to raise_error RemoteResource::HTTPServerError, "with HTTP response status: #{response_code} and response: #{response}"
582
+ end
583
+ end
584
+
585
+ context 'when the response code is nothing and no other error is raised' do
586
+ let(:response_code) { nil }
587
+
588
+ it 'raises a RemoteResource::HTTPError' do
589
+ expect{ raise_http_errors }.to raise_error RemoteResource::HTTPError, "with HTTP response: #{response}"
590
+ end
591
+ end
592
+ end
593
+
594
+ end