ddy_remote_resource 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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,29 @@
1
+ module RemoteResource
2
+ module REST
3
+ extend ActiveSupport::Concern
4
+
5
+ ACTIONS = [:get, :put, :patch, :post]
6
+
7
+ module ClassMethods
8
+
9
+ RemoteResource::REST::ACTIONS.each do |action|
10
+ define_method action do |*args|
11
+ attributes = args[0] || {}
12
+ connection_options = args[1] || {}
13
+
14
+ RemoteResource::Request.new(self, action, attributes, connection_options).perform
15
+ end
16
+ end
17
+ end
18
+
19
+ RemoteResource::REST::ACTIONS.each do |action|
20
+ define_method action do |*args|
21
+ attributes = args[0] || {}
22
+ connection_options = args[1] || {}
23
+
24
+ RemoteResource::Request.new(self, action, attributes, connection_options).perform
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module RemoteResource
2
+ module UrlNaming
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :site, :version, :path_prefix, :path_postfix, :collection, :collection_name, instance_accessor: false
7
+
8
+ self.collection = false
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def app_host(app, env = 'development')
14
+ CONFIG[env.to_sym][:apps][app.to_sym]
15
+ end
16
+
17
+ def base_url
18
+ determined_url_naming.base_url
19
+ end
20
+
21
+ def use_relative_model_naming?
22
+ true
23
+ end
24
+
25
+ private
26
+
27
+ def determined_url_naming
28
+ RemoteResource::UrlNamingDetermination.new self
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ module RemoteResource
2
+ class UrlNamingDetermination
3
+
4
+ attr_reader :resource_klass, :connection_options
5
+
6
+ def initialize(resource_klass, connection_options = {})
7
+ @resource_klass = resource_klass
8
+ @connection_options = connection_options
9
+ end
10
+
11
+ def base_url(id = nil)
12
+ site = connection_options.fetch(:site, resource_klass.site)
13
+ version = connection_options.fetch(:version, resource_klass.version)
14
+ path_prefix = connection_options.fetch(:path_prefix, resource_klass.path_prefix)
15
+ path_postfix = connection_options.fetch(:path_postfix, resource_klass.path_postfix)
16
+
17
+ id = "/#{id}" if id.present?
18
+
19
+ "#{site}#{version.presence}#{path_prefix.presence}/#{url_safe_relative_name}#{id}#{path_postfix.presence}"
20
+ end
21
+
22
+ def url_safe_relative_name
23
+ collection = connection_options.fetch(:collection, resource_klass.collection)
24
+
25
+ if collection
26
+ relative_name.underscore.downcase.pluralize
27
+ else
28
+ relative_name.underscore.downcase
29
+ end
30
+ end
31
+
32
+ def relative_name
33
+ collection_name = connection_options.fetch(:collection_name, resource_klass.collection_name)
34
+
35
+ collection_name.to_s.presence || resource_klass.name.to_s.demodulize
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module RemoteResource
2
+ VERSION = '0.4.2'
3
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'remote_resource/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ddy_remote_resource'
8
+ spec.version = RemoteResource::VERSION
9
+ spec.authors = ["Jan van der Pas"]
10
+ spec.email = ["jvanderpas@digidentity.eu"]
11
+ spec.summary = %q{RemoteResource, a gem to use resources with REST services.}
12
+ spec.description = %q{RemoteResource, a gem to use resources with REST services. A replacement for ActiveResource gem.}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'pry'
25
+
26
+ spec.add_runtime_dependency 'activesupport'
27
+ spec.add_runtime_dependency 'activemodel'
28
+ spec.add_runtime_dependency 'virtus'
29
+ spec.add_runtime_dependency 'mime-types', '>= 1.16'
30
+ spec.add_runtime_dependency 'ethon', '~> 0.7.1'
31
+ spec.add_runtime_dependency 'typhoeus'
32
+ end
@@ -0,0 +1,135 @@
1
+ require 'ethon'
2
+ require_relative '../../../../../lib/extensions/ethon/easy/queryable'
3
+
4
+ describe Ethon::Easy::Queryable do
5
+
6
+ let(:hash) { {} }
7
+ let!(:easy) { Ethon::Easy.new }
8
+ let(:params) { Ethon::Easy::Params.new(easy, hash) }
9
+
10
+ describe "#build_query_pairs" do
11
+ let(:pairs) { params.method(:build_query_pairs).call(hash) }
12
+
13
+ context "when params is empty" do
14
+ it "returns empty array" do
15
+ expect(pairs).to eq([])
16
+ end
17
+ end
18
+
19
+ context "when params is string" do
20
+ let(:hash) { "{a: 1}" }
21
+
22
+ it "wraps it in an array" do
23
+ expect(pairs).to eq([hash])
24
+ end
25
+ end
26
+
27
+ context "when params is simple hash" do
28
+ let(:hash) { {:a => 1, :b => 2} }
29
+
30
+ it "transforms" do
31
+ expect(pairs).to include([:a, 1])
32
+ expect(pairs).to include([:b, 2])
33
+ end
34
+ end
35
+
36
+ context "when params is a nested hash" do
37
+ let(:hash) { {:a => 1, :b => {:c => 2}} }
38
+
39
+ it "transforms" do
40
+ expect(pairs).to include([:a, 1])
41
+ expect(pairs).to include(["b[c]", 2])
42
+ end
43
+ end
44
+
45
+ context "when params contains an array" do
46
+ let(:hash) { {:a => 1, :b => [2, 3]} }
47
+
48
+ it "transforms" do
49
+ expect(pairs).to include([:a, 1])
50
+ expect(pairs).to include(["b[]", 2])
51
+ expect(pairs).to include(["b[]", 3])
52
+ end
53
+ end
54
+
55
+ context "when params contains something nested in an array" do
56
+ context "when string" do
57
+ let(:hash) { {:a => {:b => ["hello", "world"]}} }
58
+
59
+ it "transforms" do
60
+ expect(pairs).to eq([["a[b][]", "hello"], ["a[b][]", "world"]])
61
+ end
62
+ end
63
+
64
+ context "when hash" do
65
+ let(:hash) { {:a => {:b => [{:c =>1}, {:d => 2}]}} }
66
+
67
+ it "transforms" do
68
+ expect(pairs).to eq([["a[b][][c]", 1], ["a[b][][d]", 2]])
69
+ end
70
+ end
71
+
72
+ context "when file" do
73
+ let(:file) { File.open("spec/spec_helper.rb") }
74
+ let(:file_info) { params.method(:file_info).call(file) }
75
+ let(:hash) { {:a => {:b => [file]}} }
76
+ let(:mime_type) { file_info[1] }
77
+
78
+ it "transforms" do
79
+ expect(pairs).to eq([["a[b][]", file_info]])
80
+ end
81
+
82
+ context "when MIME" do
83
+ context "when mime type" do
84
+ it "sets mime type to text" do
85
+ expect(mime_type).to eq("application/x-ruby")
86
+ end
87
+ end
88
+
89
+ context "when no mime type" do
90
+ let(:file) { Tempfile.new("fubar") }
91
+
92
+ it "sets mime type to default application/octet-stream" do
93
+ Object.send(:remove_const, :MIME)
94
+ expect(mime_type).to eq("application/octet-stream")
95
+ end
96
+ end
97
+ end
98
+
99
+ context "when no MIME" do
100
+ it "sets mime type to default application/octet-stream" do
101
+ expect(mime_type).to eq("application/octet-stream")
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+
108
+ context "when params contains file" do
109
+ let(:file) { Tempfile.new("fubar") }
110
+ let(:file_info) { params.method(:file_info).call(file) }
111
+ let(:hash) { {:a => 1, :b => file} }
112
+
113
+ it "transforms" do
114
+ expect(pairs).to include([:a, 1])
115
+ expect(pairs).to include([:b, file_info])
116
+ end
117
+ end
118
+
119
+ context "when params key contains a null byte" do
120
+ let(:hash) { {:a => "1\0" } }
121
+
122
+ it "preserves" do
123
+ expect(pairs).to eq([[:a, "1\0"]])
124
+ end
125
+ end
126
+
127
+ context "when params value contains a null byte" do
128
+ let(:hash) { {"a\0" => 1 } }
129
+
130
+ it "preserves" do
131
+ expect(pairs).to eq([["a\0", 1]])
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,388 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteResource::Base do
4
+
5
+ module RemoteResource
6
+ class Dummy
7
+ include RemoteResource::Base
8
+
9
+ self.site = 'https://foobar.com'
10
+
11
+ end
12
+ end
13
+
14
+ let(:dummy_class) { RemoteResource::Dummy }
15
+ let(:dummy) { dummy_class.new }
16
+
17
+ specify { expect(described_class.const_defined?('RemoteResource::Builder')).to be_truthy }
18
+ specify { expect(described_class.const_defined?('RemoteResource::UrlNaming')).to be_truthy }
19
+ specify { expect(described_class.const_defined?('RemoteResource::Connection')).to be_truthy }
20
+ specify { expect(described_class.const_defined?('RemoteResource::REST')).to be_truthy }
21
+
22
+ specify { expect(described_class.const_defined?('RemoteResource::Querying::FinderMethods')).to be_truthy }
23
+ specify { expect(described_class.const_defined?('RemoteResource::Querying::PersistenceMethods')).to be_truthy }
24
+
25
+ describe 'OPTIONS' do
26
+ let(:options) { [:base_url, :site, :headers, :version, :path_prefix, :path_postfix, :content_type, :collection, :collection_name, :root_element] }
27
+
28
+ specify { expect(described_class::OPTIONS).to eql options }
29
+ end
30
+
31
+ describe 'attributes' do
32
+ it '#id' do
33
+ expect(dummy.attributes).to have_key :id
34
+ end
35
+ end
36
+
37
+ describe '.global_headers=' do
38
+ let(:global_headers) do
39
+ {
40
+ 'X-Locale' => 'en',
41
+ 'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
42
+ }
43
+ end
44
+
45
+ after { described_class.global_headers = nil }
46
+
47
+ it 'sets the global headers Thread variable' do
48
+ expect{ described_class.global_headers = global_headers }.to change{ Thread.current[:global_headers] }.from(nil).to global_headers
49
+ end
50
+ end
51
+
52
+ describe '.global_headers' do
53
+ let(:global_headers) do
54
+ {
55
+ 'X-Locale' => 'en',
56
+ 'Authorization' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
57
+ }
58
+ end
59
+
60
+ before { described_class.global_headers = global_headers }
61
+ after { described_class.global_headers = nil }
62
+
63
+ it 'returns the global headers Thread variable' do
64
+ expect(described_class.global_headers).to eql global_headers
65
+ end
66
+ end
67
+
68
+ describe '.connection_options' do
69
+ it 'instantiates as a RemoteResource::ConnectionOptions' do
70
+ expect(dummy_class.connection_options).to be_a RemoteResource::ConnectionOptions
71
+ end
72
+
73
+ it 'uses the implemented class as base_class' do
74
+ expect(dummy_class.connection_options.base_class).to be RemoteResource::Dummy
75
+ end
76
+
77
+ it 'sets the name of Thread variable with the implemented class' do
78
+ expect(dummy_class.connection_options).to eql Thread.current['remote_resource.dummy.connection_options']
79
+ end
80
+ end
81
+
82
+ describe '.threaded_connection_options' do
83
+ it 'instantiates as a Hash' do
84
+ expect(dummy_class.threaded_connection_options).to be_a Hash
85
+ end
86
+
87
+ it 'sets the name of Thread variable with the implemented class' do
88
+ expect(dummy_class.threaded_connection_options).to eql Thread.current['remote_resource.dummy.threaded_connection_options']
89
+ end
90
+ end
91
+
92
+ describe '.with_connection_options' do
93
+ let(:connection_options) { {} }
94
+
95
+ let(:block_with_connection_options) do
96
+ dummy_class.with_connection_options(connection_options) do
97
+ dummy_class.find_by({ username: 'foobar' }, { content_type: '.json' })
98
+ dummy_class.create({ username: 'bazbar' }, { content_type: '.xml' })
99
+ end
100
+ end
101
+
102
+ before { allow_any_instance_of(Typhoeus::Request).to receive(:run) { double.as_null_object } }
103
+
104
+ it 'yields the block' do
105
+ expect(dummy_class).to receive(:find_by).with({ username: 'foobar' }, { content_type: '.json' }).and_call_original
106
+ expect(dummy_class).to receive(:create).with({ username: 'bazbar' }, { content_type: '.xml' }).and_call_original
107
+ block_with_connection_options
108
+ end
109
+
110
+ it 'ensures to set the threaded_connection_options Thread variable to nil' do
111
+ dummy_class.threaded_connection_options
112
+
113
+ expect{ block_with_connection_options }.to change{ Thread.current['remote_resource.dummy.threaded_connection_options'] }.from(an_instance_of(Hash)).to nil
114
+ end
115
+
116
+ context 'when the given connection_options contain headers' do
117
+ let(:connection_options) do
118
+ {
119
+ headers: { "Foo" => "Bar" }
120
+ }
121
+ end
122
+
123
+ it 'uses the headers of the given connection_options' do
124
+ expect(Typhoeus::Request).to receive(:get).with('https://foobar.com/dummy.json', params: { username: 'foobar' }, headers: { "Accept" => "application/json", "Foo" => "Bar" }).and_call_original
125
+ expect(Typhoeus::Request).to receive(:post).with('https://foobar.com/dummy.xml', body: { username: 'bazbar' }, headers: { "Accept" => "application/json", "Foo" => "Bar" }).and_call_original
126
+ block_with_connection_options
127
+ end
128
+ end
129
+
130
+ context 'when the given connection_options contain base_url' do
131
+ let(:connection_options) do
132
+ {
133
+ base_url: 'https://api.foobar.eu/dummy'
134
+ }
135
+ end
136
+
137
+ it 'uses the base_url of the given connection_options' do
138
+ expect(Typhoeus::Request).to receive(:get).with('https://api.foobar.eu/dummy.json', params: { username: 'foobar' }, headers: { "Accept" => "application/json" }).and_call_original
139
+ expect(Typhoeus::Request).to receive(:post).with('https://api.foobar.eu/dummy.xml', body: { username: 'bazbar' }, headers: { "Accept" => "application/json" }).and_call_original
140
+ block_with_connection_options
141
+ end
142
+ end
143
+
144
+ context 'when the given connection_options contain something else' do
145
+ let(:connection_options) do
146
+ {
147
+ collection: true,
148
+ path_prefix: '/api',
149
+ root_element: :bazbar
150
+ }
151
+ end
152
+
153
+ it 'uses the given connection_options' do
154
+ expect(Typhoeus::Request).to receive(:get).with('https://foobar.com/api/dummies.json', params: { username: 'foobar' }, headers: { "Accept" => "application/json" }).and_call_original
155
+ expect(Typhoeus::Request).to receive(:post).with('https://foobar.com/api/dummies.xml', body: { 'bazbar' => { username: 'bazbar' } }, headers: { "Accept" => "application/json" }).and_call_original
156
+ block_with_connection_options
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#connection_options' do
162
+ it 'instanties as a RemoteResource::ConnectionOptions' do
163
+ expect(dummy.connection_options).to be_a RemoteResource::ConnectionOptions
164
+ end
165
+
166
+ it 'uses the implemented class as base_class' do
167
+ expect(dummy.connection_options.base_class).to be RemoteResource::Dummy
168
+ end
169
+ end
170
+
171
+ describe '#empty?' do
172
+ before { allow(dummy).to receive(:_response) { response } }
173
+
174
+ context 'when the response is present' do
175
+ let(:response) { instance_double(RemoteResource::Response, sanitized_response_body: sanitized_response_body) }
176
+
177
+ context 'and the #sanitized_response_body is present' do
178
+ let(:sanitized_response_body) do
179
+ { name: 'Mies' }
180
+ end
181
+
182
+ it 'returns false' do
183
+ expect(dummy.empty?).to eql false
184
+ end
185
+ end
186
+
187
+ context 'and the #sanitized_response_body is blank' do
188
+ let(:sanitized_response_body) do
189
+ {}
190
+ end
191
+
192
+ it 'returns true' do
193
+ expect(dummy.empty?).to eql true
194
+ end
195
+ end
196
+
197
+ context 'and the #sanitized_response_body is NOT present' do
198
+ let(:sanitized_response_body) { nil }
199
+
200
+ it 'returns true' do
201
+ expect(dummy.empty?).to eql true
202
+ end
203
+ end
204
+ end
205
+
206
+ context 'when the response is NOT present' do
207
+ let(:response) { nil }
208
+
209
+ it 'returns true' do
210
+ expect(dummy.empty?).to eql true
211
+ end
212
+ end
213
+ end
214
+
215
+ describe '#persisted?' do
216
+ context 'when id is present' do
217
+ it 'returns true' do
218
+ dummy.id = 10
219
+ expect(dummy.persisted?).to eql true
220
+ end
221
+ end
222
+
223
+ context 'when is is NOT present' do
224
+ it 'returns false' do
225
+ expect(dummy.persisted?).to eql false
226
+ end
227
+ end
228
+ end
229
+
230
+ describe '#new_record?' do
231
+ context 'when instance persisted' do
232
+ it 'returns false' do
233
+ allow(dummy).to receive(:persisted?) { true }
234
+
235
+ expect(dummy.new_record?).to eql false
236
+ end
237
+ end
238
+
239
+ context 'when instance does NOT persist' do
240
+ it 'returns true' do
241
+ allow(dummy).to receive(:persisted?) { false }
242
+
243
+ expect(dummy.new_record?).to eql true
244
+ end
245
+ end
246
+ end
247
+
248
+ describe '#success?' do
249
+ let(:response) { instance_double(RemoteResource::Response) }
250
+
251
+ before { allow(dummy).to receive(:_response) { response } }
252
+
253
+ context 'when response is successful' do
254
+ before { allow(response).to receive(:success?) { true } }
255
+
256
+ context 'and the resource has NO errors present' do
257
+ it 'returns true' do
258
+ expect(dummy.success?).to eql true
259
+ end
260
+ end
261
+
262
+ context 'and the resource has errors present' do
263
+ it 'returns false' do
264
+ dummy.errors.add :id, 'must be present'
265
+
266
+ expect(dummy.success?).to eql false
267
+ end
268
+ end
269
+ end
270
+
271
+ context 'when response is NOT successful' do
272
+ before { allow(response).to receive(:success?) { false } }
273
+
274
+ it 'returns false' do
275
+ expect(dummy.success?).to eql false
276
+ end
277
+ end
278
+ end
279
+
280
+ describe '#errors?' do
281
+ context 'when resource has errors present' do
282
+ it 'returns true' do
283
+ dummy.errors.add :id, 'must be present'
284
+
285
+ expect(dummy.errors?).to eql true
286
+ end
287
+ end
288
+
289
+ context 'when resource has NO errors present' do
290
+ it 'returns false' do
291
+ expect(dummy.errors?).to eql false
292
+ end
293
+ end
294
+ end
295
+
296
+ describe '#handle_response' do
297
+ let(:response) { instance_double(RemoteResource::Response) }
298
+
299
+ before { allow(dummy).to receive(:rebuild_resource_from_response) { dummy } }
300
+
301
+ context 'when the response is a unprocessable_entity' do
302
+ before do
303
+ allow(response).to receive(:unprocessable_entity?) { true }
304
+
305
+ allow(dummy).to receive(:assign_errors_from_response)
306
+ end
307
+
308
+ it 'rebuilds the resource from the response' do
309
+ expect(dummy).to receive(:rebuild_resource_from_response).with response
310
+ dummy.handle_response response
311
+ end
312
+
313
+ it 'assigns the errors from the response to the resource' do
314
+ expect(dummy).to receive(:assign_errors_from_response).with response
315
+ dummy.handle_response response
316
+ end
317
+ end
318
+
319
+ context 'when the response is NOT a unprocessable_entity' do
320
+ before { allow(response).to receive(:unprocessable_entity?) { false } }
321
+
322
+ it 'rebuilds the resource from the response' do
323
+ expect(dummy).to receive(:rebuild_resource_from_response).with response
324
+ dummy.handle_response response
325
+ end
326
+ end
327
+ end
328
+
329
+ describe '#assign_response' do
330
+ let(:response) { instance_double(RemoteResource::Response) }
331
+
332
+ it 'assigns the #_response' do
333
+ expect{ dummy.assign_response response }.to change{ dummy._response }.from(nil).to response
334
+ end
335
+ end
336
+
337
+ describe '#assign_errors_from_response' do
338
+ let(:response) { instance_double(RemoteResource::Response) }
339
+ let(:error_messages_response_body) { double('error_messages_response_body') }
340
+
341
+ it 'calls the #assign_errors method with the #error_messages_response_body of the response' do
342
+ allow(response).to receive(:error_messages_response_body) { error_messages_response_body }
343
+
344
+ expect(dummy).to receive(:assign_errors).with error_messages_response_body
345
+ dummy.assign_errors_from_response response
346
+ end
347
+ end
348
+
349
+ describe '#assign_errors' do
350
+ context 'with errors in the error_messages' do
351
+ let(:error_messages) do
352
+ {
353
+ "foo" => ["is required"],
354
+ "bar" => ["must be greater than 5"]
355
+ }
356
+ end
357
+
358
+ it 'assigns the error_messages as errors' do
359
+ dummy.send :assign_errors, error_messages
360
+ expect(dummy.errors.messages).to eql foo: ["is required"], bar: ["must be greater than 5"]
361
+ end
362
+ end
363
+
364
+ context 'with an empty Hash in the error_messages' do
365
+ let(:error_messages) do
366
+ {}
367
+ end
368
+
369
+ it 'does NOT assign the error_messages as errors' do
370
+ dummy.send :assign_errors, error_messages
371
+ expect(dummy.errors.messages).to eql({})
372
+ end
373
+ end
374
+
375
+ context 'with a String in the error_messages' do
376
+ let(:error_messages) do
377
+ "unauthorized"
378
+ end
379
+
380
+ it 'does NOT assign the error_messages as errors' do
381
+ dummy.send :assign_errors, error_messages
382
+ expect(dummy.errors.messages).to eql({})
383
+ end
384
+ end
385
+ end
386
+
387
+ end
388
+