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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +372 -0
- data/LICENSE +22 -0
- data/README.md +321 -0
- data/acfs.gemspec +38 -0
- data/lib/acfs.rb +51 -0
- data/lib/acfs/adapter/base.rb +26 -0
- data/lib/acfs/adapter/typhoeus.rb +82 -0
- data/lib/acfs/collection.rb +28 -0
- data/lib/acfs/collections/paginatable.rb +76 -0
- data/lib/acfs/configuration.rb +120 -0
- data/lib/acfs/errors.rb +147 -0
- data/lib/acfs/global.rb +101 -0
- data/lib/acfs/location.rb +76 -0
- data/lib/acfs/middleware/base.rb +24 -0
- data/lib/acfs/middleware/json.rb +31 -0
- data/lib/acfs/middleware/logger.rb +23 -0
- data/lib/acfs/middleware/msgpack.rb +32 -0
- data/lib/acfs/middleware/print.rb +23 -0
- data/lib/acfs/middleware/serializer.rb +41 -0
- data/lib/acfs/operation.rb +96 -0
- data/lib/acfs/request.rb +32 -0
- data/lib/acfs/request/callbacks.rb +54 -0
- data/lib/acfs/resource.rb +39 -0
- data/lib/acfs/resource/attributes.rb +270 -0
- data/lib/acfs/resource/attributes/base.rb +29 -0
- data/lib/acfs/resource/attributes/boolean.rb +39 -0
- data/lib/acfs/resource/attributes/date_time.rb +32 -0
- data/lib/acfs/resource/attributes/dict.rb +39 -0
- data/lib/acfs/resource/attributes/float.rb +33 -0
- data/lib/acfs/resource/attributes/integer.rb +29 -0
- data/lib/acfs/resource/attributes/list.rb +36 -0
- data/lib/acfs/resource/attributes/string.rb +26 -0
- data/lib/acfs/resource/attributes/uuid.rb +48 -0
- data/lib/acfs/resource/dirty.rb +37 -0
- data/lib/acfs/resource/initialization.rb +31 -0
- data/lib/acfs/resource/loadable.rb +35 -0
- data/lib/acfs/resource/locatable.rb +135 -0
- data/lib/acfs/resource/operational.rb +26 -0
- data/lib/acfs/resource/persistence.rb +258 -0
- data/lib/acfs/resource/query_methods.rb +266 -0
- data/lib/acfs/resource/service.rb +44 -0
- data/lib/acfs/resource/validation.rb +49 -0
- data/lib/acfs/response.rb +30 -0
- data/lib/acfs/response/formats.rb +27 -0
- data/lib/acfs/response/status.rb +33 -0
- data/lib/acfs/rspec.rb +13 -0
- data/lib/acfs/runner.rb +102 -0
- data/lib/acfs/service.rb +94 -0
- data/lib/acfs/service/middleware.rb +58 -0
- data/lib/acfs/service/middleware/stack.rb +65 -0
- data/lib/acfs/singleton_resource.rb +85 -0
- data/lib/acfs/stub.rb +199 -0
- data/lib/acfs/util.rb +22 -0
- data/lib/acfs/version.rb +16 -0
- data/lib/acfs/yard.rb +6 -0
- data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
- data/spec/acfs/collection_spec.rb +157 -0
- data/spec/acfs/configuration_spec.rb +53 -0
- data/spec/acfs/global_spec.rb +140 -0
- data/spec/acfs/location_spec.rb +25 -0
- data/spec/acfs/middleware/json_spec.rb +79 -0
- data/spec/acfs/middleware/msgpack_spec.rb +62 -0
- data/spec/acfs/operation_spec.rb +12 -0
- data/spec/acfs/request/callbacks_spec.rb +48 -0
- data/spec/acfs/request_spec.rb +79 -0
- data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
- data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
- data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
- data/spec/acfs/resource/attributes/float_spec.rb +61 -0
- data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
- data/spec/acfs/resource/attributes/list_spec.rb +60 -0
- data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
- data/spec/acfs/resource/attributes_spec.rb +179 -0
- data/spec/acfs/resource/dirty_spec.rb +49 -0
- data/spec/acfs/resource/initialization_spec.rb +36 -0
- data/spec/acfs/resource/loadable_spec.rb +22 -0
- data/spec/acfs/resource/locatable_spec.rb +118 -0
- data/spec/acfs/resource/persistance_spec.rb +322 -0
- data/spec/acfs/resource/query_methods_spec.rb +548 -0
- data/spec/acfs/resource/validation_spec.rb +129 -0
- data/spec/acfs/response/formats_spec.rb +52 -0
- data/spec/acfs/response/status_spec.rb +71 -0
- data/spec/acfs/runner_spec.rb +95 -0
- data/spec/acfs/service/middleware_spec.rb +35 -0
- data/spec/acfs/service_spec.rb +48 -0
- data/spec/acfs/singleton_resource_spec.rb +17 -0
- data/spec/acfs/stub_spec.rb +345 -0
- data/spec/acfs_spec.rb +205 -0
- data/spec/fixtures/config.yml +14 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/hash.rb +11 -0
- data/spec/support/response.rb +12 -0
- data/spec/support/service.rb +92 -0
- data/spec/support/shared/find_callbacks.rb +50 -0
- 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
|