cuprum-collections 0.3.0 → 0.4.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 +48 -0
- data/DEVELOPMENT.md +2 -2
- data/README.md +13 -11
- data/lib/cuprum/collections/association.rb +256 -0
- data/lib/cuprum/collections/associations/belongs_to.rb +32 -0
- data/lib/cuprum/collections/associations/has_many.rb +23 -0
- data/lib/cuprum/collections/associations/has_one.rb +23 -0
- data/lib/cuprum/collections/associations.rb +10 -0
- data/lib/cuprum/collections/basic/collection.rb +39 -74
- data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
- data/lib/cuprum/collections/basic/commands/find_matching.rb +1 -1
- data/lib/cuprum/collections/basic/repository.rb +9 -33
- data/lib/cuprum/collections/basic.rb +1 -0
- data/lib/cuprum/collections/collection.rb +154 -0
- data/lib/cuprum/collections/commands/associations/find_many.rb +161 -0
- data/lib/cuprum/collections/commands/associations/require_many.rb +48 -0
- data/lib/cuprum/collections/commands/associations.rb +13 -0
- data/lib/cuprum/collections/commands/find_one_matching.rb +1 -1
- data/lib/cuprum/collections/commands.rb +1 -0
- data/lib/cuprum/collections/errors/abstract_find_error.rb +1 -1
- data/lib/cuprum/collections/relation.rb +401 -0
- data/lib/cuprum/collections/repository.rb +71 -4
- data/lib/cuprum/collections/resource.rb +65 -0
- data/lib/cuprum/collections/rspec/contracts/association_contracts.rb +2137 -0
- data/lib/cuprum/collections/rspec/contracts/basic/command_contracts.rb +484 -0
- data/lib/cuprum/collections/rspec/contracts/basic.rb +11 -0
- data/lib/cuprum/collections/rspec/contracts/collection_contracts.rb +429 -0
- data/lib/cuprum/collections/rspec/contracts/command_contracts.rb +1462 -0
- data/lib/cuprum/collections/rspec/contracts/query_contracts.rb +1093 -0
- data/lib/cuprum/collections/rspec/contracts/relation_contracts.rb +1381 -0
- data/lib/cuprum/collections/rspec/contracts/repository_contracts.rb +605 -0
- data/lib/cuprum/collections/rspec/contracts.rb +23 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +85 -82
- data/lib/cuprum/collections/rspec.rb +4 -1
- data/lib/cuprum/collections/version.rb +1 -1
- data/lib/cuprum/collections.rb +9 -4
- metadata +23 -19
- data/lib/cuprum/collections/base.rb +0 -11
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +0 -392
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +0 -168
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +0 -93
- data/lib/cuprum/collections/rspec/collection_contract.rb +0 -190
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +0 -108
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +0 -407
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +0 -194
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +0 -157
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +0 -84
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +0 -92
- data/lib/cuprum/collections/rspec/query_contract.rb +0 -650
- data/lib/cuprum/collections/rspec/querying_contract.rb +0 -298
- data/lib/cuprum/collections/rspec/repository_contract.rb +0 -235
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +0 -80
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +0 -96
@@ -0,0 +1,1462 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/rspec/contracts'
|
4
|
+
|
5
|
+
module Cuprum::Collections::RSpec::Contracts
|
6
|
+
# Contracts for asserting on Command objects.
|
7
|
+
module CommandContracts
|
8
|
+
# Contract validating the behavior of an AssignOne command implementation.
|
9
|
+
module ShouldBeAnAssignOneCommandContract
|
10
|
+
extend RSpec::SleepingKingStudios::Contract
|
11
|
+
|
12
|
+
# @!method apply(example_group, allow_extra_attributes:)
|
13
|
+
# Adds the contract to the example group.
|
14
|
+
#
|
15
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
16
|
+
# which the contract is applied.
|
17
|
+
# @param allow_extra_attributes [Boolean] if false, the command should
|
18
|
+
# fail if given attributes not defined for the entity.
|
19
|
+
contract do |allow_extra_attributes:|
|
20
|
+
describe '#call' do
|
21
|
+
shared_examples 'should assign the attributes' do
|
22
|
+
it { expect(result).to be_a_passing_result }
|
23
|
+
|
24
|
+
it { expect(result.value).to be_a entity.class }
|
25
|
+
|
26
|
+
it { expect(result.value).to be == expected_value }
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:attributes) { {} }
|
30
|
+
let(:result) do
|
31
|
+
command.call(attributes: attributes, entity: entity)
|
32
|
+
end
|
33
|
+
let(:expected_attributes) do
|
34
|
+
initial_attributes.merge(attributes)
|
35
|
+
end
|
36
|
+
let(:expected_value) do
|
37
|
+
defined?(super()) ? super() : expected_attributes
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should validate the :attributes keyword' do
|
41
|
+
expect(command)
|
42
|
+
.to validate_parameter(:call, :attributes)
|
43
|
+
.using_constraint(
|
44
|
+
Stannum::Constraints::Types::HashWithIndifferentKeys.new
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should validate the :entity keyword' do
|
49
|
+
expect(command)
|
50
|
+
.to validate_parameter(:call, :entity)
|
51
|
+
.using_constraint(entity_type)
|
52
|
+
.with_parameters(attributes: {}, entity: nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'with an empty attributes hash' do
|
56
|
+
let(:attributes) { {} }
|
57
|
+
|
58
|
+
include_examples 'should assign the attributes'
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'with an attributes hash with partial attributes' do
|
62
|
+
let(:attributes) { { title: 'Gideon the Ninth' } }
|
63
|
+
|
64
|
+
include_examples 'should assign the attributes'
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'with an attributes hash with full attributes' do
|
68
|
+
let(:attributes) do
|
69
|
+
{
|
70
|
+
title: 'Gideon the Ninth',
|
71
|
+
author: 'Tamsyn Muir',
|
72
|
+
series: 'The Locked Tomb',
|
73
|
+
category: 'Horror'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
include_examples 'should assign the attributes'
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'with an attributes hash with extra attributes' do
|
81
|
+
let(:attributes) do
|
82
|
+
{
|
83
|
+
title: 'The Book of Lost Tales',
|
84
|
+
audiobook: true
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
if allow_extra_attributes
|
89
|
+
include_examples 'should assign the attributes'
|
90
|
+
else
|
91
|
+
# :nocov:
|
92
|
+
let(:valid_attributes) do
|
93
|
+
defined?(super()) ? super() : expected_attributes.keys
|
94
|
+
end
|
95
|
+
let(:expected_error) do
|
96
|
+
Cuprum::Collections::Errors::ExtraAttributes.new(
|
97
|
+
entity_class: entity.class,
|
98
|
+
extra_attributes: %w[audiobook],
|
99
|
+
valid_attributes: valid_attributes
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should return a failing result' do
|
104
|
+
expect(result).to be_a_failing_result.with_error(expected_error)
|
105
|
+
end
|
106
|
+
# :nocov:
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when the entity has existing attributes' do
|
111
|
+
let(:initial_attributes) do
|
112
|
+
# :nocov:
|
113
|
+
if defined?(super())
|
114
|
+
super().merge(fixtures_data.first)
|
115
|
+
else
|
116
|
+
fixtures_data.first
|
117
|
+
end
|
118
|
+
# :nocov:
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'with an empty attributes hash' do
|
122
|
+
let(:attributes) { {} }
|
123
|
+
|
124
|
+
include_examples 'should assign the attributes'
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'with an attributes hash with partial attributes' do
|
128
|
+
let(:attributes) { { title: 'Gideon the Ninth' } }
|
129
|
+
|
130
|
+
include_examples 'should assign the attributes'
|
131
|
+
end
|
132
|
+
|
133
|
+
describe 'with an attributes hash with full attributes' do
|
134
|
+
let(:attributes) do
|
135
|
+
{
|
136
|
+
title: 'Gideon the Ninth',
|
137
|
+
author: 'Tamsyn Muir',
|
138
|
+
series: 'The Locked Tomb',
|
139
|
+
category: 'Horror'
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
include_examples 'should assign the attributes'
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'with an attributes hash with extra attributes' do
|
147
|
+
let(:attributes) do
|
148
|
+
{
|
149
|
+
title: 'The Book of Lost Tales',
|
150
|
+
audiobook: true
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
if allow_extra_attributes
|
155
|
+
include_examples 'should assign the attributes'
|
156
|
+
else
|
157
|
+
# :nocov:
|
158
|
+
let(:valid_attributes) do
|
159
|
+
defined?(super()) ? super() : expected_attributes.keys
|
160
|
+
end
|
161
|
+
let(:expected_error) do
|
162
|
+
Cuprum::Collections::Errors::ExtraAttributes.new(
|
163
|
+
entity_class: entity.class,
|
164
|
+
extra_attributes: %w[audiobook],
|
165
|
+
valid_attributes: valid_attributes
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should return a failing result' do
|
170
|
+
expect(result)
|
171
|
+
.to be_a_failing_result
|
172
|
+
.with_error(expected_error)
|
173
|
+
end
|
174
|
+
# :nocov:
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Contract validating the behavior of a Build command implementation.
|
183
|
+
module ShouldBeABuildOneCommandContract
|
184
|
+
extend RSpec::SleepingKingStudios::Contract
|
185
|
+
|
186
|
+
# @!method apply(example_group, allow_extra_attributes:)
|
187
|
+
# Adds the contract to the example group.
|
188
|
+
#
|
189
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
190
|
+
# which the contract is applied.
|
191
|
+
# @param allow_extra_attributes [Boolean] if false, the command should
|
192
|
+
# fail if given attributes not defined for the entity.
|
193
|
+
contract do |allow_extra_attributes:|
|
194
|
+
include Stannum::RSpec::Matchers
|
195
|
+
|
196
|
+
describe '#call' do
|
197
|
+
shared_examples 'should build the entity' do
|
198
|
+
it { expect(result).to be_a_passing_result }
|
199
|
+
|
200
|
+
it { expect(result.value).to be == expected_value }
|
201
|
+
end
|
202
|
+
|
203
|
+
let(:attributes) { {} }
|
204
|
+
let(:result) { command.call(attributes: attributes) }
|
205
|
+
let(:expected_attributes) do
|
206
|
+
attributes
|
207
|
+
end
|
208
|
+
let(:expected_value) do
|
209
|
+
defined?(super()) ? super() : attributes
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should validate the :attributes keyword' do
|
213
|
+
expect(command)
|
214
|
+
.to validate_parameter(:call, :attributes)
|
215
|
+
.using_constraint(
|
216
|
+
Stannum::Constraints::Types::HashWithIndifferentKeys.new
|
217
|
+
)
|
218
|
+
end
|
219
|
+
|
220
|
+
describe 'with an empty attributes hash' do
|
221
|
+
let(:attributes) { {} }
|
222
|
+
|
223
|
+
include_examples 'should build the entity'
|
224
|
+
end
|
225
|
+
|
226
|
+
describe 'with an attributes hash with partial attributes' do
|
227
|
+
let(:attributes) { { title: 'Gideon the Ninth' } }
|
228
|
+
|
229
|
+
include_examples 'should build the entity'
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'with an attributes hash with full attributes' do
|
233
|
+
let(:attributes) do
|
234
|
+
{
|
235
|
+
title: 'Gideon the Ninth',
|
236
|
+
author: 'Tamsyn Muir',
|
237
|
+
series: 'The Locked Tomb',
|
238
|
+
category: 'Horror'
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
include_examples 'should build the entity'
|
243
|
+
end
|
244
|
+
|
245
|
+
describe 'with an attributes hash with extra attributes' do
|
246
|
+
let(:attributes) do
|
247
|
+
{
|
248
|
+
title: 'The Book of Lost Tales',
|
249
|
+
audiobook: true
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
if allow_extra_attributes
|
254
|
+
include_examples 'should build the entity'
|
255
|
+
else
|
256
|
+
# :nocov:
|
257
|
+
let(:valid_attributes) do
|
258
|
+
defined?(super()) ? super() : expected_attributes.keys
|
259
|
+
end
|
260
|
+
let(:expected_error) do
|
261
|
+
Cuprum::Collections::Errors::ExtraAttributes.new(
|
262
|
+
entity_class: entity_type,
|
263
|
+
extra_attributes: %w[audiobook],
|
264
|
+
valid_attributes: valid_attributes
|
265
|
+
)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'should return a failing result' do
|
269
|
+
expect(result).to be_a_failing_result.with_error(expected_error)
|
270
|
+
end
|
271
|
+
# :nocov:
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Contract validating the behavior of a FindOne command implementation.
|
279
|
+
module ShouldBeADestroyOneCommandContract
|
280
|
+
extend RSpec::SleepingKingStudios::Contract
|
281
|
+
|
282
|
+
# @!method apply(example_group)
|
283
|
+
# Adds the contract to the example group.
|
284
|
+
#
|
285
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
286
|
+
# which the contract is applied.
|
287
|
+
contract do
|
288
|
+
describe '#call' do
|
289
|
+
let(:mapped_data) do
|
290
|
+
defined?(super()) ? super() : data
|
291
|
+
end
|
292
|
+
let(:primary_key_name) { defined?(super()) ? super() : 'id' }
|
293
|
+
let(:primary_key_type) { defined?(super()) ? super() : Integer }
|
294
|
+
let(:invalid_primary_key_value) do
|
295
|
+
defined?(super()) ? super() : 100
|
296
|
+
end
|
297
|
+
let(:valid_primary_key_value) do
|
298
|
+
defined?(super()) ? super() : 0
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should validate the :primary_key keyword' do
|
302
|
+
expect(command)
|
303
|
+
.to validate_parameter(:call, :primary_key)
|
304
|
+
.using_constraint(primary_key_type)
|
305
|
+
end
|
306
|
+
|
307
|
+
describe 'with an invalid primary key' do
|
308
|
+
let(:primary_key) { invalid_primary_key_value }
|
309
|
+
let(:expected_error) do
|
310
|
+
Cuprum::Collections::Errors::NotFound.new(
|
311
|
+
attribute_name: primary_key_name,
|
312
|
+
attribute_value: primary_key,
|
313
|
+
collection_name: collection_name,
|
314
|
+
primary_key: true
|
315
|
+
)
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'should return a failing result' do
|
319
|
+
expect(command.call(primary_key: primary_key))
|
320
|
+
.to be_a_failing_result
|
321
|
+
.with_error(expected_error)
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'should not remove an entity from the collection' do
|
325
|
+
expect { command.call(primary_key: primary_key) }
|
326
|
+
.not_to(change { query.reset.count })
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context 'when the collection has many items' do
|
331
|
+
let(:data) { fixtures_data }
|
332
|
+
let(:matching_data) do
|
333
|
+
mapped_data.find do |item|
|
334
|
+
item[primary_key_name.to_s] == primary_key
|
335
|
+
end
|
336
|
+
end
|
337
|
+
let!(:expected_data) do
|
338
|
+
defined?(super()) ? super() : matching_data
|
339
|
+
end
|
340
|
+
|
341
|
+
describe 'with an invalid primary key' do
|
342
|
+
let(:primary_key) { invalid_primary_key_value }
|
343
|
+
let(:expected_error) do
|
344
|
+
Cuprum::Collections::Errors::NotFound.new(
|
345
|
+
attribute_name: primary_key_name,
|
346
|
+
attribute_value: primary_key,
|
347
|
+
collection_name: collection_name,
|
348
|
+
primary_key: true
|
349
|
+
)
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should return a failing result' do
|
353
|
+
expect(command.call(primary_key: primary_key))
|
354
|
+
.to be_a_failing_result
|
355
|
+
.with_error(expected_error)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'should not remove an entity from the collection' do
|
359
|
+
expect { command.call(primary_key: primary_key) }
|
360
|
+
.not_to(change { query.reset.count })
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe 'with a valid primary key' do
|
365
|
+
let(:primary_key) { valid_primary_key_value }
|
366
|
+
|
367
|
+
it 'should return a passing result' do
|
368
|
+
expect(command.call(primary_key: primary_key))
|
369
|
+
.to be_a_passing_result
|
370
|
+
.with_value(expected_data)
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'should remove an entity from the collection' do
|
374
|
+
expect { command.call(primary_key: primary_key) }
|
375
|
+
.to(
|
376
|
+
change { query.reset.count }.by(-1)
|
377
|
+
)
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'should remove the entity from the collection' do
|
381
|
+
command.call(primary_key: primary_key)
|
382
|
+
|
383
|
+
expect(query.map { |item| item[primary_key_name.to_s] })
|
384
|
+
.not_to include primary_key
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Contract validating the behavior of a FindMany command implementation.
|
393
|
+
module ShouldBeAFindManyCommandContract
|
394
|
+
extend RSpec::SleepingKingStudios::Contract
|
395
|
+
|
396
|
+
# @!method apply(example_group)
|
397
|
+
# Adds the contract to the example group.
|
398
|
+
#
|
399
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
400
|
+
# which the contract is applied.
|
401
|
+
contract do
|
402
|
+
describe '#call' do
|
403
|
+
let(:mapped_data) do
|
404
|
+
defined?(super()) ? super() : data
|
405
|
+
end
|
406
|
+
let(:primary_key_name) { defined?(super()) ? super() : 'id' }
|
407
|
+
let(:primary_key_type) { defined?(super()) ? super() : Integer }
|
408
|
+
let(:primary_keys_contract) do
|
409
|
+
Stannum::Constraints::Types::ArrayType
|
410
|
+
.new(item_type: primary_key_type)
|
411
|
+
end
|
412
|
+
let(:invalid_primary_key_values) do
|
413
|
+
defined?(super()) ? super() : [100, 101, 102]
|
414
|
+
end
|
415
|
+
let(:valid_primary_key_values) do
|
416
|
+
defined?(super()) ? super() : [0, 1, 2]
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'should validate the :allow_partial keyword' do
|
420
|
+
expect(command)
|
421
|
+
.to validate_parameter(:call, :allow_partial)
|
422
|
+
.using_constraint(Stannum::Constraints::Boolean.new)
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'should validate the :envelope keyword' do
|
426
|
+
expect(command)
|
427
|
+
.to validate_parameter(:call, :envelope)
|
428
|
+
.using_constraint(Stannum::Constraints::Boolean.new)
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should validate the :primary_keys keyword' do
|
432
|
+
expect(command)
|
433
|
+
.to validate_parameter(:call, :primary_keys)
|
434
|
+
.using_constraint(Array)
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'should validate the :primary_keys keyword items' do
|
438
|
+
expect(command)
|
439
|
+
.to validate_parameter(:call, :primary_keys)
|
440
|
+
.with_value([nil])
|
441
|
+
.using_constraint(primary_keys_contract)
|
442
|
+
end
|
443
|
+
|
444
|
+
it 'should validate the :scope keyword' do
|
445
|
+
expect(command)
|
446
|
+
.to validate_parameter(:call, :scope)
|
447
|
+
.using_constraint(
|
448
|
+
Stannum::Constraints::Type.new(query.class, optional: true)
|
449
|
+
)
|
450
|
+
.with_value(Object.new.freeze)
|
451
|
+
end
|
452
|
+
|
453
|
+
describe 'with an array of invalid primary keys' do
|
454
|
+
let(:primary_keys) { invalid_primary_key_values }
|
455
|
+
let(:expected_error) do
|
456
|
+
Cuprum::Errors::MultipleErrors.new(
|
457
|
+
errors: primary_keys.map do |primary_key|
|
458
|
+
Cuprum::Collections::Errors::NotFound.new(
|
459
|
+
attribute_name: primary_key_name,
|
460
|
+
attribute_value: primary_key,
|
461
|
+
collection_name: command.collection_name,
|
462
|
+
primary_key: true
|
463
|
+
)
|
464
|
+
end
|
465
|
+
)
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'should return a failing result' do
|
469
|
+
expect(command.call(primary_keys: primary_keys))
|
470
|
+
.to be_a_failing_result
|
471
|
+
.with_error(expected_error)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context 'when the collection has many items' do
|
476
|
+
let(:data) { fixtures_data }
|
477
|
+
let(:matching_data) do
|
478
|
+
primary_keys
|
479
|
+
.map do |key|
|
480
|
+
mapped_data.find { |item| item[primary_key_name.to_s] == key }
|
481
|
+
end
|
482
|
+
end
|
483
|
+
let(:expected_data) do
|
484
|
+
defined?(super()) ? super() : matching_data
|
485
|
+
end
|
486
|
+
|
487
|
+
describe 'with an array of invalid primary keys' do
|
488
|
+
let(:primary_keys) { invalid_primary_key_values }
|
489
|
+
let(:expected_error) do
|
490
|
+
Cuprum::Errors::MultipleErrors.new(
|
491
|
+
errors: primary_keys.map do |primary_key|
|
492
|
+
Cuprum::Collections::Errors::NotFound.new(
|
493
|
+
attribute_name: primary_key_name,
|
494
|
+
attribute_value: primary_key,
|
495
|
+
collection_name: command.collection_name,
|
496
|
+
primary_key: true
|
497
|
+
)
|
498
|
+
end
|
499
|
+
)
|
500
|
+
end
|
501
|
+
|
502
|
+
it 'should return a failing result' do
|
503
|
+
expect(command.call(primary_keys: primary_keys))
|
504
|
+
.to be_a_failing_result
|
505
|
+
.with_error(expected_error)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
describe 'with a partially valid array of primary keys' do
|
510
|
+
let(:primary_keys) do
|
511
|
+
invalid_primary_key_values + valid_primary_key_values
|
512
|
+
end
|
513
|
+
let(:expected_error) do
|
514
|
+
Cuprum::Errors::MultipleErrors.new(
|
515
|
+
errors: primary_keys.map do |primary_key|
|
516
|
+
unless invalid_primary_key_values.include?(primary_key)
|
517
|
+
next nil
|
518
|
+
end
|
519
|
+
|
520
|
+
Cuprum::Collections::Errors::NotFound.new(
|
521
|
+
attribute_name: primary_key_name,
|
522
|
+
attribute_value: primary_key,
|
523
|
+
collection_name: command.collection_name,
|
524
|
+
primary_key: true
|
525
|
+
)
|
526
|
+
end
|
527
|
+
)
|
528
|
+
end
|
529
|
+
|
530
|
+
it 'should return a failing result' do
|
531
|
+
expect(command.call(primary_keys: primary_keys))
|
532
|
+
.to be_a_failing_result
|
533
|
+
.with_error(expected_error)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
describe 'with a valid array of primary keys' do
|
538
|
+
let(:primary_keys) { valid_primary_key_values }
|
539
|
+
|
540
|
+
it 'should return a passing result' do
|
541
|
+
expect(command.call(primary_keys: primary_keys))
|
542
|
+
.to be_a_passing_result
|
543
|
+
.with_value(expected_data)
|
544
|
+
end
|
545
|
+
|
546
|
+
describe 'with an ordered array of primary keys' do
|
547
|
+
let(:primary_keys) { valid_primary_key_values.reverse }
|
548
|
+
|
549
|
+
it 'should return a passing result' do
|
550
|
+
expect(command.call(primary_keys: primary_keys))
|
551
|
+
.to be_a_passing_result
|
552
|
+
.with_value(expected_data)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
describe 'with allow_partial: true' do
|
558
|
+
describe 'with an array of invalid primary keys' do
|
559
|
+
let(:primary_keys) { invalid_primary_key_values }
|
560
|
+
let(:expected_error) do
|
561
|
+
Cuprum::Errors::MultipleErrors.new(
|
562
|
+
errors: invalid_primary_key_values.map do |primary_key|
|
563
|
+
Cuprum::Collections::Errors::NotFound.new(
|
564
|
+
attribute_name: primary_key_name,
|
565
|
+
attribute_value: primary_key,
|
566
|
+
collection_name: command.collection_name,
|
567
|
+
primary_key: true
|
568
|
+
)
|
569
|
+
end
|
570
|
+
)
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'should return a failing result' do
|
574
|
+
expect(command.call(primary_keys: primary_keys))
|
575
|
+
.to be_a_failing_result
|
576
|
+
.with_error(expected_error)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
describe 'with a partially valid array of primary keys' do
|
581
|
+
let(:primary_keys) do
|
582
|
+
invalid_primary_key_values + valid_primary_key_values
|
583
|
+
end
|
584
|
+
let(:expected_error) do
|
585
|
+
Cuprum::Errors::MultipleErrors.new(
|
586
|
+
errors: primary_keys.map do |primary_key|
|
587
|
+
unless invalid_primary_key_values.include?(primary_key)
|
588
|
+
next nil
|
589
|
+
end
|
590
|
+
|
591
|
+
Cuprum::Collections::Errors::NotFound.new(
|
592
|
+
attribute_name: primary_key_name,
|
593
|
+
attribute_value: primary_key,
|
594
|
+
collection_name: command.collection_name,
|
595
|
+
primary_key: true
|
596
|
+
)
|
597
|
+
end
|
598
|
+
)
|
599
|
+
end
|
600
|
+
|
601
|
+
it 'should return a passing result' do
|
602
|
+
expect(
|
603
|
+
command.call(
|
604
|
+
primary_keys: primary_keys,
|
605
|
+
allow_partial: true
|
606
|
+
)
|
607
|
+
)
|
608
|
+
.to be_a_passing_result
|
609
|
+
.with_value(expected_data)
|
610
|
+
.and_error(expected_error)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
describe 'with a valid array of primary keys' do
|
615
|
+
let(:primary_keys) { valid_primary_key_values }
|
616
|
+
|
617
|
+
it 'should return a passing result' do
|
618
|
+
expect(
|
619
|
+
command.call(
|
620
|
+
primary_keys: primary_keys,
|
621
|
+
allow_partial: true
|
622
|
+
)
|
623
|
+
)
|
624
|
+
.to be_a_passing_result
|
625
|
+
.with_value(expected_data)
|
626
|
+
end
|
627
|
+
|
628
|
+
describe 'with an ordered array of primary keys' do
|
629
|
+
let(:primary_keys) { valid_primary_key_values.reverse }
|
630
|
+
|
631
|
+
it 'should return a passing result' do
|
632
|
+
expect(
|
633
|
+
command.call(
|
634
|
+
primary_keys: primary_keys,
|
635
|
+
allow_partial: true
|
636
|
+
)
|
637
|
+
)
|
638
|
+
.to be_a_passing_result
|
639
|
+
.with_value(expected_data)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
describe 'with envelope: true' do
|
646
|
+
describe 'with a valid array of primary keys' do
|
647
|
+
let(:primary_keys) { valid_primary_key_values }
|
648
|
+
|
649
|
+
it 'should return a passing result' do
|
650
|
+
expect(
|
651
|
+
command.call(primary_keys: primary_keys, envelope: true)
|
652
|
+
)
|
653
|
+
.to be_a_passing_result
|
654
|
+
.with_value({ collection_name => expected_data })
|
655
|
+
end
|
656
|
+
|
657
|
+
describe 'with an ordered array of primary keys' do
|
658
|
+
let(:primary_keys) { valid_primary_key_values.reverse }
|
659
|
+
|
660
|
+
it 'should return a passing result' do
|
661
|
+
expect(
|
662
|
+
command.call(primary_keys: primary_keys, envelope: true)
|
663
|
+
)
|
664
|
+
.to be_a_passing_result
|
665
|
+
.with_value({ collection_name => expected_data })
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
describe 'with scope: query' do
|
672
|
+
let(:scope_filter) { -> { {} } }
|
673
|
+
|
674
|
+
describe 'with an array of invalid primary keys' do
|
675
|
+
let(:primary_keys) { invalid_primary_key_values }
|
676
|
+
let(:expected_error) do
|
677
|
+
Cuprum::Errors::MultipleErrors.new(
|
678
|
+
errors: primary_keys.map do |primary_key|
|
679
|
+
Cuprum::Collections::Errors::NotFound.new(
|
680
|
+
attribute_name: primary_key_name,
|
681
|
+
attribute_value: primary_key,
|
682
|
+
collection_name: command.collection_name,
|
683
|
+
primary_key: true
|
684
|
+
)
|
685
|
+
end
|
686
|
+
)
|
687
|
+
end
|
688
|
+
|
689
|
+
it 'should return a failing result' do
|
690
|
+
expect(command.call(primary_keys: primary_keys, scope: scope))
|
691
|
+
.to be_a_failing_result
|
692
|
+
.with_error(expected_error)
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
describe 'with a scope that does not match any keys' do
|
697
|
+
let(:scope_filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
698
|
+
|
699
|
+
describe 'with a valid array of primary keys' do
|
700
|
+
let(:primary_keys) { valid_primary_key_values }
|
701
|
+
let(:expected_error) do
|
702
|
+
Cuprum::Errors::MultipleErrors.new(
|
703
|
+
errors: primary_keys.map do |primary_key|
|
704
|
+
Cuprum::Collections::Errors::NotFound.new(
|
705
|
+
attribute_name: primary_key_name,
|
706
|
+
attribute_value: primary_key,
|
707
|
+
collection_name: command.collection_name,
|
708
|
+
primary_key: true
|
709
|
+
)
|
710
|
+
end
|
711
|
+
)
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'should return a failing result' do
|
715
|
+
expect(
|
716
|
+
command.call(primary_keys: primary_keys, scope: scope)
|
717
|
+
)
|
718
|
+
.to be_a_failing_result
|
719
|
+
.with_error(expected_error)
|
720
|
+
end
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
describe 'with a scope that matches some keys' do
|
725
|
+
let(:scope_filter) { -> { { series: nil } } }
|
726
|
+
let(:matching_data) do
|
727
|
+
super().map do |item|
|
728
|
+
next nil unless item['series'].nil?
|
729
|
+
|
730
|
+
item
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
describe 'with a valid array of primary keys' do
|
735
|
+
let(:primary_keys) { valid_primary_key_values }
|
736
|
+
let(:expected_error) do
|
737
|
+
found_keys =
|
738
|
+
matching_data
|
739
|
+
.compact
|
740
|
+
.map { |item| item[primary_key_name.to_s] }
|
741
|
+
|
742
|
+
Cuprum::Errors::MultipleErrors.new(
|
743
|
+
errors: primary_keys.map do |primary_key|
|
744
|
+
next if found_keys.include?(primary_key)
|
745
|
+
|
746
|
+
Cuprum::Collections::Errors::NotFound.new(
|
747
|
+
attribute_name: primary_key_name,
|
748
|
+
attribute_value: primary_key,
|
749
|
+
collection_name: command.collection_name,
|
750
|
+
primary_key: true
|
751
|
+
)
|
752
|
+
end
|
753
|
+
)
|
754
|
+
end
|
755
|
+
|
756
|
+
it 'should return a failing result' do
|
757
|
+
expect(
|
758
|
+
command.call(primary_keys: primary_keys, scope: scope)
|
759
|
+
)
|
760
|
+
.to be_a_failing_result
|
761
|
+
.with_error(expected_error)
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
describe 'with allow_partial: true' do
|
766
|
+
describe 'with a valid array of primary keys' do
|
767
|
+
let(:primary_keys) { valid_primary_key_values }
|
768
|
+
let(:expected_error) do
|
769
|
+
found_keys =
|
770
|
+
matching_data
|
771
|
+
.compact
|
772
|
+
.map { |item| item[primary_key_name.to_s] }
|
773
|
+
|
774
|
+
Cuprum::Errors::MultipleErrors.new(
|
775
|
+
errors: primary_keys.map do |primary_key|
|
776
|
+
next if found_keys.include?(primary_key)
|
777
|
+
|
778
|
+
Cuprum::Collections::Errors::NotFound.new(
|
779
|
+
attribute_name: primary_key_name,
|
780
|
+
attribute_value: primary_key,
|
781
|
+
collection_name: command.collection_name,
|
782
|
+
primary_key: true
|
783
|
+
)
|
784
|
+
end
|
785
|
+
)
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'should return a passing result' do
|
789
|
+
expect(
|
790
|
+
command.call(
|
791
|
+
allow_partial: true,
|
792
|
+
primary_keys: primary_keys,
|
793
|
+
scope: scope
|
794
|
+
)
|
795
|
+
)
|
796
|
+
.to be_a_passing_result
|
797
|
+
.with_value(expected_data)
|
798
|
+
.and_error(expected_error)
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
describe 'with a scope that matches all keys' do
|
805
|
+
let(:scope_filter) { -> { { author: 'J.R.R. Tolkien' } } }
|
806
|
+
|
807
|
+
describe 'with a valid array of primary keys' do
|
808
|
+
let(:primary_keys) { valid_primary_key_values }
|
809
|
+
|
810
|
+
it 'should return a passing result' do
|
811
|
+
expect(command.call(primary_keys: primary_keys))
|
812
|
+
.to be_a_passing_result
|
813
|
+
.with_value(expected_data)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
# Contract validating the behavior of a FindMatching command implementation.
|
824
|
+
module ShouldBeAFindMatchingCommandContract
|
825
|
+
extend RSpec::SleepingKingStudios::Contract
|
826
|
+
|
827
|
+
# @!method apply(example_group)
|
828
|
+
# Adds the contract to the example group.
|
829
|
+
#
|
830
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
831
|
+
# which the contract is applied.
|
832
|
+
contract do
|
833
|
+
include Stannum::RSpec::Matchers
|
834
|
+
include Cuprum::Collections::RSpec::Contracts::QueryContracts
|
835
|
+
|
836
|
+
describe '#call' do
|
837
|
+
include_contract 'with query contexts'
|
838
|
+
|
839
|
+
shared_examples 'should return the matching items' do
|
840
|
+
it { expect(result).to be_a_passing_result }
|
841
|
+
|
842
|
+
it { expect(result.value).to be_a Enumerator }
|
843
|
+
|
844
|
+
it { expect(result.value.to_a).to be == expected_data }
|
845
|
+
end
|
846
|
+
|
847
|
+
shared_examples 'should return the wrapped items' do
|
848
|
+
it { expect(result).to be_a_passing_result }
|
849
|
+
|
850
|
+
it { expect(result.value).to be_a Hash }
|
851
|
+
|
852
|
+
it { expect(result.value.keys).to be == [collection_name] }
|
853
|
+
|
854
|
+
it { expect(result.value[collection_name]).to be == expected_data }
|
855
|
+
end
|
856
|
+
|
857
|
+
let(:options) do
|
858
|
+
opts = {}
|
859
|
+
|
860
|
+
opts[:limit] = limit if limit
|
861
|
+
opts[:offset] = offset if offset
|
862
|
+
opts[:order] = order if order
|
863
|
+
opts[:where] = filter unless filter.nil? || filter.is_a?(Proc)
|
864
|
+
|
865
|
+
opts
|
866
|
+
end
|
867
|
+
let(:block) { filter.is_a?(Proc) ? filter : nil }
|
868
|
+
let(:result) { command.call(**options, &block) }
|
869
|
+
let(:data) { [] }
|
870
|
+
let(:matching_data) { data }
|
871
|
+
let(:expected_data) do
|
872
|
+
defined?(super()) ? super() : matching_data
|
873
|
+
end
|
874
|
+
|
875
|
+
it 'should validate the :envelope keyword' do
|
876
|
+
expect(command)
|
877
|
+
.to validate_parameter(:call, :envelope)
|
878
|
+
.using_constraint(Stannum::Constraints::Boolean.new)
|
879
|
+
end
|
880
|
+
|
881
|
+
it 'should validate the :limit keyword' do
|
882
|
+
expect(command)
|
883
|
+
.to validate_parameter(:call, :limit)
|
884
|
+
.with_value(Object.new)
|
885
|
+
.using_constraint(Integer, required: false)
|
886
|
+
end
|
887
|
+
|
888
|
+
it 'should validate the :offset keyword' do
|
889
|
+
expect(command)
|
890
|
+
.to validate_parameter(:call, :offset)
|
891
|
+
.with_value(Object.new)
|
892
|
+
.using_constraint(Integer, required: false)
|
893
|
+
end
|
894
|
+
|
895
|
+
it 'should validate the :order keyword' do
|
896
|
+
constraint = Cuprum::Collections::Constraints::Ordering.new
|
897
|
+
|
898
|
+
expect(command)
|
899
|
+
.to validate_parameter(:call, :order)
|
900
|
+
.with_value(Object.new)
|
901
|
+
.using_constraint(constraint, required: false)
|
902
|
+
end
|
903
|
+
|
904
|
+
it 'should validate the :scope keyword' do
|
905
|
+
expect(command)
|
906
|
+
.to validate_parameter(:call, :scope)
|
907
|
+
.using_constraint(
|
908
|
+
Stannum::Constraints::Type.new(query.class, optional: true)
|
909
|
+
)
|
910
|
+
.with_value(Object.new.freeze)
|
911
|
+
end
|
912
|
+
|
913
|
+
it 'should validate the :where keyword' do
|
914
|
+
expect(command).to validate_parameter(:call, :where)
|
915
|
+
end
|
916
|
+
|
917
|
+
include_examples 'should return the matching items'
|
918
|
+
|
919
|
+
include_contract 'should perform queries',
|
920
|
+
block: lambda {
|
921
|
+
include_examples 'should return the matching items'
|
922
|
+
}
|
923
|
+
|
924
|
+
describe 'with an invalid filter block' do
|
925
|
+
let(:block) { -> {} }
|
926
|
+
let(:expected_error) do
|
927
|
+
an_instance_of(Cuprum::Collections::Errors::InvalidQuery)
|
928
|
+
end
|
929
|
+
|
930
|
+
it 'should return a failing result' do
|
931
|
+
expect(result).to be_a_failing_result.with_error(expected_error)
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
describe 'with envelope: true' do
|
936
|
+
let(:options) { super().merge(envelope: true) }
|
937
|
+
|
938
|
+
include_examples 'should return the wrapped items'
|
939
|
+
|
940
|
+
include_contract 'should perform queries',
|
941
|
+
block: lambda {
|
942
|
+
include_examples 'should return the wrapped items'
|
943
|
+
}
|
944
|
+
end
|
945
|
+
|
946
|
+
context 'when the collection has many items' do
|
947
|
+
let(:data) { fixtures_data }
|
948
|
+
|
949
|
+
include_examples 'should return the matching items'
|
950
|
+
|
951
|
+
include_contract 'should perform queries',
|
952
|
+
block: lambda {
|
953
|
+
include_examples 'should return the matching items'
|
954
|
+
}
|
955
|
+
|
956
|
+
describe 'with envelope: true' do
|
957
|
+
let(:options) { super().merge(envelope: true) }
|
958
|
+
|
959
|
+
include_examples 'should return the wrapped items'
|
960
|
+
|
961
|
+
include_contract 'should perform queries',
|
962
|
+
block: lambda {
|
963
|
+
include_examples 'should return the wrapped items'
|
964
|
+
}
|
965
|
+
end
|
966
|
+
|
967
|
+
describe 'with scope: query' do
|
968
|
+
let(:scope_filter) { -> { {} } }
|
969
|
+
let(:options) { super().merge(scope: scope) }
|
970
|
+
|
971
|
+
describe 'with a scope that does not match any values' do
|
972
|
+
let(:scope_filter) { -> { { series: 'Mistborn' } } }
|
973
|
+
let(:matching_data) { [] }
|
974
|
+
|
975
|
+
include_examples 'should return the matching items'
|
976
|
+
end
|
977
|
+
|
978
|
+
describe 'with a scope that matches some values' do
|
979
|
+
let(:scope_filter) { -> { { series: nil } } }
|
980
|
+
let(:matching_data) do
|
981
|
+
super().select { |item| item['series'].nil? }
|
982
|
+
end
|
983
|
+
|
984
|
+
include_examples 'should return the matching items'
|
985
|
+
|
986
|
+
describe 'with a where filter' do
|
987
|
+
let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
988
|
+
let(:options) { super().merge(where: filter) }
|
989
|
+
let(:matching_data) do
|
990
|
+
super()
|
991
|
+
.select { |item| item['author'] == 'Ursula K. LeGuin' }
|
992
|
+
end
|
993
|
+
|
994
|
+
include_examples 'should return the matching items'
|
995
|
+
end
|
996
|
+
end
|
997
|
+
|
998
|
+
describe 'with a scope that matches all values' do
|
999
|
+
let(:scope_filter) { -> { { id: not_equal(nil) } } }
|
1000
|
+
|
1001
|
+
include_examples 'should return the matching items'
|
1002
|
+
|
1003
|
+
describe 'with a where filter' do
|
1004
|
+
let(:filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
1005
|
+
let(:options) { super().merge(where: filter) }
|
1006
|
+
let(:matching_data) do
|
1007
|
+
super()
|
1008
|
+
.select { |item| item['author'] == 'Ursula K. LeGuin' }
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
include_examples 'should return the matching items'
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
# Contract validating the behavior of a FindOne command implementation.
|
1021
|
+
module ShouldBeAFindOneCommandContract
|
1022
|
+
extend RSpec::SleepingKingStudios::Contract
|
1023
|
+
|
1024
|
+
# @!method apply(example_group)
|
1025
|
+
# Adds the contract to the example group.
|
1026
|
+
#
|
1027
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
1028
|
+
# which the contract is applied.
|
1029
|
+
contract do
|
1030
|
+
describe '#call' do
|
1031
|
+
let(:mapped_data) do
|
1032
|
+
defined?(super()) ? super() : data
|
1033
|
+
end
|
1034
|
+
let(:primary_key_name) { defined?(super()) ? super() : 'id' }
|
1035
|
+
let(:primary_key_type) { defined?(super()) ? super() : Integer }
|
1036
|
+
let(:invalid_primary_key_value) do
|
1037
|
+
defined?(super()) ? super() : 100
|
1038
|
+
end
|
1039
|
+
let(:valid_primary_key_value) do
|
1040
|
+
defined?(super()) ? super() : 0
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def tools
|
1044
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it 'should validate the :envelope keyword' do
|
1048
|
+
expect(command)
|
1049
|
+
.to validate_parameter(:call, :envelope)
|
1050
|
+
.using_constraint(Stannum::Constraints::Boolean.new)
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
it 'should validate the :primary_key keyword' do
|
1054
|
+
expect(command)
|
1055
|
+
.to validate_parameter(:call, :primary_key)
|
1056
|
+
.using_constraint(primary_key_type)
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
it 'should validate the :scope keyword' do
|
1060
|
+
expect(command)
|
1061
|
+
.to validate_parameter(:call, :scope)
|
1062
|
+
.using_constraint(
|
1063
|
+
Stannum::Constraints::Type.new(query.class, optional: true)
|
1064
|
+
)
|
1065
|
+
.with_value(Object.new.freeze)
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
describe 'with an invalid primary key' do
|
1069
|
+
let(:primary_key) { invalid_primary_key_value }
|
1070
|
+
let(:expected_error) do
|
1071
|
+
Cuprum::Collections::Errors::NotFound.new(
|
1072
|
+
attribute_name: primary_key_name,
|
1073
|
+
attribute_value: primary_key,
|
1074
|
+
collection_name: command.collection_name,
|
1075
|
+
primary_key: true
|
1076
|
+
)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
it 'should return a failing result' do
|
1080
|
+
expect(command.call(primary_key: primary_key))
|
1081
|
+
.to be_a_failing_result
|
1082
|
+
.with_error(expected_error)
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
context 'when the collection has many items' do
|
1087
|
+
let(:data) { fixtures_data }
|
1088
|
+
let(:matching_data) do
|
1089
|
+
mapped_data
|
1090
|
+
.find { |item| item[primary_key_name.to_s] == primary_key }
|
1091
|
+
end
|
1092
|
+
let(:expected_data) do
|
1093
|
+
defined?(super()) ? super() : matching_data
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
describe 'with an invalid primary key' do
|
1097
|
+
let(:primary_key) { invalid_primary_key_value }
|
1098
|
+
let(:expected_error) do
|
1099
|
+
Cuprum::Collections::Errors::NotFound.new(
|
1100
|
+
attribute_name: primary_key_name,
|
1101
|
+
attribute_value: primary_key,
|
1102
|
+
collection_name: command.collection_name,
|
1103
|
+
primary_key: true
|
1104
|
+
)
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
it 'should return a failing result' do
|
1108
|
+
expect(command.call(primary_key: primary_key))
|
1109
|
+
.to be_a_failing_result
|
1110
|
+
.with_error(expected_error)
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
describe 'with a valid primary key' do
|
1115
|
+
let(:primary_key) { valid_primary_key_value }
|
1116
|
+
|
1117
|
+
it 'should return a passing result' do
|
1118
|
+
expect(command.call(primary_key: primary_key))
|
1119
|
+
.to be_a_passing_result
|
1120
|
+
.with_value(expected_data)
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
describe 'with envelope: true' do
|
1125
|
+
let(:member_name) { tools.str.singularize(collection_name) }
|
1126
|
+
|
1127
|
+
describe 'with a valid primary key' do
|
1128
|
+
let(:primary_key) { valid_primary_key_value }
|
1129
|
+
|
1130
|
+
it 'should return a passing result' do
|
1131
|
+
expect(command.call(primary_key: primary_key, envelope: true))
|
1132
|
+
.to be_a_passing_result
|
1133
|
+
.with_value({ member_name => expected_data })
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
describe 'with scope: query' do
|
1139
|
+
let(:scope_filter) { -> { {} } }
|
1140
|
+
|
1141
|
+
describe 'with a scope that does not match the key' do
|
1142
|
+
let(:scope_filter) { -> { { author: 'Ursula K. LeGuin' } } }
|
1143
|
+
|
1144
|
+
describe 'with an valid primary key' do
|
1145
|
+
let(:primary_key) { valid_primary_key_value }
|
1146
|
+
let(:expected_error) do
|
1147
|
+
Cuprum::Collections::Errors::NotFound.new(
|
1148
|
+
attribute_name: primary_key_name,
|
1149
|
+
attribute_value: primary_key,
|
1150
|
+
collection_name: command.collection_name,
|
1151
|
+
primary_key: true
|
1152
|
+
)
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
it 'should return a failing result' do
|
1156
|
+
expect(command.call(primary_key: primary_key, scope: scope))
|
1157
|
+
.to be_a_failing_result
|
1158
|
+
.with_error(expected_error)
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
describe 'with a scope that matches the key' do
|
1164
|
+
let(:scope_filter) { -> { { author: 'J.R.R. Tolkien' } } }
|
1165
|
+
|
1166
|
+
describe 'with a valid primary key' do
|
1167
|
+
let(:primary_key) { valid_primary_key_value }
|
1168
|
+
|
1169
|
+
it 'should return a passing result' do
|
1170
|
+
expect(command.call(primary_key: primary_key))
|
1171
|
+
.to be_a_passing_result
|
1172
|
+
.with_value(expected_data)
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
# Contract validating the behavior of an InsertOne command implementation.
|
1183
|
+
module ShouldBeAnInsertOneCommandContract
|
1184
|
+
extend RSpec::SleepingKingStudios::Contract
|
1185
|
+
|
1186
|
+
# @!method apply(example_group)
|
1187
|
+
# Adds the contract to the example group.
|
1188
|
+
#
|
1189
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
1190
|
+
# which the contract is applied.
|
1191
|
+
contract do
|
1192
|
+
describe '#call' do
|
1193
|
+
let(:matching_data) { attributes }
|
1194
|
+
let(:expected_data) do
|
1195
|
+
defined?(super()) ? super() : matching_data
|
1196
|
+
end
|
1197
|
+
let(:primary_key_name) do
|
1198
|
+
defined?(super()) ? super() : 'id'
|
1199
|
+
end
|
1200
|
+
let(:primary_key_type) do
|
1201
|
+
defined?(super()) ? super() : Integer
|
1202
|
+
end
|
1203
|
+
let(:scoped) do
|
1204
|
+
key = primary_key_name
|
1205
|
+
value = entity[primary_key_name.to_s]
|
1206
|
+
|
1207
|
+
query.where { { key => value } }
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
it 'should validate the :entity keyword' do
|
1211
|
+
expect(command)
|
1212
|
+
.to validate_parameter(:call, :entity)
|
1213
|
+
.using_constraint(entity_type)
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
context 'when the item does not exist in the collection' do
|
1217
|
+
it 'should return a passing result' do
|
1218
|
+
expect(command.call(entity: entity))
|
1219
|
+
.to be_a_passing_result
|
1220
|
+
.with_value(be == expected_data)
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
it 'should append an item to the collection' do
|
1224
|
+
expect { command.call(entity: entity) }
|
1225
|
+
.to(
|
1226
|
+
change { query.reset.count }
|
1227
|
+
.by(1)
|
1228
|
+
)
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
it 'should add the entity to the collection' do
|
1232
|
+
expect { command.call(entity: entity) }
|
1233
|
+
.to change(scoped, :exists?)
|
1234
|
+
.to be true
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
it 'should set the attributes' do
|
1238
|
+
command.call(entity: entity)
|
1239
|
+
|
1240
|
+
expect(scoped.to_a.first).to be == expected_data
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
|
1244
|
+
context 'when the item exists in the collection' do
|
1245
|
+
let(:data) { fixtures_data }
|
1246
|
+
let(:expected_error) do
|
1247
|
+
Cuprum::Collections::Errors::AlreadyExists.new(
|
1248
|
+
attribute_name: primary_key_name,
|
1249
|
+
attribute_value: attributes.fetch(
|
1250
|
+
primary_key_name.to_s,
|
1251
|
+
attributes[primary_key_name.intern]
|
1252
|
+
),
|
1253
|
+
collection_name: collection_name,
|
1254
|
+
primary_key: true
|
1255
|
+
)
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
it 'should return a failing result' do
|
1259
|
+
expect(command.call(entity: entity))
|
1260
|
+
.to be_a_failing_result
|
1261
|
+
.with_error(expected_error)
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
it 'should not append an item to the collection' do
|
1265
|
+
expect { command.call(entity: entity) }
|
1266
|
+
.not_to(change { query.reset.count })
|
1267
|
+
end
|
1268
|
+
end
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
# Contract validating the behavior of an UpdateOne command implementation.
|
1274
|
+
module ShouldBeAnUpdateOneCommandContract
|
1275
|
+
extend RSpec::SleepingKingStudios::Contract
|
1276
|
+
|
1277
|
+
# @!method apply(example_group)
|
1278
|
+
# Adds the contract to the example group.
|
1279
|
+
#
|
1280
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
1281
|
+
# which the contract is applied.
|
1282
|
+
contract do
|
1283
|
+
describe '#call' do
|
1284
|
+
let(:mapped_data) do
|
1285
|
+
defined?(super()) ? super() : data
|
1286
|
+
end
|
1287
|
+
let(:matching_data) { attributes }
|
1288
|
+
let(:expected_data) do
|
1289
|
+
defined?(super()) ? super() : matching_data
|
1290
|
+
end
|
1291
|
+
let(:primary_key_name) do
|
1292
|
+
defined?(super()) ? super() : 'id'
|
1293
|
+
end
|
1294
|
+
let(:scoped) do
|
1295
|
+
key = primary_key_name
|
1296
|
+
value = entity[primary_key_name.to_s]
|
1297
|
+
|
1298
|
+
query.where { { key => value } }
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
it 'should validate the :entity keyword' do
|
1302
|
+
expect(command)
|
1303
|
+
.to validate_parameter(:call, :entity)
|
1304
|
+
.using_constraint(entity_type)
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
context 'when the item does not exist in the collection' do
|
1308
|
+
let(:expected_error) do
|
1309
|
+
Cuprum::Collections::Errors::NotFound.new(
|
1310
|
+
attribute_name: primary_key_name,
|
1311
|
+
attribute_value: attributes.fetch(
|
1312
|
+
primary_key_name.to_s,
|
1313
|
+
attributes[primary_key_name.intern]
|
1314
|
+
),
|
1315
|
+
collection_name: collection_name,
|
1316
|
+
primary_key: true
|
1317
|
+
)
|
1318
|
+
end
|
1319
|
+
let(:matching_data) { mapped_data.first }
|
1320
|
+
|
1321
|
+
it 'should return a failing result' do
|
1322
|
+
expect(command.call(entity: entity))
|
1323
|
+
.to be_a_failing_result
|
1324
|
+
.with_error(expected_error)
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
it 'should not append an item to the collection' do
|
1328
|
+
expect { command.call(entity: entity) }
|
1329
|
+
.not_to(change { query.reset.count })
|
1330
|
+
end
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
context 'when the item exists in the collection' do
|
1334
|
+
let(:data) { fixtures_data }
|
1335
|
+
let(:matching_data) do
|
1336
|
+
mapped_data.first.merge(super())
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
it 'should return a passing result' do
|
1340
|
+
expect(command.call(entity: entity))
|
1341
|
+
.to be_a_passing_result
|
1342
|
+
.with_value(be == expected_data)
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
it 'should not append an item to the collection' do
|
1346
|
+
expect { command.call(entity: entity) }
|
1347
|
+
.not_to(change { query.reset.count })
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
it 'should set the attributes' do
|
1351
|
+
command.call(entity: entity)
|
1352
|
+
|
1353
|
+
expect(scoped.to_a.first).to be == expected_data
|
1354
|
+
end
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
# Contract validating the behavior of a ValidateOne command implementation.
|
1361
|
+
module ShouldBeAValidateOneCommandContract
|
1362
|
+
extend RSpec::SleepingKingStudios::Contract
|
1363
|
+
|
1364
|
+
# @!method apply(example_group, default_contract:)
|
1365
|
+
# Adds the contract to the example group.
|
1366
|
+
#
|
1367
|
+
# @param default_contract [Boolean] if true, the command defines a
|
1368
|
+
# default contract.
|
1369
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
1370
|
+
# which the contract is applied.
|
1371
|
+
contract do |default_contract:|
|
1372
|
+
describe '#call' do
|
1373
|
+
it 'should validate the :contract keyword' do
|
1374
|
+
expect(command)
|
1375
|
+
.to validate_parameter(:call, :contract)
|
1376
|
+
.with_value(Object.new.freeze)
|
1377
|
+
.using_constraint(Stannum::Constraints::Base, optional: true)
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
it 'should validate the :entity keyword' do
|
1381
|
+
expect(command)
|
1382
|
+
.to validate_parameter(:call, :entity)
|
1383
|
+
.with_value(Object.new.freeze)
|
1384
|
+
.using_constraint(entity_type)
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
describe 'with contract: nil' do
|
1388
|
+
if default_contract
|
1389
|
+
context 'when the entity does not match the default contract' do
|
1390
|
+
let(:attributes) { invalid_default_attributes }
|
1391
|
+
let(:expected_error) do
|
1392
|
+
Cuprum::Collections::Errors::FailedValidation.new(
|
1393
|
+
entity_class: entity.class,
|
1394
|
+
errors: expected_errors
|
1395
|
+
)
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
it 'should return a failing result' do
|
1399
|
+
expect(command.call(entity: entity))
|
1400
|
+
.to be_a_failing_result
|
1401
|
+
.with_error(expected_error)
|
1402
|
+
end
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
context 'when the entity matches the default contract' do
|
1406
|
+
let(:attributes) { valid_default_attributes }
|
1407
|
+
|
1408
|
+
it 'should return a passing result' do
|
1409
|
+
expect(command.call(entity: entity))
|
1410
|
+
.to be_a_passing_result
|
1411
|
+
.with_value(entity)
|
1412
|
+
end
|
1413
|
+
end
|
1414
|
+
else
|
1415
|
+
let(:attributes) { valid_attributes }
|
1416
|
+
let(:expected_error) do
|
1417
|
+
Cuprum::Collections::Errors::MissingDefaultContract.new(
|
1418
|
+
entity_class: entity.class
|
1419
|
+
)
|
1420
|
+
end
|
1421
|
+
|
1422
|
+
it 'should return a failing result' do
|
1423
|
+
expect(command.call(entity: entity))
|
1424
|
+
.to be_a_failing_result
|
1425
|
+
.with_error(expected_error)
|
1426
|
+
end
|
1427
|
+
end
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
describe 'with contract: value' do
|
1431
|
+
context 'when the entity does not match the contract' do
|
1432
|
+
let(:attributes) { invalid_attributes }
|
1433
|
+
let(:errors) { contract.errors_for(entity) }
|
1434
|
+
let(:expected_error) do
|
1435
|
+
Cuprum::Collections::Errors::FailedValidation.new(
|
1436
|
+
entity_class: entity.class,
|
1437
|
+
errors: errors
|
1438
|
+
)
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
it 'should return a failing result' do
|
1442
|
+
expect(command.call(contract: contract, entity: entity))
|
1443
|
+
.to be_a_failing_result
|
1444
|
+
.with_error(expected_error)
|
1445
|
+
end
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
context 'when the entity matches the contract' do
|
1449
|
+
let(:attributes) { valid_attributes }
|
1450
|
+
|
1451
|
+
it 'should return a passing result' do
|
1452
|
+
expect(command.call(contract: contract, entity: entity))
|
1453
|
+
.to be_a_passing_result
|
1454
|
+
.with_value(entity)
|
1455
|
+
end
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
end
|
1461
|
+
end
|
1462
|
+
end
|