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,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
+