lotus-dynamodb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,940 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Adapters::DynamodbAdapter do
4
+ before do
5
+ TestUser = Struct.new(:id, :name, :age) do
6
+ include Lotus::Entity
7
+ end
8
+
9
+ TestDevice = Struct.new(:id, :created_at) do
10
+ include Lotus::Entity
11
+ end
12
+
13
+ TestPurchase = Struct.new(:id, :region, :subtotal, :item_ids, :content, :created_at) do
14
+ include Lotus::Entity
15
+ end
16
+
17
+ coercer = Lotus::Model::Adapters::Dynamodb::Coercer
18
+ @mapper = Lotus::Model::Mapper.new(coercer) do
19
+ collection :test_users do
20
+ entity TestUser
21
+
22
+ attribute :id, String
23
+ attribute :name, String
24
+ attribute :age, Integer
25
+ end
26
+
27
+ collection :test_devices do
28
+ entity TestDevice
29
+
30
+ attribute :id, String, as: :uuid
31
+ attribute :created_at, Time
32
+
33
+ identity :uuid
34
+ end
35
+
36
+ collection :test_purchases do
37
+ entity TestPurchase
38
+
39
+ attribute :id, String, as: :uuid
40
+ attribute :region, String
41
+ attribute :subtotal, Float
42
+ attribute :item_ids, Set
43
+ attribute :content, AWS::DynamoDB::Binary
44
+ attribute :created_at, Time
45
+
46
+ identity :uuid
47
+ end
48
+ end.load!
49
+
50
+ @adapter = Lotus::Model::Adapters::DynamodbAdapter.new(@mapper)
51
+ @adapter.clear(collection)
52
+ end
53
+
54
+ after do
55
+ Object.send(:remove_const, :TestUser)
56
+ Object.send(:remove_const, :TestDevice)
57
+ Object.send(:remove_const, :TestPurchase)
58
+ end
59
+
60
+ let(:collection) { :test_users }
61
+
62
+ describe '#first' do
63
+ it 'raises an error' do
64
+ -> { @adapter.first(collection) }.must_raise NotImplementedError
65
+ end
66
+ end
67
+
68
+ describe '#last' do
69
+ it 'raises an error' do
70
+ -> { @adapter.last(collection) }.must_raise NotImplementedError
71
+ end
72
+ end
73
+
74
+ describe 'multiple collections' do
75
+ it 'create records' do
76
+ user = TestUser.new
77
+ device = TestDevice.new(created_at: Time.new)
78
+
79
+ @adapter.clear(:test_users)
80
+ @adapter.clear(:test_devices)
81
+
82
+ @adapter.create(:test_users, user)
83
+ @adapter.create(:test_devices, device)
84
+
85
+ @adapter.all(:test_users).must_equal [user]
86
+ @adapter.all(:test_devices).must_equal [device]
87
+ end
88
+ end
89
+
90
+ describe '#persist' do
91
+ describe 'when the given entity is not persisted' do
92
+ let(:entity) { TestUser.new }
93
+
94
+ it 'stores the record and assigns an id' do
95
+ @adapter.persist(collection, entity)
96
+
97
+ entity.id.wont_be_nil
98
+ @adapter.find(collection, entity.id).must_equal entity
99
+ end
100
+ end
101
+
102
+ describe 'when the given entity is persisted' do
103
+ before do
104
+ @adapter.create(collection, entity)
105
+ end
106
+
107
+ let(:entity) { TestUser.new }
108
+
109
+ it 'updates the record and leaves untouched the id' do
110
+ id = entity.id
111
+ id.wont_be_nil
112
+
113
+ entity.name = 'L'
114
+ @adapter.persist(collection, entity)
115
+
116
+ entity.id.must_equal(id)
117
+ @adapter.find(collection, entity.id).must_equal entity
118
+ end
119
+ end
120
+ end
121
+
122
+ describe '#create' do
123
+ let(:entity) { TestUser.new }
124
+
125
+ it 'stores the record and assigns an id' do
126
+ @adapter.create(collection, entity)
127
+
128
+ entity.id.wont_be_nil
129
+ @adapter.find(collection, entity.id).must_equal entity
130
+ end
131
+ end
132
+
133
+ describe '#update' do
134
+ before do
135
+ @adapter.create(collection, entity)
136
+ end
137
+
138
+ let(:entity) { TestUser.new(id: nil, name: 'L') }
139
+
140
+ it 'stores the changes and leave the id untouched' do
141
+ id = entity.id
142
+
143
+ entity.name = 'MG'
144
+ @adapter.update(collection, entity)
145
+
146
+ entity.id.must_equal id
147
+ @adapter.find(collection, entity.id).must_equal entity
148
+ end
149
+ end
150
+
151
+ describe '#delete' do
152
+ before do
153
+ @adapter.create(collection, entity)
154
+ end
155
+
156
+ let(:entity) { TestUser.new }
157
+
158
+ it 'removes the given identity' do
159
+ @adapter.delete(collection, entity)
160
+ @adapter.find(collection, entity.id).must_be_nil
161
+ end
162
+ end
163
+
164
+ describe '#all' do
165
+ describe 'when no records are persisted' do
166
+ it 'returns an empty collection' do
167
+ @adapter.all(collection).must_be_empty
168
+ end
169
+ end
170
+
171
+ describe 'when some records are persisted' do
172
+ before do
173
+ @adapter.create(collection, entity)
174
+ end
175
+
176
+ let(:entity) { TestUser.new }
177
+
178
+ it 'returns all of them' do
179
+ @adapter.all(collection).must_equal [entity]
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#find' do
185
+ before do
186
+ @adapter.create(collection, entity)
187
+ end
188
+
189
+ describe 'simple key' do
190
+ let(:entity) { TestUser.new }
191
+
192
+ it 'returns the record by id' do
193
+ @adapter.find(collection, entity.id).must_equal entity
194
+ end
195
+
196
+ it 'returns nil when the record cannot be found' do
197
+ @adapter.find(collection, 1_000_000.to_s).must_be_nil
198
+ end
199
+
200
+ it 'returns nil when the given id is nil' do
201
+ @adapter.find(collection, nil).must_be_nil
202
+ end
203
+
204
+ it 'returns nil when the given id is empty string' do
205
+ @adapter.find(collection, nil).must_be_nil
206
+ end
207
+ end
208
+
209
+ describe 'complex key' do
210
+ let(:entity) { TestDevice.new(created_at: Time.new) }
211
+ let(:collection) { :test_devices }
212
+
213
+ it 'returns the record by id' do
214
+ @adapter.find(collection, entity.id, entity.created_at).must_equal entity
215
+ end
216
+
217
+ it 'returns nil when not enough keys' do
218
+ @adapter.find(collection, entity.id).must_be_nil
219
+ end
220
+ end
221
+ end
222
+
223
+ describe '#clear' do
224
+ before do
225
+ @adapter.create(collection, entity)
226
+ end
227
+
228
+ let(:entity) { TestUser.new }
229
+
230
+ it 'removes all the records' do
231
+ @adapter.clear(collection)
232
+ @adapter.all(collection).must_be_empty
233
+ end
234
+ end
235
+
236
+ describe '#query' do
237
+ let(:collection) { :test_purchases }
238
+ let(:purchase1) do
239
+ TestPurchase.new(
240
+ region: 'europe',
241
+ subtotal: 15.0,
242
+ item_ids: [1, 2, 3],
243
+ content: "OMG",
244
+ created_at: Time.new,
245
+ )
246
+ end
247
+ let(:purchase2) do
248
+ TestPurchase.new(
249
+ region: 'europe',
250
+ subtotal: 10.0,
251
+ item_ids: ["2", "3", "4"],
252
+ content: AWS::DynamoDB::Binary.new("SO"),
253
+ created_at: Time.new,
254
+ )
255
+ end
256
+ let(:purchase3) do
257
+ TestPurchase.new(
258
+ region: 'usa',
259
+ subtotal: 5.0,
260
+ item_ids: [AWS::DynamoDB::Binary.new("WOW")],
261
+ content: AWS::DynamoDB::Binary.new("MUCH"),
262
+ created_at: Time.new,
263
+ )
264
+ end
265
+ let(:purchase4) do
266
+ TestPurchase.new(
267
+ region: 'asia',
268
+ subtotal: 100.0,
269
+ item_ids: [4, 5, 6],
270
+ content: AWS::DynamoDB::Binary.new("CONTENT"),
271
+ created_at: Time.new,
272
+ )
273
+ end
274
+ let(:purchase5) do
275
+ TestPurchase.new(
276
+ region: 'europe',
277
+ subtotal: 1.0,
278
+ created_at: Time.new,
279
+ )
280
+ end
281
+ let(:purchases) { [purchase1, purchase2, purchase3, purchase4] }
282
+
283
+ describe 'types' do
284
+ before do
285
+ purchases.each do |purchase|
286
+ @adapter.create(collection, purchase)
287
+ end
288
+
289
+ @purchases = @adapter.query(collection).all.sort_by(&:created_at)
290
+ end
291
+
292
+ it 'has string type' do
293
+ @purchases.first.region.class.must_equal String
294
+ end
295
+
296
+ it 'has number type' do
297
+ @purchases.first.subtotal.class.must_equal Float
298
+ end
299
+
300
+ it 'has set type' do
301
+ @purchases.first.item_ids.class.must_equal Set
302
+ @purchases.at(0).item_ids.map(&:class).must_equal [BigDecimal, BigDecimal, BigDecimal]
303
+ @purchases.at(1).item_ids.map(&:class).must_equal [String, String, String]
304
+ @purchases.at(2).item_ids.map(&:class).must_equal [AWS::DynamoDB::Binary]
305
+ end
306
+
307
+ it 'has binary type' do
308
+ @purchases.each do |purchase|
309
+ purchase.content.class.must_equal AWS::DynamoDB::Binary
310
+ end
311
+ end
312
+ end
313
+
314
+ describe 'where' do
315
+ describe 'with an empty collection' do
316
+ it 'returns an empty result set' do
317
+ result = @adapter.query(collection) do
318
+ where(region: 'europe')
319
+ end.all
320
+
321
+ result.must_be_empty
322
+ end
323
+ end
324
+
325
+ describe 'with a filled collection' do
326
+ before do
327
+ purchases.each do |purchase|
328
+ @adapter.create(collection, purchase)
329
+ end
330
+ end
331
+
332
+ describe 'without key schema' do
333
+ it 'returns selected records' do
334
+ query = Proc.new {
335
+ where(subtotal: 100.0)
336
+ }
337
+
338
+ result = @adapter.query(collection, &query).all
339
+ result.must_equal [purchase4]
340
+ end
341
+ end
342
+
343
+ describe 'with key schema' do
344
+ it 'returns selected records' do
345
+ query = Proc.new {
346
+ where(region: 'europe')
347
+ }
348
+
349
+ result = @adapter.query(collection, &query).all
350
+ result.must_equal [purchase1, purchase2]
351
+ end
352
+
353
+ it 'can use multiple where conditions' do
354
+ created_at = purchase1.created_at
355
+ query = Proc.new {
356
+ where(region: 'europe').where(created_at: created_at)
357
+ }
358
+
359
+ result = @adapter.query(collection, &query).all
360
+ result.must_equal [purchase1]
361
+ end
362
+
363
+ it 'can use array as where condition' do
364
+ query = Proc.new {
365
+ where(region: 'europe').where(subtotal: [15.0, 10.0])
366
+ }
367
+
368
+ result = @adapter.query(collection, &query).all
369
+ result.must_equal [purchase1, purchase2]
370
+ end
371
+
372
+ it 'can use range as where condition' do
373
+ query = Proc.new {
374
+ where(region: 'europe').where(subtotal: 8..14)
375
+ }
376
+
377
+ result = @adapter.query(collection, &query).all
378
+ result.must_equal [purchase2]
379
+ end
380
+
381
+ it 'can use "eq" alias' do
382
+ query = Proc.new {
383
+ eq(region: 'europe')
384
+ }
385
+
386
+ @adapter.query(collection, &query).count.must_equal 2
387
+ end
388
+
389
+ it 'can use "in" alias' do
390
+ @adapter.create(collection, purchase5)
391
+
392
+ query = Proc.new {
393
+ where(region: 'europe').in(subtotal: [10.0, 1.0])
394
+ }
395
+
396
+ @adapter.query(collection, &query).count.must_equal 2
397
+ end
398
+
399
+ it 'can use "between" alias' do
400
+ @adapter.create(collection, purchase5)
401
+
402
+ query = Proc.new {
403
+ where(region: 'europe').between(subtotal: 0.0..11.0)
404
+ }
405
+
406
+ @adapter.query(collection, &query).count.must_equal 2
407
+ end
408
+ end
409
+ end
410
+ end
411
+
412
+ describe 'exclude' do
413
+ describe 'with an empty collection' do
414
+ it 'returns an empty result set' do
415
+ result = @adapter.query(collection) do
416
+ exclude(subtotal: 10.0)
417
+ end.all
418
+
419
+ result.must_be_empty
420
+ end
421
+ end
422
+
423
+ describe 'with a filled collection' do
424
+ before do
425
+ purchases.each do |purchase|
426
+ @adapter.create(collection, purchase)
427
+ end
428
+ end
429
+
430
+ describe 'without key schema' do
431
+ it 'returns selected records' do
432
+ query = Proc.new {
433
+ exclude(subtotal: 100.0)
434
+ }
435
+
436
+ result = @adapter.query(collection, &query).all
437
+ result.must_include purchase1
438
+ result.must_include purchase2
439
+ result.must_include purchase3
440
+ end
441
+ end
442
+
443
+ describe 'with key schema' do
444
+ it 'returns selected records' do
445
+ query = Proc.new {
446
+ where(region: 'europe').exclude(subtotal: 15.0)
447
+ }
448
+
449
+ result = @adapter.query(collection, &query).all
450
+ result.must_equal [purchase2]
451
+ end
452
+
453
+ it 'can use multiple exclude conditions' do
454
+ id = purchase2.id
455
+
456
+ query = Proc.new {
457
+ where(region: 'europe').exclude(subtotal: 15.0).exclude(uuid: id)
458
+ }
459
+
460
+ result = @adapter.query(collection, &query).all
461
+ result.must_equal []
462
+ end
463
+
464
+ it 'can use multiple exclude conditions with "not" alias' do
465
+ id = purchase2.id
466
+
467
+ query = Proc.new {
468
+ where(region: 'europe').not(subtotal: 15.0).not(uuid: id)
469
+ }
470
+
471
+ result = @adapter.query(collection, &query).all
472
+ result.must_equal []
473
+ end
474
+
475
+ it 'can not use array as exclude condition' do
476
+ query = Proc.new {
477
+ exclude(subtotal: [15.0, 10.0])
478
+ }
479
+
480
+ ->{ @adapter.query(collection, &query).all }.must_raise \
481
+ NotImplementedError
482
+ end
483
+
484
+ it 'can use "ne" alias' do
485
+ query = Proc.new {
486
+ where(region: 'europe').ne(subtotal: 1.0)
487
+ }
488
+
489
+ @adapter.query(collection, &query).count.must_equal 2
490
+ end
491
+ end
492
+ end
493
+ end
494
+
495
+ describe 'comparison' do
496
+ describe 'with an empty collection' do
497
+ it 'returns an empty result set' do
498
+ result = @adapter.query(collection) do
499
+ le(subtotal: 10.0)
500
+ end.all
501
+
502
+ result.must_be_empty
503
+ end
504
+ end
505
+
506
+ describe 'with a filled collection' do
507
+ before do
508
+ purchases.each do |purchase|
509
+ @adapter.create(collection, purchase)
510
+ end
511
+ end
512
+
513
+ it 'can use "le" method' do
514
+ query = Proc.new {
515
+ where(region: 'europe').le(subtotal: 15.0)
516
+ }
517
+
518
+ @adapter.query(collection, &query).count.must_equal 2
519
+ end
520
+
521
+ it 'can use "lt" method' do
522
+ query = Proc.new {
523
+ where(region: 'europe').lt(subtotal: 15.0)
524
+ }
525
+
526
+ @adapter.query(collection, &query).count.must_equal 1
527
+ end
528
+
529
+ it 'can use "ge" method' do
530
+ query = Proc.new {
531
+ where(region: 'europe').ge(subtotal: 10.0)
532
+ }
533
+
534
+ @adapter.query(collection, &query).count.must_equal 2
535
+ end
536
+
537
+ it 'can use "gt" method' do
538
+ query = Proc.new {
539
+ where(region: 'europe').gt(subtotal: 10.0)
540
+ }
541
+
542
+ @adapter.query(collection, &query).count.must_equal 1
543
+ end
544
+
545
+ it 'can use "contains" method' do
546
+ query = Proc.new {
547
+ where(region: 'europe').contains(item_ids: 2)
548
+ }
549
+
550
+ @adapter.query(collection, &query).count.must_equal 1
551
+ end
552
+
553
+ it 'can use "not_contains" method' do
554
+ skip_for_fake_dynamo
555
+ query = Proc.new {
556
+ where(region: 'europe').not_contains(item_ids: '2')
557
+ }
558
+
559
+ @adapter.query(collection, &query).count.must_equal 1
560
+ end
561
+
562
+ it 'can use "begins_with" method' do
563
+ query = Proc.new {
564
+ where(region: 'asia').begins_with(content: "CON")
565
+ }
566
+
567
+ @adapter.query(collection, &query).count.must_equal 1
568
+ end
569
+
570
+ it 'can use "null" method' do
571
+ skip_for_fake_dynamo
572
+ @adapter.create(collection, purchase5)
573
+
574
+ query = Proc.new {
575
+ where(region: 'europe').null(:content)
576
+ }
577
+
578
+ @adapter.query(collection, &query).count.must_equal 1
579
+ end
580
+
581
+ it 'can use "not_null" method' do
582
+ skip_for_fake_dynamo
583
+ @adapter.create(collection, purchase5)
584
+
585
+ query = Proc.new {
586
+ where(region: 'europe').not_null(:content)
587
+ }
588
+
589
+ @adapter.query(collection, &query).count.must_equal 2
590
+ end
591
+ end
592
+ end
593
+
594
+ describe 'or' do
595
+ describe 'with an empty collection' do
596
+ it 'returns an empty result set' do
597
+ result = @adapter.query(collection) do
598
+ where(subtotal: 10.0).or.where(uuid: "omg")
599
+ end.all
600
+
601
+ result.must_be_empty
602
+ end
603
+ end
604
+
605
+ describe 'with a filled collection' do
606
+ before do
607
+ purchases.each do |purchase|
608
+ @adapter.create(collection, purchase)
609
+ end
610
+ end
611
+
612
+ it 'returns selected records' do
613
+ skip_for_fake_dynamo
614
+ id = purchase1.id
615
+
616
+ query = Proc.new {
617
+ where(region: 'europe').where(subtotal: 10.0).or.where(uuid: id)
618
+ }
619
+
620
+ result = @adapter.query(collection, &query).all
621
+ result.must_equal [purchase1, purchase2]
622
+ end
623
+ end
624
+ end
625
+
626
+ describe 'select' do
627
+ describe 'with an empty collection' do
628
+ it 'returns an empty result' do
629
+ result = @adapter.query(collection) do
630
+ select(:subtotal)
631
+ end.all
632
+
633
+ result.must_be_empty
634
+ end
635
+ end
636
+
637
+ describe 'with a filled collection' do
638
+ before do
639
+ purchases.each do |purchase|
640
+ @adapter.create(collection, purchase)
641
+ end
642
+ end
643
+
644
+ it 'returns the selected columns from all the records' do
645
+ query = Proc.new {
646
+ select(:subtotal)
647
+ }
648
+
649
+ result = @adapter.query(collection, &query).all
650
+ purchases.each do |purchase|
651
+ record = result.find { |r| r.subtotal == purchase.subtotal }
652
+ record.wont_be_nil
653
+ record.region.must_be_nil
654
+ end
655
+ end
656
+
657
+ it 'returns only the select of requested records' do
658
+ query = Proc.new {
659
+ where(region: 'europe').select(:subtotal)
660
+ }
661
+
662
+ result = @adapter.query(collection, &query).all
663
+
664
+ record = result.first
665
+ record.subtotal.must_equal(purchase1.subtotal)
666
+ record.region.must_be_nil
667
+ end
668
+
669
+ it 'returns only the multiple select of requested records' do
670
+ query = Proc.new {
671
+ where(region: 'europe').select(:subtotal, :region)
672
+ }
673
+
674
+ result = @adapter.query(collection, &query).all
675
+
676
+ record = result.first
677
+ record.subtotal.must_equal(purchase1.subtotal)
678
+ record.region.must_equal(purchase1.region)
679
+ record.id.must_be_nil
680
+ end
681
+ end
682
+ end
683
+
684
+ describe 'order' do
685
+ let(:collection) { :test_devices }
686
+ let(:device1) { TestDevice.new(id: 'device', created_at: Time.new) }
687
+ let(:device2) { TestDevice.new(id: 'device', created_at: Time.new) }
688
+ let(:devices) { [device1, device2] }
689
+
690
+ describe 'asc' do
691
+ describe 'with an empty collection' do
692
+ it 'returns an empty result set' do
693
+ result = @adapter.query(collection) do
694
+ where(uuid: 'device').asc
695
+ end.all
696
+
697
+ result.must_be_empty
698
+ end
699
+ end
700
+
701
+ describe 'with a filled collection' do
702
+ before do
703
+ devices.each do |device|
704
+ @adapter.create(collection, device)
705
+ end
706
+ end
707
+
708
+ it 'returns sorted records' do
709
+ query = Proc.new {
710
+ where(uuid: 'device').asc
711
+ }
712
+
713
+ result = @adapter.query(collection, &query).all
714
+ result.must_equal [device1, device2]
715
+ end
716
+ end
717
+ end
718
+
719
+ describe 'desc' do
720
+ describe 'with an empty collection' do
721
+ it 'returns an empty result set' do
722
+ result = @adapter.query(collection) do
723
+ where(uuid: 'device').desc
724
+ end.all
725
+
726
+ result.must_be_empty
727
+ end
728
+ end
729
+
730
+ describe 'with a filled collection' do
731
+ before do
732
+ devices.each do |device|
733
+ @adapter.create(collection, device)
734
+ end
735
+ end
736
+
737
+ it 'returns reverse sorted records' do
738
+ query = Proc.new {
739
+ where(uuid: 'device').desc
740
+ }
741
+
742
+ result = @adapter.query(collection, &query).all
743
+ result.must_equal [device1, device2]
744
+ end
745
+ end
746
+ end
747
+ end
748
+
749
+ describe 'limit' do
750
+ describe 'with an empty collection' do
751
+ it 'returns an empty result set' do
752
+ result = @adapter.query(collection) do
753
+ limit(1)
754
+ end.all
755
+
756
+ result.must_be_empty
757
+ end
758
+ end
759
+
760
+ describe 'with a filled collection' do
761
+ before do
762
+ purchases.each do |purchase|
763
+ @adapter.create(collection, purchase)
764
+ end
765
+ end
766
+
767
+ it 'returns only the number of requested records' do
768
+ query = Proc.new {
769
+ where(region: 'europe').limit(1)
770
+ }
771
+
772
+ result = @adapter.query(collection, &query).all
773
+ result.must_equal [purchase1]
774
+ end
775
+ end
776
+ end
777
+
778
+ describe 'exists?' do
779
+ describe 'with an empty collection' do
780
+ it 'returns false' do
781
+ result = @adapter.query(collection) do
782
+ where(region: 'wow')
783
+ end.exists?
784
+
785
+ result.must_equal false
786
+ end
787
+ end
788
+
789
+ describe 'with a filled collection' do
790
+ before do
791
+ purchases.each do |purchase|
792
+ @adapter.create(collection, purchase)
793
+ end
794
+ end
795
+
796
+ it 'returns true when there are matched records' do
797
+ query = Proc.new {
798
+ where(region: 'asia')
799
+ }
800
+
801
+ result = @adapter.query(collection, &query).exists?
802
+ result.must_equal true
803
+ end
804
+
805
+ it 'returns false when there are matched records' do
806
+ query = Proc.new {
807
+ where(region: 'wtf')
808
+ }
809
+
810
+ result = @adapter.query(collection, &query).exists?
811
+ result.must_equal false
812
+ end
813
+
814
+ it 'can use "exist?" alias' do
815
+ query = Proc.new {
816
+ where(region: 'usa')
817
+ }
818
+
819
+ result = @adapter.query(collection, &query).exist?
820
+ result.must_equal true
821
+ end
822
+ end
823
+ end
824
+
825
+ describe 'count' do
826
+ describe 'with an empty collection' do
827
+ it 'returns 0' do
828
+ result = @adapter.query(collection) do
829
+ all
830
+ end.count
831
+
832
+ result.must_equal 0
833
+ end
834
+ end
835
+
836
+ describe 'with a filled collection' do
837
+ before do
838
+ purchases.each do |purchase|
839
+ @adapter.create(collection, purchase)
840
+ end
841
+ end
842
+
843
+ it 'returns the count of all the records' do
844
+ query = Proc.new {
845
+ all
846
+ }
847
+
848
+ result = @adapter.query(collection, &query).count
849
+ result.must_equal 4
850
+ end
851
+
852
+ it 'returns the count from an empty query block' do
853
+ query = Proc.new {
854
+ }
855
+
856
+ result = @adapter.query(collection, &query).count
857
+ result.must_equal 4
858
+ end
859
+
860
+ it 'returns only the count of requested records' do
861
+ query = Proc.new {
862
+ where(region: 'europe')
863
+ }
864
+
865
+ result = @adapter.query(collection, &query).count
866
+ result.must_equal 2
867
+ end
868
+ end
869
+ end
870
+
871
+ describe 'consistent' do
872
+ before do
873
+ purchases.each do |purchase|
874
+ @adapter.create(collection, purchase)
875
+ end
876
+ end
877
+
878
+ it 'does not fail' do
879
+ query = Proc.new {
880
+ where(region: 'europe').consistent
881
+ }
882
+
883
+ result = @adapter.query(collection, &query).count
884
+ result.must_equal 2
885
+ end
886
+ end
887
+
888
+ describe 'index' do
889
+ describe 'with an empty collection' do
890
+ it 'returns an empty result for local index' do
891
+ result = @adapter.query(collection) do
892
+ index('by_subtotal').where(region: 'europe')
893
+ end.all
894
+
895
+ result.must_be_empty
896
+ end
897
+
898
+ it 'returns an empty result for global index' do
899
+ result = @adapter.query(collection) do
900
+ index('by_uuid').where(uuid: 'wow')
901
+ end.all
902
+
903
+ result.must_be_empty
904
+ end
905
+ end
906
+
907
+ describe 'with a filled collection' do
908
+ before do
909
+ purchases.each do |purchase|
910
+ @adapter.create(collection, purchase)
911
+ end
912
+ end
913
+
914
+ describe 'local index' do
915
+ it 'returns selected records' do
916
+ query = Proc.new {
917
+ index('by_subtotal').where(region: 'europe')
918
+ }
919
+
920
+ result = @adapter.query(collection, &query).all
921
+ result.must_equal [purchase2, purchase1]
922
+ end
923
+ end
924
+
925
+ describe 'global index' do
926
+ it 'returns selected records' do
927
+ id = purchase3.id
928
+
929
+ query = Proc.new {
930
+ index('by_uuid').where(uuid: id)
931
+ }
932
+
933
+ result = @adapter.query(collection, &query).all
934
+ result.must_equal [purchase3]
935
+ end
936
+ end
937
+ end
938
+ end
939
+ end
940
+ end