acfs 1.3.3 → 1.6.0

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +372 -0
  3. data/LICENSE +22 -0
  4. data/README.md +321 -0
  5. data/acfs.gemspec +38 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +26 -0
  8. data/lib/acfs/adapter/typhoeus.rb +82 -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 +147 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +76 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +31 -0
  17. data/lib/acfs/middleware/logger.rb +23 -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 +96 -0
  22. data/lib/acfs/request.rb +32 -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 +270 -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 +135 -0
  39. data/lib/acfs/resource/operational.rb +26 -0
  40. data/lib/acfs/resource/persistence.rb +258 -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 +49 -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 +94 -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 +199 -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 +79 -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 +179 -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 +42 -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 +159 -26
@@ -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)
188
+ model.update_attributes({name: 'Idefix'})
189
+ end
190
+
191
+ it 'should kwargs 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!)
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