acfs 1.3.3 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +339 -0
  3. data/LICENSE +22 -0
  4. data/README.md +335 -0
  5. data/acfs.gemspec +46 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +24 -0
  8. data/lib/acfs/adapter/typhoeus.rb +69 -0
  9. data/lib/acfs/collection.rb +28 -0
  10. data/lib/acfs/collections/paginatable.rb +76 -0
  11. data/lib/acfs/configuration.rb +120 -0
  12. data/lib/acfs/errors.rb +127 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +82 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +29 -0
  17. data/lib/acfs/middleware/logger.rb +25 -0
  18. data/lib/acfs/middleware/msgpack.rb +32 -0
  19. data/lib/acfs/middleware/print.rb +23 -0
  20. data/lib/acfs/middleware/serializer.rb +41 -0
  21. data/lib/acfs/operation.rb +83 -0
  22. data/lib/acfs/request.rb +39 -0
  23. data/lib/acfs/request/callbacks.rb +54 -0
  24. data/lib/acfs/resource.rb +39 -0
  25. data/lib/acfs/resource/attributes.rb +269 -0
  26. data/lib/acfs/resource/attributes/base.rb +29 -0
  27. data/lib/acfs/resource/attributes/boolean.rb +39 -0
  28. data/lib/acfs/resource/attributes/date_time.rb +32 -0
  29. data/lib/acfs/resource/attributes/dict.rb +39 -0
  30. data/lib/acfs/resource/attributes/float.rb +33 -0
  31. data/lib/acfs/resource/attributes/integer.rb +29 -0
  32. data/lib/acfs/resource/attributes/list.rb +36 -0
  33. data/lib/acfs/resource/attributes/string.rb +26 -0
  34. data/lib/acfs/resource/attributes/uuid.rb +48 -0
  35. data/lib/acfs/resource/dirty.rb +37 -0
  36. data/lib/acfs/resource/initialization.rb +31 -0
  37. data/lib/acfs/resource/loadable.rb +35 -0
  38. data/lib/acfs/resource/locatable.rb +132 -0
  39. data/lib/acfs/resource/operational.rb +23 -0
  40. data/lib/acfs/resource/persistence.rb +260 -0
  41. data/lib/acfs/resource/query_methods.rb +266 -0
  42. data/lib/acfs/resource/service.rb +44 -0
  43. data/lib/acfs/resource/validation.rb +39 -0
  44. data/lib/acfs/response.rb +30 -0
  45. data/lib/acfs/response/formats.rb +27 -0
  46. data/lib/acfs/response/status.rb +33 -0
  47. data/lib/acfs/rspec.rb +13 -0
  48. data/lib/acfs/runner.rb +102 -0
  49. data/lib/acfs/service.rb +97 -0
  50. data/lib/acfs/service/middleware.rb +58 -0
  51. data/lib/acfs/service/middleware/stack.rb +65 -0
  52. data/lib/acfs/singleton_resource.rb +85 -0
  53. data/lib/acfs/stub.rb +194 -0
  54. data/lib/acfs/util.rb +22 -0
  55. data/lib/acfs/version.rb +16 -0
  56. data/lib/acfs/yard.rb +6 -0
  57. data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
  58. data/spec/acfs/collection_spec.rb +157 -0
  59. data/spec/acfs/configuration_spec.rb +53 -0
  60. data/spec/acfs/global_spec.rb +140 -0
  61. data/spec/acfs/location_spec.rb +25 -0
  62. data/spec/acfs/middleware/json_spec.rb +65 -0
  63. data/spec/acfs/middleware/msgpack_spec.rb +62 -0
  64. data/spec/acfs/operation_spec.rb +12 -0
  65. data/spec/acfs/request/callbacks_spec.rb +48 -0
  66. data/spec/acfs/request_spec.rb +79 -0
  67. data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
  68. data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
  69. data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
  70. data/spec/acfs/resource/attributes/float_spec.rb +61 -0
  71. data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
  72. data/spec/acfs/resource/attributes/list_spec.rb +60 -0
  73. data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
  74. data/spec/acfs/resource/attributes_spec.rb +181 -0
  75. data/spec/acfs/resource/dirty_spec.rb +49 -0
  76. data/spec/acfs/resource/initialization_spec.rb +36 -0
  77. data/spec/acfs/resource/loadable_spec.rb +22 -0
  78. data/spec/acfs/resource/locatable_spec.rb +118 -0
  79. data/spec/acfs/resource/persistance_spec.rb +322 -0
  80. data/spec/acfs/resource/query_methods_spec.rb +548 -0
  81. data/spec/acfs/resource/validation_spec.rb +129 -0
  82. data/spec/acfs/response/formats_spec.rb +52 -0
  83. data/spec/acfs/response/status_spec.rb +71 -0
  84. data/spec/acfs/runner_spec.rb +95 -0
  85. data/spec/acfs/service/middleware_spec.rb +35 -0
  86. data/spec/acfs/service_spec.rb +48 -0
  87. data/spec/acfs/singleton_resource_spec.rb +17 -0
  88. data/spec/acfs/stub_spec.rb +345 -0
  89. data/spec/acfs_spec.rb +205 -0
  90. data/spec/fixtures/config.yml +14 -0
  91. data/spec/spec_helper.rb +43 -0
  92. data/spec/support/hash.rb +11 -0
  93. data/spec/support/response.rb +12 -0
  94. data/spec/support/service.rb +92 -0
  95. data/spec/support/shared/find_callbacks.rb +50 -0
  96. metadata +136 -3
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Persistence do
6
+ let(:model_class) { MyUser }
7
+ before do
8
+ @get_stub = stub_request(:get, 'http://users.example.org/users/1').to_return response(id: 1, name: 'Anon', age: 12)
9
+
10
+ @patch_stub = stub_request(:put, 'http://users.example.org/users/1')
11
+ .with(body: '{"id":1,"name":"Idefix","age":12}')
12
+ .to_return response(id: 1, name: 'Idefix', age: 12)
13
+
14
+ @post_stub = stub_request(:post, 'http://users.example.org/users')
15
+ .with(body: '{"id":null,"name":"Idefix","age":12}')
16
+ .to_return response(id: 5, name: 'Idefix', age: 12)
17
+
18
+ stub_request(:post, 'http://users.example.org/users')
19
+ .with(body: '{"id":null,"name":"Anon","age":null}')
20
+ .to_return response(id: 5, name: 'Anon', age: 12)
21
+
22
+ stub_request(:post, 'http://users.example.org/users')
23
+ .with(body: '{id:null,"name":"Idefix","age":12}')
24
+ .to_return response(id: 5, name: 'Idefix', age: 12)
25
+
26
+ stub_request(:post, 'http://users.example.org/users')
27
+ .with(body: '{"id":null,"name":null,"age":12}')
28
+ .to_return response({errors: {name: ['required']}}, {status: 422})
29
+
30
+ @del = stub_request(:delete, 'http://users.example.org/users/1')
31
+ .with(body: '{}')
32
+ .to_return response({id: 1, name: 'Idefix', age: 12}, {status: 200})
33
+ end
34
+
35
+ context 'new model' do
36
+ let(:model) { model_class.new }
37
+
38
+ it { expect(model).to_not be_persisted }
39
+ it { expect(model).to be_new }
40
+
41
+ describe '#save!' do
42
+ context 'when modified' do
43
+ let(:model) { model_class.find 1 }
44
+ before do
45
+ model
46
+ Acfs.run
47
+ model.name = 'Idefix'
48
+ end
49
+
50
+ it 'should PUT to model URL' do
51
+ model.save!
52
+
53
+ expect(@patch_stub).to have_been_requested
54
+ end
55
+ end
56
+
57
+ context 'when new' do
58
+ let(:model) { model_class.new name: 'Idefix', age: 12 }
59
+
60
+ it 'should POST to collection URL' do
61
+ model.save!
62
+
63
+ expect(@post_stub).to have_been_requested
64
+ end
65
+
66
+ context 'with unknown attributes' do
67
+ let!(:req) do
68
+ stub_request(:post, 'http://users.example.org/users')
69
+ .with(body: '{"id":null,"name":"Idefix","age":null,"born_at":"Berlin"}')
70
+ .to_return response(id: 5, name: 'Idefix', age: 12, wuff: 'woa')
71
+ end
72
+ let(:model) { model_class.new name: 'Idefix', born_at: 'Berlin' }
73
+
74
+ it 'should POST to collection URL' do
75
+ model.save!
76
+ expect(req).to have_been_requested
77
+ end
78
+
79
+ it 'should still have unknown attribute' do
80
+ model.save!
81
+ expect(model.attributes).to include 'born_at' => 'Berlin'
82
+ end
83
+
84
+ it 'should include server send unknown attribute' do
85
+ model.save!
86
+ expect(model.attributes).to include 'wuff' => 'woa'
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ context 'after save' do
93
+ before { model.save! }
94
+
95
+ it { expect(model).to be_persisted }
96
+ it { expect(model).to_not be_new }
97
+ end
98
+ end
99
+
100
+ context 'unloaded model' do
101
+ let!(:model) { model_class.find 1 }
102
+
103
+ describe '#update_attributes' do
104
+ subject { -> { model.update_attributes name: 'John' } }
105
+ it { expect { subject.call }.to raise_error Acfs::ResourceNotLoaded }
106
+ end
107
+
108
+ describe '#update_attributes!' do
109
+ subject { -> { model.update_attributes! name: 'John' } }
110
+ it { expect { subject.call }.to raise_error Acfs::ResourceNotLoaded }
111
+ end
112
+ end
113
+
114
+ context 'loaded model' do
115
+ context 'without changes' do
116
+ let(:model) { model_class.find 1 }
117
+ before { model; Acfs.run }
118
+
119
+ it { expect(model).to be_persisted }
120
+ it { expect(model).to_not be_new }
121
+ end
122
+
123
+ context 'with changes' do
124
+ let(:model) { model_class.find 1 }
125
+ before { model; Acfs.run; model.name = 'dhh' }
126
+
127
+ it { expect(model).to be_persisted }
128
+ it { expect(model).to_not be_new }
129
+ end
130
+
131
+ describe '#delete!' do
132
+ let(:model) { model_class.find 1 }
133
+ let(:before_acfs_run) {}
134
+
135
+ describe 'normal delete actions' do
136
+ before { model; Acfs.run }
137
+
138
+ it 'should trigger DELETE request' do
139
+ model.delete!
140
+ expect(@del).to have_been_requested
141
+ end
142
+
143
+ it 'should be frozen after DELETE' do
144
+ model.delete!
145
+ expect(model.__getobj__).to be_frozen
146
+ end
147
+ end
148
+
149
+ describe 'correct URL generation' do
150
+ let(:model_class) { PathArguments }
151
+ let(:model) { model_class.find 1, params: {required_arg: 'some_value'} }
152
+
153
+ before :each do
154
+ resource_url = 'http://users.example.org/some_value/users/1'
155
+ @my_get_stub = stub_request(:get, resource_url)
156
+ .to_return response(id: 1, required_arg: 'some_value')
157
+ @my_delete_stub = stub_request(:delete, resource_url)
158
+ .with(body: '{}')
159
+ .to_return response({id: 1, required_arg: 'some_value'}, {status: 200})
160
+ model
161
+ Acfs.run
162
+ end
163
+
164
+ it 'should not raise an error on URL generation' do
165
+ expect do
166
+ model.delete!
167
+ end.not_to raise_error
168
+ end
169
+
170
+ it 'should request the delete' do
171
+ model.delete!
172
+ expect(@my_delete_stub).to have_been_requested
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#update_atributes!' do
178
+ let(:model) { model_class.find 1 }
179
+ before { model; Acfs.run }
180
+
181
+ it 'should set attributes' do
182
+ model.update_attributes name: 'Idefix'
183
+ expect(model.attributes.symbolize_keys).to eq id: 1, name: 'Idefix', age: 12
184
+ end
185
+
186
+ it 'should save resource' do
187
+ expect(model.__getobj__).to receive(:save).with({})
188
+ model.update_attributes name: 'Idefix'
189
+ end
190
+
191
+ it 'should pass second hash to save' do
192
+ expect(model.__getobj__).to receive(:save).with(bla: 'blub')
193
+ model.update_attributes({name: 'Idefix'}, {bla: 'blub'})
194
+ end
195
+ end
196
+
197
+ describe '#update_atributes' do
198
+ let(:model) { model_class.find 1 }
199
+ before { model; Acfs.run }
200
+
201
+ it 'should set attributes' do
202
+ model.update_attributes! name: 'Idefix'
203
+ expect(model.attributes.symbolize_keys).to eq id: 1, name: 'Idefix', age: 12
204
+ end
205
+
206
+ it 'should save resource' do
207
+ expect(model.__getobj__).to receive(:save!).with({})
208
+ model.update_attributes! name: 'Idefix'
209
+ end
210
+
211
+ it 'should pass second hash to save' do
212
+ expect(model.__getobj__).to receive(:save!).with(bla: 'blub')
213
+ model.update_attributes!({name: 'Idefix'}, {bla: 'blub'})
214
+ end
215
+ end
216
+ end
217
+
218
+ describe '.save!' do
219
+ context 'with invalid data validated on server side' do
220
+ let(:model) { model_class.find 1 }
221
+ before { model; Acfs.run }
222
+
223
+ before do
224
+ stub_request(:put, 'http://users.example.org/users/1')
225
+ .with(body: '{"id":1,"name":"","age":12}')
226
+ .to_return response({errors: {name: ['required']}}, {status: 422})
227
+ end
228
+
229
+ it 'should set local errors hash' do
230
+ model.name = ''
231
+ begin
232
+ model.save!
233
+ rescue StandardError
234
+ nil
235
+ end
236
+ expect(model.errors.to_hash).to be == {name: %w[required]}
237
+ end
238
+ end
239
+
240
+ context 'hash modification on iteration in ActiveModel when errors on field is nil' do
241
+ let(:model) { model_class.find 1 }
242
+ before { model; Acfs.run }
243
+
244
+ before do
245
+ stub_request(:put, 'http://users.example.org/users/1')
246
+ .with(body: '{"id":1,"name":"","age":12}')
247
+ .to_return response({errors: {name: ['required']}}, {status: 422})
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '.create!' do
253
+ context 'with valid data' do
254
+ let(:data) { {name: 'Idefix', age: 12} }
255
+
256
+ it 'should create new resource' do
257
+ model = model_class.create! data
258
+ expect(model.name).to be == 'Idefix'
259
+ expect(model.age).to be == 12
260
+ end
261
+
262
+ it 'should be persisted' do
263
+ model = model_class.create! data
264
+ expect(model).to be_persisted
265
+ end
266
+ end
267
+
268
+ context 'with invalid data' do
269
+ let(:data) { {name: nil, age: 12} }
270
+
271
+ it 'should raise an error' do
272
+ expect { model_class.create! data }.to \
273
+ raise_error(::Acfs::InvalidResource) do |error|
274
+ expect(error.errors).to be == {'name' => %w[required]}
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ describe '.create' do
281
+ subject { model_class.create data }
282
+
283
+ context 'with valid data' do
284
+ let(:data) { {name: 'Idefix', age: 12} }
285
+
286
+ it 'should create new resource' do
287
+ expect(subject.name).to be == 'Idefix'
288
+ expect(subject.age).to be == 12
289
+ end
290
+
291
+ it 'should be persisted' do
292
+ expect(subject).to be_persisted
293
+ end
294
+ end
295
+
296
+ context 'with invalid data' do
297
+ let(:data) { {name: nil, age: 12} }
298
+
299
+ it 'should return not persisted resource' do
300
+ expect(subject).to_not be_persisted
301
+ end
302
+
303
+ it 'should contain error hash' do
304
+ expect(subject.errors.to_hash).to eq name: %w[required]
305
+ end
306
+ end
307
+
308
+ context 'with additional data' do
309
+ let!(:req) do
310
+ stub_request(:post, 'http://users.example.org/users')
311
+ .with(body: '{"id":null,"name":"Anon","age":9,"born_at":"today"}')
312
+ .to_return response(id: 5, name: 'Anon', age: 9)
313
+ end
314
+ let(:data) { {age: 9, born_at: 'today'} }
315
+
316
+ it 'should store them in attributes' do
317
+ expect(subject.attributes).to eq 'id' => 5, 'name' => 'Anon',
318
+ 'age' => 9, 'born_at' => 'today'
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,548 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::QueryMethods do
6
+ let(:model) { MyUser }
7
+
8
+ describe '.find' do
9
+ context 'with single id' do
10
+ context 'with successful response' do
11
+ before do
12
+ stub_request(:get, 'http://users.example.org/users/1')
13
+ .to_return response id: 1, name: 'Anon', age: 12, born_at: 'Berlin'
14
+ stub_request(:get, 'http://users.example.org/users/2')
15
+ .to_return response id: 2, type: 'Customer',
16
+ name: 'Clare Customer', age: 24
17
+ end
18
+
19
+ let(:action) { ->(cb = nil) { model.find(1, &cb) } }
20
+ it_behaves_like 'a query method with multi-callback support'
21
+
22
+ it 'should load a single remote resource' do
23
+ user = action.call
24
+ Acfs.run
25
+
26
+ expect(user.attributes).to eq id: 1, name: 'Anon',
27
+ age: 12, born_at: 'Berlin'
28
+ end
29
+
30
+ context 'with resource type inheritance' do
31
+ let!(:user) { MyUser.find 2 }
32
+ subject { user }
33
+ before { Acfs.run }
34
+
35
+ it 'should respect resource type inheritance' do
36
+ expect(subject).to be_a Customer
37
+ end
38
+
39
+ it 'should implement ActiveModel class interface' do
40
+ expect(subject.class).to be_a ActiveModel::Naming
41
+ expect(subject.class).to be_a ActiveModel::Translation
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'with 404 response' do
47
+ before do
48
+ stub_request(:get, 'http://users.example.org/users/1')
49
+ .to_return response({error: 'not found'}, {status: 404})
50
+ end
51
+
52
+ it 'should raise a NotFound error' do
53
+ @user = model.find 1
54
+
55
+ expect { Acfs.run }.to raise_error(Acfs::ResourceNotFound)
56
+
57
+ expect(@user).to_not be_loaded
58
+ end
59
+ end
60
+
61
+ context 'with 500 response' do
62
+ before do
63
+ stub_request(:get, 'http://users.example.org/users/1')
64
+ .to_return response(nil, status: 500)
65
+ end
66
+
67
+ it 'should raise a response error' do
68
+ @user = model.find 1
69
+
70
+ expect { Acfs.run }.to raise_error(Acfs::ErroneousResponse)
71
+
72
+ expect(@user).to_not be_loaded
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'with multiple ids' do
78
+ before do
79
+ stub_request(:get, 'http://users.example.org/users/1')
80
+ .to_return response id: 1, name: 'Anon', age: 12
81
+ stub_request(:get, 'http://users.example.org/users/2')
82
+ .to_return response id: 2, name: 'Johnny', age: 42
83
+ stub_request(:get, 'http://users.example.org/users/3')
84
+ .to_return response id: 3, type: 'Customer', name: 'Anon', age: 12
85
+ stub_request(:get, 'http://users.example.org/users/4')
86
+ .to_return response id: 4, name: 'Johnny', age: 42
87
+ end
88
+
89
+ context 'with successful response' do
90
+ it 'should load a multiple remote resources' do
91
+ users = model.find([1, 2])
92
+ Acfs.run
93
+
94
+ expect(users.size).to be == 2
95
+ expect(users[0].attributes).to eq id: 1, name: 'Anon', age: 12
96
+ expect(users[1].attributes).to eq id: 2, name: 'Johnny', age: 42
97
+ end
98
+
99
+ it 'should invoke callback after all models are loaded' do
100
+ block = proc {}
101
+ expect(block).to receive(:call) do |users|
102
+ expect(users).to equal @users
103
+ expect(users.size).to be == 2
104
+ expect(users).to be_loaded
105
+ end
106
+
107
+ @users = model.find([1, 2], &block)
108
+ Acfs.run
109
+ end
110
+
111
+ it 'should invoke multiple callback after all models are loaded' do
112
+ proc1 = proc {}
113
+ proc2 = proc {}
114
+ expect(proc1).to receive(:call) do |users|
115
+ expect(users).to equal @users
116
+ expect(users.size).to be == 2
117
+ expect(users).to be_loaded
118
+ end
119
+ expect(proc2).to receive(:call) do |users|
120
+ expect(users).to equal @users
121
+ expect(users.size).to be == 2
122
+ expect(users).to be_loaded
123
+ end
124
+
125
+ @users = model.find([1, 2], &proc1)
126
+ Acfs.add_callback(@users, &proc2)
127
+ Acfs.run
128
+ end
129
+
130
+ it 'should respect resource type inheritance' do
131
+ customers = MyUser.find [3, 4]
132
+ Acfs.run
133
+
134
+ expect(customers[0]).to be_a Customer
135
+ expect(customers[1]).to be_a MyUser
136
+ end
137
+ end
138
+
139
+ context 'with one 404 response' do
140
+ before do
141
+ stub_request(:get, 'http://users.example.org/users/1')
142
+ .to_return response({error: 'not found'}, {status: 404})
143
+ end
144
+
145
+ it 'should raise resource not found error' do
146
+ model.find [1, 2]
147
+
148
+ expect { Acfs.run }.to raise_error(Acfs::ResourceNotFound)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '.all' do
155
+ let(:computer) { Computer }
156
+ let(:pc) { PC }
157
+ let(:mac) { Mac }
158
+ before do
159
+ stub_request(:get, 'http://computers.example.org/computers')
160
+ .to_return response [
161
+ {id: 1, type: 'PC'},
162
+ {id: 2, type: 'Computer'},
163
+ {id: 3, type: 'Mac'}
164
+ ]
165
+ end
166
+
167
+ it 'should invoke multiple callback after all models are loaded' do
168
+ proc1 = proc {}
169
+ proc2 = proc {}
170
+ expect(proc1).to receive(:call) do |computers|
171
+ expect(computers).to equal @computers
172
+ expect(computers.size).to be == 3
173
+ expect(computers).to be_loaded
174
+ end
175
+ expect(proc2).to receive(:call) do |computers|
176
+ expect(computers).to equal @computers
177
+ expect(computers.size).to be == 3
178
+ expect(computers).to be_loaded
179
+ end
180
+
181
+ @computers = computer.all(&proc1)
182
+ Acfs.add_callback(@computers, &proc2)
183
+ Acfs.run
184
+ end
185
+
186
+ context 'with resource type inheritance' do
187
+ it 'should create appropriate subclass resources' do
188
+ @computers = Computer.all
189
+
190
+ expect(@computers).to_not be_loaded
191
+
192
+ Acfs.run
193
+
194
+ expect(@computers).to be_loaded
195
+ expect(@computers).to have(3).items
196
+ expect(@computers[0]).to be_a PC
197
+ expect(@computers[1]).to be_a Computer
198
+ expect(@computers[2]).to be_a Mac
199
+ end
200
+
201
+ context 'with invalid type set' do
202
+ shared_examples 'with invalid type' do
203
+ it 'should raise error if type is no subclass' do
204
+ Computer.all
205
+ expect { Acfs.run }.to raise_error(Acfs::ResourceTypeError)
206
+ end
207
+ end
208
+
209
+ context 'with another resource as type instead' do
210
+ before do
211
+ stub_request(:get, 'http://computers.example.org/computers')
212
+ .to_return response [
213
+ {id: 1, type: 'MyUser'},
214
+ {id: 2, type: 'Computer'},
215
+ {id: 3, type: 'Mac'}
216
+ ]
217
+ end
218
+ it_behaves_like 'with invalid type'
219
+ end
220
+
221
+ context 'with a random string as type instead' do
222
+ before do
223
+ stub_request(:get, 'http://computers.example.org/computers')
224
+ .to_return response [
225
+ {id: 1, type: 'PC'},
226
+ {id: 2, type: 'noValidType'},
227
+ {id: 3, type: 'Mac'}
228
+ ]
229
+ end
230
+ it_behaves_like 'with invalid type'
231
+ end
232
+
233
+ context 'with a non-string as type instead' do
234
+ before do
235
+ stub_request(:get, 'http://computers.example.org/computers')
236
+ .to_return response [
237
+ {id: 1, type: 'PC'},
238
+ {id: 2, type: 'Computer'},
239
+ {id: 3, type: 42}
240
+ ]
241
+ end
242
+ it_behaves_like 'with invalid type'
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ shared_examples 'find_by' do
249
+ context 'standard resource' do
250
+ let(:model) { MyUser }
251
+ let!(:user) { model.send described_method, age: 24 }
252
+ subject { Acfs.run && user }
253
+
254
+ context 'return value' do
255
+ subject { user }
256
+
257
+ it { should be_a MyUser }
258
+ it { should_not be_loaded }
259
+ end
260
+
261
+ context 'with params' do
262
+ let!(:request) do
263
+ stub_request(:get, 'http://users.example.org/users?age=24')
264
+ .to_return response([{id: 1, name: 'Mike', age: 24}])
265
+ end
266
+
267
+ it 'should include params in URI to index action' do
268
+ subject
269
+ expect(request).to have_been_requested
270
+ end
271
+ end
272
+
273
+ context 'with non-empty response' do
274
+ before do
275
+ stub_request(:get, 'http://users.example.org/users?age=24')
276
+ .to_return response [
277
+ {id: 1, name: 'Mike', age: 24},
278
+ {id: 4, type: 'Maria', age: 24},
279
+ {id: 7, type: 'James', age: 24}
280
+ ]
281
+ end
282
+
283
+ it 'should invoke callback after model is loaded' do
284
+ block = proc {}
285
+
286
+ expect(block).to receive(:call) do |user|
287
+ expect(user).to eql @user.__getobj__
288
+ expect(user).to be_a MyUser
289
+ expect(user).to be_loaded
290
+ end
291
+
292
+ @user = model.send described_method, age: 24, &block
293
+ Acfs.run
294
+ end
295
+
296
+ it 'should invoke multiple callbacks after model is loaded' do
297
+ proc1 = proc {}
298
+ proc2 = proc {}
299
+
300
+ expect(proc1).to receive(:call) do |user|
301
+ expect(user).to eql @user.__getobj__
302
+ expect(user).to be_a MyUser
303
+ expect(user).to be_loaded
304
+ end
305
+
306
+ expect(proc2).to receive(:call) do |user|
307
+ expect(user).to eql @user.__getobj__
308
+ expect(user).to be_a MyUser
309
+ expect(user).to be_loaded
310
+ end
311
+
312
+ @user = model.send described_method, age: 24, &proc1
313
+ Acfs.add_callback @user, &proc2
314
+ Acfs.run
315
+ end
316
+
317
+ it 'should load a single MyUser object' do
318
+ expect(subject).to be_a MyUser
319
+ end
320
+ end
321
+ end
322
+
323
+ context 'singleton resource' do
324
+ let(:model) { Single }
325
+
326
+ it '.find_by should not be defined' do
327
+ expect { model.find_by }.to raise_error ::Acfs::UnsupportedOperation
328
+ end
329
+ end
330
+ end
331
+
332
+ describe '.find_by' do
333
+ let(:described_method) { :find_by }
334
+ it_behaves_like 'find_by'
335
+
336
+ context 'standard resource' do
337
+ let(:model) { MyUser }
338
+ let!(:user) { model.send described_method, age: 24 }
339
+ subject { Acfs.run && user }
340
+
341
+ context 'with empty response' do
342
+ before do
343
+ stub_request(:get, 'http://users.example.org/users?age=24')
344
+ .to_return response []
345
+ end
346
+
347
+ it { should be_nil }
348
+
349
+ it 'should invoke callback after model is loaded' do
350
+ block = proc {}
351
+
352
+ expect(block).to receive(:call) do |user|
353
+ expect(user).to eql @user.__getobj__
354
+ expect(user).to be_a NilClass
355
+ end
356
+
357
+ @user = model.find_by age: 24, &block
358
+ Acfs.run
359
+ end
360
+
361
+ it 'should invoke multiple callbacks after model is loaded' do
362
+ proc1 = proc {}
363
+ proc2 = proc {}
364
+
365
+ expect(proc1).to receive(:call) do |user|
366
+ expect(user).to eql @user.__getobj__
367
+ expect(user).to be_a NilClass
368
+ end
369
+ expect(proc2).to receive(:call) do |user|
370
+ expect(user).to eql @user.__getobj__
371
+ expect(user).to be_a NilClass
372
+ end
373
+
374
+ @user = model.find_by age: 24, &proc1
375
+ Acfs.add_callback @user, &proc2
376
+ Acfs.run
377
+ end
378
+ end
379
+ end
380
+ end
381
+
382
+ describe '.find_by!' do
383
+ let(:described_method) { :find_by! }
384
+ it_behaves_like 'find_by'
385
+
386
+ context 'standard resource' do
387
+ let(:model) { MyUser }
388
+ let!(:user) { model.send described_method, age: 24 }
389
+ subject { Acfs.run && user }
390
+
391
+ context 'with empty response' do
392
+ before do
393
+ stub_request(:get, 'http://users.example.org/users?age=24')
394
+ .to_return response []
395
+ end
396
+
397
+ it 'should raise an ResourceNotFound error' do
398
+ model.find_by! age: 24
399
+ expect { Acfs.run }.to raise_error Acfs::ResourceNotFound
400
+ end
401
+
402
+ it 'should not invoke callback after model could not be loaded' do
403
+ block = proc {}
404
+
405
+ expect(block).not_to receive(:call)
406
+
407
+ model.find_by! age: 24, &block
408
+ expect { Acfs.run }.to raise_error Acfs::ResourceNotFound
409
+ end
410
+ end
411
+ end
412
+
413
+ describe '#each_page' do
414
+ context 'without parameters' do
415
+ before do
416
+ stub_request(:get, 'http://users.example.org/users')
417
+ .to_return response([{id: 1, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
418
+ headers: {
419
+ 'X-Total-Pages' => '4',
420
+ 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
421
+ })
422
+ stub_request(:get, 'http://users.example.org/users?page=2')
423
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
424
+ headers: {
425
+ 'X-Total-Pages' => '4',
426
+ 'Link' => '<http://users.example.org/users?page=3>; rel="next"'
427
+ })
428
+ stub_request(:get, 'http://users.example.org/users?page=3')
429
+ .to_return response([{id: 3, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
430
+ headers: {
431
+ 'X-Total-Pages' => '4',
432
+ 'Link' => '<http://users.example.org/users?page=4>; rel="next"'
433
+ })
434
+ stub_request(:get, 'http://users.example.org/users?page=4')
435
+ .to_return response([{id: 4, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
436
+ headers: {
437
+ 'X-Total-Pages' => '4',
438
+ 'Link' => ''
439
+ })
440
+ end
441
+
442
+ it 'should iterate all pages' do
443
+ index = 0
444
+ model.each_page do |page|
445
+ expect(page).to be_a Acfs::Collection
446
+
447
+ index += 1
448
+ expect(page.first.id).to eq index
449
+ end
450
+ Acfs.run
451
+
452
+ expect(index).to eq 4
453
+ end
454
+ end
455
+
456
+ context 'with parameters' do
457
+ before do
458
+ stub_request(:get, 'http://users.example.org/users?param=bla')
459
+ .to_return response([{id: 1, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
460
+ headers: {
461
+ 'X-Total-Pages' => '4',
462
+ 'Link' => '<http://users.example.org/users?where=fuu&page=2>; rel="next"'
463
+ })
464
+ stub_request(:get, 'http://users.example.org/users?where=fuu&page=2')
465
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
466
+ headers: {
467
+ 'X-Total-Pages' => '4',
468
+ 'Link' => '<http://users.example.org/users?page=3>; rel="next"'
469
+ })
470
+ stub_request(:get, 'http://users.example.org/users?page=3')
471
+ .to_return response([{id: 3, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
472
+ headers: {
473
+ 'X-Total-Pages' => '4',
474
+ 'Link' => '<http://users.example.org/users?page=4>; rel="next"'
475
+ })
476
+ stub_request(:get, 'http://users.example.org/users?page=4')
477
+ .to_return response([{id: 4, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
478
+ headers: {
479
+ 'X-Total-Pages' => '4',
480
+ 'Link' => ''
481
+ })
482
+ end
483
+
484
+ it 'should call first page with params and follow relations' do
485
+ index = 0
486
+ model.each_page(param: 'bla') do |page|
487
+ expect(page).to be_a Acfs::Collection
488
+
489
+ index += 1
490
+ expect(page.first.id).to eq index
491
+ end
492
+ Acfs.run
493
+
494
+ expect(index).to eq 4
495
+ end
496
+ end
497
+ end
498
+
499
+ describe '#each_item' do
500
+ context 'without parameters' do
501
+ before do
502
+ stub_request(:get, 'http://users.example.org/users')
503
+ .to_return response([{id: 1, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
504
+ headers: {
505
+ 'X-Total-Pages' => '4',
506
+ 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
507
+ })
508
+ stub_request(:get, 'http://users.example.org/users?page=2')
509
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
510
+ headers: {
511
+ 'X-Total-Pages' => '4',
512
+ 'Link' => '<http://users.example.org/users?page=3>; rel="next"'
513
+ })
514
+ stub_request(:get, 'http://users.example.org/users?page=3')
515
+ .to_return response([{id: 3, name: 'Anno', age: 1604, born_at: 'Santa Maria'}, {id: 4, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
516
+ headers: {
517
+ 'X-Total-Pages' => '4',
518
+ 'Link' => '<http://users.example.org/users?page=4>; rel="next"'
519
+ })
520
+ stub_request(:get, 'http://users.example.org/users?page=4')
521
+ .to_return response([{id: 5, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
522
+ headers: {
523
+ 'X-Total-Pages' => '4',
524
+ 'Link' => ''
525
+ })
526
+ end
527
+
528
+ it 'should iterate all pages' do
529
+ indices = []
530
+ model.each_item do |item|
531
+ expect(item).to be_a MyUser
532
+ indices << item.id
533
+ end
534
+ Acfs.run
535
+
536
+ expect(indices).to eq [1, 2, 3, 4, 5]
537
+ end
538
+
539
+ it 'should pass the collection to the provided block' do
540
+ model.each_item do |_item, collection|
541
+ expect(collection).to be_a Acfs::Collection
542
+ end
543
+ Acfs.run
544
+ end
545
+ end
546
+ end
547
+ end
548
+ end