mongoid 9.1.0 → 9.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +69 -0
  3. data/lib/mongoid/association/accessors.rb +27 -5
  4. data/lib/mongoid/association/depending.rb +14 -10
  5. data/lib/mongoid/association/eager.rb +9 -0
  6. data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
  7. data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
  8. data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
  9. data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
  10. data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
  11. data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
  12. data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
  13. data/lib/mongoid/association/eager_loadable.rb +65 -101
  14. data/lib/mongoid/association/embedded/batchable.rb +17 -1
  15. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
  16. data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
  17. data/lib/mongoid/association/macros.rb +13 -1
  18. data/lib/mongoid/association/nested/many.rb +34 -5
  19. data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
  20. data/lib/mongoid/association/nested/one.rb +6 -1
  21. data/lib/mongoid/association/options.rb +17 -0
  22. data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
  23. data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
  24. data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
  25. data/lib/mongoid/association/referenced/has_and_belongs_to_many/buildable.rb +6 -0
  26. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
  27. data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
  28. data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
  29. data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
  30. data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
  31. data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
  32. data/lib/mongoid/association/referenced/has_one.rb +1 -0
  33. data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
  34. data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
  35. data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
  36. data/lib/mongoid/association/referenced.rb +2 -0
  37. data/lib/mongoid/association/relatable.rb +58 -0
  38. data/lib/mongoid/association.rb +15 -0
  39. data/lib/mongoid/atomic/modifiers.rb +100 -16
  40. data/lib/mongoid/clients/factory.rb +9 -13
  41. data/lib/mongoid/collection_configurable.rb +8 -0
  42. data/lib/mongoid/config/defaults.rb +0 -1
  43. data/lib/mongoid/config/encryption.rb +170 -22
  44. data/lib/mongoid/config.rb +56 -5
  45. data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
  46. data/lib/mongoid/contextual/memory.rb +18 -7
  47. data/lib/mongoid/criteria/includable.rb +35 -11
  48. data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
  49. data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
  50. data/lib/mongoid/deprecable.rb +45 -2
  51. data/lib/mongoid/document.rb +3 -0
  52. data/lib/mongoid/encryptable.rb +73 -0
  53. data/lib/mongoid/errors/config_redactor.rb +43 -0
  54. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
  55. data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
  56. data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
  57. data/lib/mongoid/errors/no_client_database.rb +1 -1
  58. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  59. data/lib/mongoid/errors/no_encryption_schema.rb +30 -0
  60. data/lib/mongoid/errors/readonly_association.rb +29 -0
  61. data/lib/mongoid/errors.rb +5 -0
  62. data/lib/mongoid/extensions/vector.rb +64 -0
  63. data/lib/mongoid/extensions.rb +1 -0
  64. data/lib/mongoid/field_readable.rb +69 -0
  65. data/lib/mongoid/fields.rb +7 -0
  66. data/lib/mongoid/indexable.rb +38 -26
  67. data/lib/mongoid/matchable.rb +6 -1
  68. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
  69. data/lib/mongoid/matcher/regex.rb +11 -12
  70. data/lib/mongoid/matcher/regexp_budget.rb +372 -0
  71. data/lib/mongoid/matcher.rb +1 -0
  72. data/lib/mongoid/persistable/creatable.rb +120 -14
  73. data/lib/mongoid/persistable/updatable.rb +9 -11
  74. data/lib/mongoid/persistable.rb +15 -15
  75. data/lib/mongoid/persistence_context.rb +77 -0
  76. data/lib/mongoid/search_indexable.rb +69 -24
  77. data/lib/mongoid/serializable.rb +12 -4
  78. data/lib/mongoid/tasks/database.rb +33 -16
  79. data/lib/mongoid/threaded.rb +36 -0
  80. data/lib/mongoid/timestamps/timeless.rb +84 -10
  81. data/lib/mongoid/touchable.rb +28 -1
  82. data/lib/mongoid/traversable.rb +2 -2
  83. data/lib/mongoid/validatable/associated.rb +18 -2
  84. data/lib/mongoid/version.rb +1 -1
  85. data/lib/mongoid/warnings.rb +4 -0
  86. data/spec/integration/app_spec.rb +7 -1
  87. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  88. data/spec/integration/associations/embedded_delayed_atomic_sets_spec.rb +216 -0
  89. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +31 -2
  90. data/spec/integration/atomic/modifiers_spec.rb +283 -0
  91. data/spec/integration/dots_and_dollars_spec.rb +12 -2
  92. data/spec/integration/encryption_spec.rb +300 -0
  93. data/spec/integration/matcher_operator_data/regex.yml +21 -0
  94. data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
  95. data/spec/integration/query_operator_guard_spec.rb +89 -0
  96. data/spec/mongoid/association/accessors_spec.rb +35 -0
  97. data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
  98. data/spec/mongoid/association/eager_spec.rb +2 -2
  99. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
  100. data/spec/mongoid/association/fallback_spec.rb +425 -0
  101. data/spec/mongoid/association/macros_spec.rb +26 -0
  102. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
  103. data/spec/mongoid/association/referenced/has_and_belongs_to_many/buildable_spec.rb +11 -0
  104. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
  105. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
  106. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
  107. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
  108. data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
  109. data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
  110. data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
  111. data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
  112. data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
  113. data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
  114. data/spec/mongoid/atomic/modifiers_spec.rb +347 -0
  115. data/spec/mongoid/attributes/nested_spec.rb +278 -8
  116. data/spec/mongoid/clients/factory_spec.rb +36 -0
  117. data/spec/mongoid/config/defaults_spec.rb +33 -0
  118. data/spec/mongoid/config/encryption_spec.rb +451 -0
  119. data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
  120. data/spec/mongoid/contextual/memory_spec.rb +177 -0
  121. data/spec/mongoid/criteria/includable_spec.rb +1221 -0
  122. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
  123. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
  124. data/spec/mongoid/criteria_spec.rb +3 -0
  125. data/spec/mongoid/encryptable_spec.rb +115 -0
  126. data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
  127. data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
  128. data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
  129. data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
  130. data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
  131. data/spec/mongoid/extensions/vector_spec.rb +175 -0
  132. data/spec/mongoid/fields_spec.rb +8 -0
  133. data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
  134. data/spec/mongoid/search_indexable_spec.rb +321 -9
  135. data/spec/mongoid/tasks/database_spec.rb +31 -0
  136. data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
  137. data/spec/mongoid/touchable_spec.rb +242 -0
  138. data/spec/mongoid/touchable_spec_models.rb +47 -0
  139. data/spec/mongoid/validatable/associated_spec.rb +19 -0
  140. data/spec/spec_helper.rb +5 -1
  141. data/spec/support/crypt/models.rb +253 -0
  142. data/spec/support/models/modifier_models.rb +65 -0
  143. metadata +55 -2
@@ -72,6 +72,19 @@ describe Mongoid::Config::Encryption do
72
72
  end
73
73
  end
74
74
 
75
+ # Models are registered in class load order, and Rails eager loads
76
+ # app/models alphabetically, so a child whose file name sorts before
77
+ # its parent's arrives first.
78
+ context 'when the embedded model comes before its parent' do
79
+ let(:models) do
80
+ [ Crypt::Insurance, Crypt::Patient ]
81
+ end
82
+
83
+ it 'returns a map of encryption schemas' do
84
+ expect(encryption_schema_map).to eq(expected_schema_map)
85
+ end
86
+ end
87
+
75
88
  context 'and fields do not have encryption options' do
76
89
  let(:models) do
77
90
  [ Crypt::Car ]
@@ -106,6 +119,7 @@ describe Mongoid::Config::Encryption do
106
119
  let(:expected_schema_map) do
107
120
  {
108
121
  'mongoid_test.crypt_users' => {
122
+ 'bsonType' => 'object',
109
123
  'properties' => {
110
124
  'name' => {
111
125
  'encrypt' => {
@@ -145,5 +159,442 @@ describe Mongoid::Config::Encryption do
145
159
  expect(encryption_schema_map).to eq({})
146
160
  end
147
161
  end
162
+
163
+ # A field left out of the schema map is written in plaintext, with no
164
+ # exception and nothing logged, so an omission here is silent data
165
+ # exposure rather than a broken feature.
166
+ context 'when the encrypted fields are on an embedded model' do
167
+ let(:token_properties) do
168
+ {
169
+ 'bsonType' => 'object',
170
+ 'properties' => {
171
+ 'value' => {
172
+ 'encrypt' => {
173
+ 'bsonType' => 'string',
174
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
175
+ }
176
+ }
177
+ }
178
+ }
179
+ end
180
+
181
+ context 'when two models embed the same encrypted model' do
182
+ let(:models) do
183
+ [ Crypt::Vault, Crypt::Chest ]
184
+ end
185
+
186
+ it 'maps the embedded model for the first parent' do
187
+ expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
188
+ .to eq(token_properties)
189
+ end
190
+
191
+ it 'maps the embedded model for the second parent' do
192
+ expect(encryption_schema_map.dig('mongoid_test.crypt_chests', 'properties', 'token'))
193
+ .to eq(token_properties)
194
+ end
195
+ end
196
+
197
+ context 'when one model embeds the same encrypted model twice' do
198
+ let(:models) do
199
+ [ Crypt::Ledger ]
200
+ end
201
+
202
+ it 'maps the first relation' do
203
+ expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'primary_token'))
204
+ .to eq(token_properties)
205
+ end
206
+
207
+ it 'maps the second relation' do
208
+ expect(encryption_schema_map.dig('mongoid_test.crypt_ledgers', 'properties', 'backup_token'))
209
+ .to eq(token_properties)
210
+ end
211
+ end
212
+
213
+ # relation_class constantizes, and a polymorphic embedded_in has no class
214
+ # to resolve, so the walk has to look at the relation type first.
215
+ context 'when the embedded model is embedded polymorphically' do
216
+ let(:models) do
217
+ [ Crypt::Vault ]
218
+ end
219
+
220
+ it 'does not raise' do
221
+ expect { encryption_schema_map }.not_to raise_error
222
+ end
223
+ end
224
+
225
+ context 'when the embedded model comes before its parent' do
226
+ let(:models) do
227
+ [ Crypt::Token, Crypt::Vault ]
228
+ end
229
+
230
+ it 'maps the embedded model' do
231
+ expect(encryption_schema_map.dig('mongoid_test.crypt_vaults', 'properties', 'token'))
232
+ .to eq(token_properties)
233
+ end
234
+ end
235
+
236
+ context 'when the parent has no encrypted field of its own' do
237
+ let(:models) do
238
+ [ Crypt::Wallet, Crypt::Token ]
239
+ end
240
+
241
+ it 'maps the embedded model' do
242
+ expect(encryption_schema_map.dig('mongoid_test.crypt_wallets', 'properties', 'token'))
243
+ .to eq(token_properties)
244
+ end
245
+ end
246
+
247
+ context 'when the encrypted model is nested three levels deep' do
248
+ let(:account_properties) do
249
+ {
250
+ 'bsonType' => 'object',
251
+ 'properties' => {
252
+ 'credential' => {
253
+ 'bsonType' => 'object',
254
+ 'properties' => {
255
+ 'secret' => {
256
+ 'encrypt' => {
257
+ 'bsonType' => 'string',
258
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ end
266
+
267
+ context 'when the models are listed outermost first' do
268
+ let(:models) do
269
+ [ Crypt::Owner, Crypt::Account, Crypt::Credential ]
270
+ end
271
+
272
+ it 'maps the whole subtree' do
273
+ expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
274
+ .to eq(account_properties)
275
+ end
276
+ end
277
+
278
+ context 'when the middle model comes first' do
279
+ let(:models) do
280
+ [ Crypt::Account, Crypt::Owner, Crypt::Credential ]
281
+ end
282
+
283
+ it 'maps the whole subtree' do
284
+ expect(encryption_schema_map.dig('mongoid_test.crypt_owners', 'properties', 'account'))
285
+ .to eq(account_properties)
286
+ end
287
+ end
288
+ end
289
+
290
+ # The walk has to keep stopping at a model it is already inside of,
291
+ # otherwise a self-embedding model recurses forever.
292
+ context 'when a model embeds itself' do
293
+ let(:models) do
294
+ [ Crypt::Article ]
295
+ end
296
+
297
+ it 'terminates' do
298
+ expect { encryption_schema_map }.not_to raise_error
299
+ end
300
+
301
+ it 'maps the embedded model without descending into the cycle' do
302
+ expect(encryption_schema_map.dig('mongoid_test.crypt_articles', 'properties', 'comment')).to eq(
303
+ 'bsonType' => 'object',
304
+ 'properties' => {
305
+ 'body' => {
306
+ 'encrypt' => {
307
+ 'bsonType' => 'string',
308
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
309
+ }
310
+ }
311
+ }
312
+ )
313
+ end
314
+ end
315
+ end
316
+
317
+ context 'when an encrypted model has a callable database name' do
318
+ let(:models) do
319
+ [ Crypt::DynamicCar ]
320
+ end
321
+
322
+ it 'does not build a key from the unresolved callable' do
323
+ expect(encryption_schema_map.keys.grep(/Proc/)).to be_empty
324
+ end
325
+
326
+ it 'does not build a key that the model can never write to' do
327
+ expect(encryption_schema_map.keys).to all(satisfy { |key| key.count('.') == 1 })
328
+ end
329
+ end
330
+
331
+ context 'when an encrypted model has a callable database name that needs a tenant' do
332
+ let(:models) do
333
+ [ Crypt::TenantCar ]
334
+ end
335
+
336
+ # Resolving the callable while the map is built is not a fix: the
337
+ # documented multi-tenant idiom has no correct value at client
338
+ # construction time. Generating the map must not depend on it.
339
+ it 'does not raise' do
340
+ expect { encryption_schema_map }.not_to raise_error
341
+ end
342
+
343
+ it 'does not pin the map to whichever tenant happens to be current' do
344
+ Thread.current[:tenant_database] = 'tenant_a'
345
+ expect(encryption_schema_map.keys).not_to include('tenant_a.crypt_tenant_cars')
346
+ ensure
347
+ Thread.current[:tenant_database] = nil
348
+ end
349
+ end
350
+
351
+ # The map is generated over every model in the application, and a model may
352
+ # name an embedded class that is never defined. Resolving that name raises,
353
+ # which would take down every client build.
354
+ context 'when a model names an embedded class that is not defined' do
355
+ let(:models) do
356
+ [ Crypt::Drawer, Crypt::Cabinet ]
357
+ end
358
+
359
+ it 'does not raise' do
360
+ expect { encryption_schema_map }.not_to raise_error
361
+ end
362
+
363
+ it 'leaves out the model with nothing to encrypt' do
364
+ expect(encryption_schema_map.keys).not_to include('mongoid_test.crypt_drawers')
365
+ end
366
+
367
+ it 'still maps the encrypted fields of the model itself' do
368
+ expect(encryption_schema_map.dig('mongoid_test.crypt_cabinets', 'properties', 'label')).to eq(
369
+ 'encrypt' => {
370
+ 'bsonType' => 'string',
371
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
372
+ }
373
+ )
374
+ end
375
+ end
376
+
377
+ # The map has one entry per namespace. Every model that writes to the
378
+ # namespace, including subclasses that are never registered in
379
+ # Mongoid.models, has to be covered by that entry, or its encrypted fields
380
+ # are written in plaintext.
381
+ context 'when more than one model writes to a namespace' do
382
+ let(:encrypt_metadata) do
383
+ {
384
+ 'keyId' => [ BSON::Binary.new(Base64.decode64('grolrnFVSSW9Gq04Q87R9Q=='), :uuid) ],
385
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
386
+ }
387
+ end
388
+
389
+ let(:encrypted_string) do
390
+ { 'encrypt' => { 'bsonType' => 'string' } }
391
+ end
392
+
393
+ context 'when two models are stored in one collection' do
394
+ let(:expected_entry) do
395
+ {
396
+ 'bsonType' => 'object',
397
+ 'encryptMetadata' => encrypt_metadata,
398
+ 'properties' => {
399
+ 'serial' => encrypted_string,
400
+ 'holder' => encrypted_string
401
+ }
402
+ }
403
+ end
404
+
405
+ context 'when the models are registered in one order' do
406
+ let(:models) do
407
+ [ Crypt::Badge, Crypt::BadgeScan ]
408
+ end
409
+
410
+ it 'maps the fields of both models' do
411
+ expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
412
+ end
413
+ end
414
+
415
+ context 'when the models are registered in the other order' do
416
+ let(:models) do
417
+ [ Crypt::BadgeScan, Crypt::Badge ]
418
+ end
419
+
420
+ it 'maps the fields of both models' do
421
+ expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
422
+ end
423
+ end
424
+ end
425
+
426
+ context 'when a subclass adds an encrypted field to an encrypted parent' do
427
+ let(:expected_entry) do
428
+ {
429
+ 'bsonType' => 'object',
430
+ 'encryptMetadata' => encrypt_metadata,
431
+ 'properties' => {
432
+ 'ssn' => encrypted_string,
433
+ 'salary' => encrypted_string,
434
+ 'rate' => encrypted_string
435
+ }
436
+ }
437
+ end
438
+
439
+ # Subclasses are not registered in Mongoid.models, so only the parent
440
+ # is passed in.
441
+ context 'when only the parent is registered' do
442
+ let(:models) do
443
+ [ Crypt::Member ]
444
+ end
445
+
446
+ it 'maps the fields of the parent and of every subclass' do
447
+ expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
448
+ end
449
+ end
450
+
451
+ context 'when a subclass is registered as well' do
452
+ let(:models) do
453
+ [ Crypt::Member, Crypt::Employee ]
454
+ end
455
+
456
+ it 'maps the fields of the parent and of every subclass' do
457
+ expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
458
+ end
459
+ end
460
+ end
461
+
462
+ context 'when only a subclass has encrypted fields' do
463
+ let(:models) do
464
+ [ Crypt::Listing ]
465
+ end
466
+
467
+ it 'maps the fields of the subclass' do
468
+ expect(encryption_schema_map['mongoid_test.crypt_listings']).to eq(
469
+ 'bsonType' => 'object',
470
+ 'encryptMetadata' => encrypt_metadata,
471
+ 'properties' => {
472
+ 'owner_ssn' => encrypted_string
473
+ }
474
+ )
475
+ end
476
+ end
477
+
478
+ context 'when a subclass of an embeds_one target adds an encrypted field' do
479
+ let(:models) do
480
+ [ Crypt::Box ]
481
+ end
482
+
483
+ it 'maps the fields of the target and of its subclass' do
484
+ expect(encryption_schema_map.dig('mongoid_test.crypt_boxes', 'properties', 'item')).to eq(
485
+ 'bsonType' => 'object',
486
+ 'properties' => {
487
+ 'label' => encrypted_string,
488
+ 'seal' => encrypted_string
489
+ }
490
+ )
491
+ end
492
+ end
493
+
494
+ context 'when only a subclass of an embeds_one target has encrypted fields' do
495
+ let(:models) do
496
+ [ Crypt::Locker ]
497
+ end
498
+
499
+ it 'maps the fields of the subclass' do
500
+ expect(encryption_schema_map.dig('mongoid_test.crypt_lockers', 'properties', 'content')).to eq(
501
+ 'bsonType' => 'object',
502
+ 'encryptMetadata' => encrypt_metadata,
503
+ 'properties' => {
504
+ 'code' => encrypted_string
505
+ }
506
+ )
507
+ end
508
+ end
509
+
510
+ # These models are not added to the shared support file: every client
511
+ # with automatic encryption builds the map over all registered models,
512
+ # and a conflict would make every such client fail to build.
513
+ context 'when models on one collection disagree on encryption' do
514
+ let(:defined_models) { [] }
515
+
516
+ after do
517
+ defined_models.each { |model| Mongoid.deregister_model(model) }
518
+ end
519
+
520
+ def define_model(&block)
521
+ model = Class.new do
522
+ include Mongoid::Document
523
+
524
+ store_in collection: 'crypt_conflicts'
525
+ end
526
+ model.class_eval(&block)
527
+ defined_models << model
528
+ model
529
+ end
530
+
531
+ context 'when the same field is encrypted the same way' do
532
+ let(:models) do
533
+ [
534
+ define_model { field :code, type: String, encrypt: { deterministic: true } },
535
+ define_model { field :code, type: String, encrypt: { deterministic: true } }
536
+ ]
537
+ end
538
+
539
+ it 'maps the field once' do
540
+ expect(encryption_schema_map['mongoid_test.crypt_conflicts']).to eq(
541
+ 'bsonType' => 'object',
542
+ 'properties' => {
543
+ 'code' => {
544
+ 'encrypt' => {
545
+ 'bsonType' => 'string',
546
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
547
+ }
548
+ }
549
+ }
550
+ )
551
+ end
552
+ end
553
+
554
+ context 'when the same field is encrypted differently' do
555
+ let(:models) do
556
+ [
557
+ define_model { field :code, type: String, encrypt: { deterministic: true } },
558
+ define_model { field :code, type: String, encrypt: { deterministic: false } }
559
+ ]
560
+ end
561
+
562
+ it 'raises' do
563
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
564
+ end
565
+ end
566
+
567
+ context 'when the collection metadata differs' do
568
+ let(:models) do
569
+ [
570
+ define_model do
571
+ encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
572
+ field :code, type: String, encrypt: true
573
+ end,
574
+ define_model do
575
+ encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: false
576
+ field :name, type: String, encrypt: true
577
+ end
578
+ ]
579
+ end
580
+
581
+ it 'raises' do
582
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
583
+ end
584
+ end
585
+
586
+ context 'when a subclass encrypts an inherited field differently' do
587
+ let(:models) do
588
+ parent = define_model { field :code, type: String, encrypt: { deterministic: true } }
589
+ Class.new(parent) { field :code, type: String, encrypt: { deterministic: false } }
590
+ [ parent ]
591
+ end
592
+
593
+ it 'raises' do
594
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
595
+ end
596
+ end
597
+ end
598
+ end
148
599
  end
149
600
  end
@@ -545,4 +545,95 @@ describe Mongoid::Contextual::Aggregable::Memory do
545
545
  end
546
546
  end
547
547
  end
548
+
549
+ context 'when the field name is a method name' do
550
+ let!(:depeche) do
551
+ Band.create!(name: 'Depeche Mode', likes: 1000)
552
+ end
553
+
554
+ let(:criteria) do
555
+ Band.all.tap do |crit|
556
+ crit.documents = [ depeche ]
557
+ end
558
+ end
559
+
560
+ it 'does not call the method for sum' do
561
+ expect(depeche).not_to receive(:delete)
562
+ expect(context.sum(:delete)).to eq(0)
563
+ end
564
+
565
+ it 'does not call the method for min' do
566
+ expect(depeche).not_to receive(:delete)
567
+ expect(context.min(:delete)).to be_nil
568
+ end
569
+
570
+ it 'does not call the method for max' do
571
+ expect(depeche).not_to receive(:delete)
572
+ expect(context.max(:delete)).to be_nil
573
+ end
574
+
575
+ it 'does not call the method for avg' do
576
+ expect(depeche).not_to receive(:delete)
577
+ expect(context.avg(:delete)).to be_nil
578
+ end
579
+
580
+ it 'does not call the method for aggregates' do
581
+ expect(depeche).not_to receive(:delete)
582
+ expect(context.aggregates(:delete))
583
+ .to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0)
584
+ end
585
+
586
+ it 'does not call a private method for avg' do
587
+ expect(depeche).not_to receive(:exit)
588
+ expect(context.avg(:exit)).to be_nil
589
+ end
590
+ end
591
+
592
+ context 'when the field is not defined' do
593
+ let!(:depeche) do
594
+ Band.create!(name: 'Depeche Mode', likes: 1000)
595
+ end
596
+
597
+ let(:criteria) do
598
+ Band.all.tap do |crit|
599
+ crit.documents = [ depeche ]
600
+ end
601
+ end
602
+
603
+ it 'returns zero for sum' do
604
+ expect(context.sum(:not_a_field)).to eq(0)
605
+ end
606
+
607
+ it 'returns nil for min' do
608
+ expect(context.min(:not_a_field)).to be_nil
609
+ end
610
+
611
+ it 'returns nil for max' do
612
+ expect(context.max(:not_a_field)).to be_nil
613
+ end
614
+
615
+ it 'returns nil for avg' do
616
+ expect(context.avg(:not_a_field)).to be_nil
617
+ end
618
+ end
619
+
620
+ context 'when the field is the foreign key of a many to many association' do
621
+ let!(:preference) do
622
+ Preference.create!(name: 'nature')
623
+ end
624
+
625
+ let!(:person) do
626
+ Person.create!(preferences: [ preference ])
627
+ end
628
+
629
+ let(:criteria) do
630
+ Person.all.tap do |crit|
631
+ crit.documents = [ person ]
632
+ end
633
+ end
634
+
635
+ it 'aggregates the stored ids' do
636
+ expect(context.max(:preference_ids)).to eq([ preference.id ])
637
+ end
638
+ end
548
639
  end