mongoid 9.0.12 → 9.0.13

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.
@@ -114,4 +114,61 @@ describe Mongoid::Atomic::Modifiers do
114
114
  end
115
115
  end
116
116
 
117
+ context 'when assigning embeds_many attributes on a new embedded document' do
118
+ let(:cabinet) do
119
+ ModifierModels::Cabinet.create!
120
+ end
121
+
122
+ before do
123
+ cabinet.reload
124
+ end
125
+
126
+ context 'when the nested documents are assigned on the built document' do
127
+ before do
128
+ cabinet.build_drawer(matcher: 'all')
129
+ cabinet.drawer.assign_attributes(items: [ { name: 'a' } ])
130
+ end
131
+
132
+ it 'saves without conflicting $set paths' do
133
+ expect { cabinet.save! }.not_to raise_error
134
+
135
+ cabinet.reload
136
+ expect(cabinet.drawer.matcher).to eq('all')
137
+ expect(cabinet.drawer.items.map(&:name)).to eq([ 'a' ])
138
+ end
139
+ end
140
+
141
+ context 'when the nested documents are assigned via the parent' do
142
+ before do
143
+ cabinet.assign_attributes(drawer: { matcher: 'all', items: [ { name: 'a' } ] })
144
+ end
145
+
146
+ it 'saves without conflicting $set paths' do
147
+ expect { cabinet.save! }.not_to raise_error
148
+
149
+ cabinet.reload
150
+ expect(cabinet.drawer.matcher).to eq('all')
151
+ expect(cabinet.drawer.items.map(&:name)).to eq([ 'a' ])
152
+ end
153
+ end
154
+
155
+ context 'when an assigned child has its own embedded collection' do
156
+ let(:item) { ModifierModels::Item.new(name: 'a') }
157
+
158
+ before do
159
+ item.assign_attributes(widgets: [ { label: 'w' } ])
160
+ cabinet.build_drawer(matcher: 'all')
161
+ cabinet.drawer.assign_attributes(items: [ item ])
162
+ end
163
+
164
+ it 'does not write the child collection to the root document' do
165
+ expect { cabinet.save! }.not_to raise_error
166
+
167
+ stored = ModifierModels::Cabinet.collection.find(_id: cabinet.id).first
168
+ expect(stored.keys).to contain_exactly('_id', 'drawer')
169
+ expect(stored['drawer']['items'].first['widgets'].first['label']).to eq('w')
170
+ end
171
+ end
172
+ end
173
+
117
174
  end
@@ -246,4 +246,153 @@ describe 'Encryption' do
246
246
  end
247
247
  end
248
248
  end
249
+
250
+ # The schema map has one entry per namespace, and it has to cover every model
251
+ # that writes there: models sharing a collection, subclasses, and subclasses
252
+ # of embedded models. Documents are read back through a client without
253
+ # automatic encryption, to see what is actually stored.
254
+ describe 'when more than one model writes to a namespace' do
255
+ let(:root_models) do
256
+ [ Crypt::Badge, Crypt::BadgeScan, Crypt::Member, Crypt::Listing, Crypt::Box, Crypt::Locker ]
257
+ end
258
+
259
+ let(:keyed_models) do
260
+ [ Crypt::Badge, Crypt::BadgeScan, Crypt::Member, Crypt::PrivateListing, Crypt::Box, Crypt::SecretContent ]
261
+ end
262
+
263
+ around do |example|
264
+ original_options = root_models.to_h { |model| [ model, model.storage_options ] }
265
+ existing_key_ids = keyed_models.to_h { |model| [ model, model.encrypt_metadata[:key_id] ] }
266
+ keyed_models.each { |model| model.set_key_id(data_key_id) }
267
+ root_models.each do |model|
268
+ unencrypted_client[model.collection_name.to_s].drop
269
+ model.store_in(client: :encrypted)
270
+ end
271
+ # The schema map is generated when the client is built, so the client has
272
+ # to be built after the key ids above are in place.
273
+ Mongoid::Clients.clear
274
+
275
+ example.run
276
+
277
+ original_options.each { |model, options| model.storage_options = options }
278
+ existing_key_ids.each { |model, key_id| model.set_key_id(key_id) }
279
+ Mongoid::Clients.clear
280
+ end
281
+
282
+ def stored_document(model, id)
283
+ unencrypted_client[model.collection_name.to_s].find(_id: id).first
284
+ end
285
+
286
+ def expect_ciphertext(value)
287
+ expect(value).to be_a(BSON::Binary).and(have_attributes(type: :ciphertext))
288
+ end
289
+
290
+ context 'when two models are stored in one collection' do
291
+ it 'stores the fields of both models encrypted', :aggregate_failures do
292
+ badge = Crypt::Badge.create!(serial: 'SERIAL-1')
293
+ scan = Crypt::BadgeScan.create!(holder: 'HOLDER-1')
294
+
295
+ expect_ciphertext(stored_document(Crypt::Badge, badge.id)['serial'])
296
+ expect_ciphertext(stored_document(Crypt::BadgeScan, scan.id)['holder'])
297
+ end
298
+ end
299
+
300
+ context 'when a subclass adds an encrypted field to an encrypted parent' do
301
+ it 'stores the fields of the parent and of the subclass encrypted', :aggregate_failures do
302
+ employee = Crypt::Employee.create!(ssn: 'SSN-1', salary: 'SALARY-1')
303
+
304
+ stored_document(Crypt::Member, employee.id).tap do |doc|
305
+ expect_ciphertext(doc['ssn'])
306
+ expect_ciphertext(doc['salary'])
307
+ end
308
+ end
309
+
310
+ it 'stores the fields of a sibling subclass encrypted' do
311
+ contractor = Crypt::Contractor.create!(ssn: 'SSN-2', rate: 'RATE-1')
312
+
313
+ expect_ciphertext(stored_document(Crypt::Member, contractor.id)['rate'])
314
+ end
315
+
316
+ it 'reads the fields back' do
317
+ employee = Crypt::Employee.create!(ssn: 'SSN-3', salary: 'SALARY-3')
318
+
319
+ expect(Crypt::Member.find(employee.id).salary).to eq('SALARY-3')
320
+ end
321
+ end
322
+
323
+ context 'when only a subclass has encrypted fields' do
324
+ it 'stores the fields of the subclass encrypted' do
325
+ listing = Crypt::PrivateListing.create!(title: 'TITLE-1', owner_ssn: 'SSN-4')
326
+
327
+ expect_ciphertext(stored_document(Crypt::Listing, listing.id)['owner_ssn'])
328
+ end
329
+ end
330
+
331
+ context 'when a subclass of an embeds_one target adds an encrypted field' do
332
+ it 'stores the fields of the target and of the subclass encrypted', :aggregate_failures do
333
+ box = Crypt::Box.create!(item: Crypt::SealedItem.new(label: 'LABEL-1', seal: 'SEAL-1'))
334
+
335
+ stored_document(Crypt::Box, box.id)['item'].tap do |item|
336
+ expect_ciphertext(item['label'])
337
+ expect_ciphertext(item['seal'])
338
+ end
339
+ end
340
+ end
341
+
342
+ context 'when only a subclass of an embeds_one target has encrypted fields' do
343
+ it 'stores the fields of the subclass encrypted' do
344
+ locker = Crypt::Locker.create!(content: Crypt::SecretContent.new(label: 'LABEL-2', code: 'CODE-1'))
345
+
346
+ expect_ciphertext(stored_document(Crypt::Locker, locker.id)['content']['code'])
347
+ end
348
+ end
349
+
350
+ # An entry for the namespace exists, but it does not cover every encrypted
351
+ # field of the model. The driver encrypts what the entry lists and writes
352
+ # the rest in plaintext.
353
+ context 'when the schema entry for the namespace misses an encrypted field' do
354
+ let(:config) do
355
+ super().merge(
356
+ partial: {
357
+ hosts: SpecConfig.instance.addresses,
358
+ database: database_id,
359
+ options: {
360
+ auto_encryption_options: {
361
+ key_vault_client: :key_vault,
362
+ kms_providers: kms_providers,
363
+ key_vault_namespace: key_vault_namespace,
364
+ extra_options: extra_options,
365
+ schema_map: partial_schema_map
366
+ }
367
+ }
368
+ }
369
+ )
370
+ end
371
+
372
+ let(:partial_schema_map) do
373
+ {
374
+ "#{database_id}.#{Crypt::Member.collection_name}" => {
375
+ 'bsonType' => 'object',
376
+ 'encryptMetadata' => {
377
+ 'keyId' => [ BSON::Binary.new(Base64.decode64(data_key_id), :uuid) ],
378
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
379
+ },
380
+ 'properties' => {
381
+ 'ssn' => { 'encrypt' => { 'bsonType' => 'string' } }
382
+ }
383
+ }
384
+ }
385
+ end
386
+
387
+ it 'does not store the field in plaintext', :aggregate_failures do
388
+ expect do
389
+ Crypt::Employee.with(client: :partial) { |klass| klass.create!(ssn: 'SSN-5', salary: 'PARTIAL-1') }
390
+ end.to raise_error(Mongoid::Errors::NoEncryptionSchema)
391
+
392
+ expect(
393
+ unencrypted_client[Crypt::Member.collection_name.to_s].find(salary: 'PARTIAL-1').to_a
394
+ ).to be_empty
395
+ end
396
+ end
397
+ end
249
398
  end
@@ -354,6 +354,42 @@ describe Mongoid::Clients::Factory do
354
354
  end
355
355
  end
356
356
  end
357
+
358
+ context 'when bypass_auto_encryption is set' do
359
+ it 'warns that encryption is bypassed' do
360
+ expect(Mongoid::Warnings).to receive(:warn_bypass_auto_encryption)
361
+
362
+ described_class.send(
363
+ :build_auto_encryption_options,
364
+ { auto_encryption_options: { bypass_auto_encryption: true, schema_map: {} } },
365
+ database_id
366
+ )
367
+ end
368
+ end
369
+
370
+ context 'when bypass_auto_encryption is not set' do
371
+ it 'does not warn' do
372
+ expect(Mongoid::Warnings).not_to receive(:warn_bypass_auto_encryption)
373
+
374
+ described_class.send(
375
+ :build_auto_encryption_options,
376
+ { auto_encryption_options: { schema_map: {} } },
377
+ database_id
378
+ )
379
+ end
380
+ end
381
+
382
+ context 'when bypass_auto_encryption is false' do
383
+ it 'does not warn' do
384
+ expect(Mongoid::Warnings).not_to receive(:warn_bypass_auto_encryption)
385
+
386
+ described_class.send(
387
+ :build_auto_encryption_options,
388
+ { auto_encryption_options: { bypass_auto_encryption: false, schema_map: {} } },
389
+ database_id
390
+ )
391
+ end
392
+ end
357
393
  end
358
394
 
359
395
  context "when no name is provided" do
@@ -376,5 +376,228 @@ describe Mongoid::Config::Encryption do
376
376
  )
377
377
  end
378
378
  end
379
+
380
+ # The map has one entry per namespace. Every model that writes to the
381
+ # namespace, including subclasses that are never registered in
382
+ # Mongoid.models, has to be covered by that entry, or its encrypted fields
383
+ # are written in plaintext.
384
+ context 'when more than one model writes to a namespace' do
385
+ let(:encrypt_metadata) do
386
+ {
387
+ 'keyId' => [ BSON::Binary.new(Base64.decode64('grolrnFVSSW9Gq04Q87R9Q=='), :uuid) ],
388
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
389
+ }
390
+ end
391
+
392
+ let(:encrypted_string) do
393
+ { 'encrypt' => { 'bsonType' => 'string' } }
394
+ end
395
+
396
+ context 'when two models are stored in one collection' do
397
+ let(:expected_entry) do
398
+ {
399
+ 'bsonType' => 'object',
400
+ 'encryptMetadata' => encrypt_metadata,
401
+ 'properties' => {
402
+ 'serial' => encrypted_string,
403
+ 'holder' => encrypted_string
404
+ }
405
+ }
406
+ end
407
+
408
+ context 'when the models are registered in one order' do
409
+ let(:models) do
410
+ [ Crypt::Badge, Crypt::BadgeScan ]
411
+ end
412
+
413
+ it 'maps the fields of both models' do
414
+ expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
415
+ end
416
+ end
417
+
418
+ context 'when the models are registered in the other order' do
419
+ let(:models) do
420
+ [ Crypt::BadgeScan, Crypt::Badge ]
421
+ end
422
+
423
+ it 'maps the fields of both models' do
424
+ expect(encryption_schema_map['mongoid_test.crypt_badges']).to eq(expected_entry)
425
+ end
426
+ end
427
+ end
428
+
429
+ context 'when a subclass adds an encrypted field to an encrypted parent' do
430
+ let(:expected_entry) do
431
+ {
432
+ 'bsonType' => 'object',
433
+ 'encryptMetadata' => encrypt_metadata,
434
+ 'properties' => {
435
+ 'ssn' => encrypted_string,
436
+ 'salary' => encrypted_string,
437
+ 'rate' => encrypted_string
438
+ }
439
+ }
440
+ end
441
+
442
+ # Subclasses are not registered in Mongoid.models, so only the parent
443
+ # is passed in.
444
+ context 'when only the parent is registered' do
445
+ let(:models) do
446
+ [ Crypt::Member ]
447
+ end
448
+
449
+ it 'maps the fields of the parent and of every subclass' do
450
+ expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
451
+ end
452
+ end
453
+
454
+ context 'when a subclass is registered as well' do
455
+ let(:models) do
456
+ [ Crypt::Member, Crypt::Employee ]
457
+ end
458
+
459
+ it 'maps the fields of the parent and of every subclass' do
460
+ expect(encryption_schema_map['mongoid_test.crypt_members']).to eq(expected_entry)
461
+ end
462
+ end
463
+ end
464
+
465
+ context 'when only a subclass has encrypted fields' do
466
+ let(:models) do
467
+ [ Crypt::Listing ]
468
+ end
469
+
470
+ it 'maps the fields of the subclass' do
471
+ expect(encryption_schema_map['mongoid_test.crypt_listings']).to eq(
472
+ 'bsonType' => 'object',
473
+ 'encryptMetadata' => encrypt_metadata,
474
+ 'properties' => {
475
+ 'owner_ssn' => encrypted_string
476
+ }
477
+ )
478
+ end
479
+ end
480
+
481
+ context 'when a subclass of an embeds_one target adds an encrypted field' do
482
+ let(:models) do
483
+ [ Crypt::Box ]
484
+ end
485
+
486
+ it 'maps the fields of the target and of its subclass' do
487
+ expect(encryption_schema_map.dig('mongoid_test.crypt_boxes', 'properties', 'item')).to eq(
488
+ 'bsonType' => 'object',
489
+ 'properties' => {
490
+ 'label' => encrypted_string,
491
+ 'seal' => encrypted_string
492
+ }
493
+ )
494
+ end
495
+ end
496
+
497
+ context 'when only a subclass of an embeds_one target has encrypted fields' do
498
+ let(:models) do
499
+ [ Crypt::Locker ]
500
+ end
501
+
502
+ it 'maps the fields of the subclass' do
503
+ expect(encryption_schema_map.dig('mongoid_test.crypt_lockers', 'properties', 'content')).to eq(
504
+ 'bsonType' => 'object',
505
+ 'encryptMetadata' => encrypt_metadata,
506
+ 'properties' => {
507
+ 'code' => encrypted_string
508
+ }
509
+ )
510
+ end
511
+ end
512
+
513
+ # These models are not added to the shared support file: every client
514
+ # with automatic encryption builds the map over all registered models,
515
+ # and a conflict would make every such client fail to build.
516
+ context 'when models on one collection disagree on encryption' do
517
+ let(:defined_models) { [] }
518
+
519
+ after do
520
+ defined_models.each { |model| Mongoid.deregister_model(model) }
521
+ end
522
+
523
+ def define_model(&block)
524
+ model = Class.new do
525
+ include Mongoid::Document
526
+
527
+ store_in collection: 'crypt_conflicts'
528
+ end
529
+ model.class_eval(&block)
530
+ defined_models << model
531
+ model
532
+ end
533
+
534
+ context 'when the same field is encrypted the same way' do
535
+ let(:models) do
536
+ [
537
+ define_model { field :code, type: String, encrypt: { deterministic: true } },
538
+ define_model { field :code, type: String, encrypt: { deterministic: true } }
539
+ ]
540
+ end
541
+
542
+ it 'maps the field once' do
543
+ expect(encryption_schema_map['mongoid_test.crypt_conflicts']).to eq(
544
+ 'bsonType' => 'object',
545
+ 'properties' => {
546
+ 'code' => {
547
+ 'encrypt' => {
548
+ 'bsonType' => 'string',
549
+ 'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
550
+ }
551
+ }
552
+ }
553
+ )
554
+ end
555
+ end
556
+
557
+ context 'when the same field is encrypted differently' do
558
+ let(:models) do
559
+ [
560
+ define_model { field :code, type: String, encrypt: { deterministic: true } },
561
+ define_model { field :code, type: String, encrypt: { deterministic: false } }
562
+ ]
563
+ end
564
+
565
+ it 'raises' do
566
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
567
+ end
568
+ end
569
+
570
+ context 'when the collection metadata differs' do
571
+ let(:models) do
572
+ [
573
+ define_model do
574
+ encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: true
575
+ field :code, type: String, encrypt: true
576
+ end,
577
+ define_model do
578
+ encrypt_with key_id: 'grolrnFVSSW9Gq04Q87R9Q==', deterministic: false
579
+ field :name, type: String, encrypt: true
580
+ end
581
+ ]
582
+ end
583
+
584
+ it 'raises' do
585
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
586
+ end
587
+ end
588
+
589
+ context 'when a subclass encrypts an inherited field differently' do
590
+ let(:models) do
591
+ parent = define_model { field :code, type: String, encrypt: { deterministic: true } }
592
+ Class.new(parent) { field :code, type: String, encrypt: { deterministic: false } }
593
+ [ parent ]
594
+ end
595
+
596
+ it 'raises' do
597
+ expect { encryption_schema_map }.to raise_error(Mongoid::Errors::InvalidEncryptionSchema)
598
+ end
599
+ end
600
+ end
601
+ end
379
602
  end
380
603
  end
@@ -29,6 +29,14 @@ describe Mongoid::Encryptable do
29
29
  end
30
30
  end
31
31
 
32
+ # Any subclass of the declared class can be assigned to an embeds_one
33
+ # association, so the check has to look at the subclasses too.
34
+ context 'when only a subclass of an embedded model is encrypted' do
35
+ it 'returns true' do
36
+ expect(Crypt::Locker.requires_encryption_schema?).to be true
37
+ end
38
+ end
39
+
32
40
  context 'when the model declares no encryption anywhere' do
33
41
  it 'returns false' do
34
42
  expect(Person.requires_encryption_schema?).to be false
@@ -57,5 +65,51 @@ describe Mongoid::Encryptable do
57
65
  expect(Crypt::Drawer.requires_encryption_schema?).to be false
58
66
  end
59
67
  end
68
+
69
+ context 'when every embedded association resolves' do
70
+ it 'memoizes a false answer' do
71
+ expect(Truck.requires_encryption_schema?).to be false
72
+ expect(Truck.instance_variable_defined?(:@requires_encryption_schema)).to be true
73
+ end
74
+ end
75
+
76
+ context 'when an embedded association does not resolve' do
77
+ it 'does not memoize a false answer' do
78
+ expect(Crypt::Drawer.requires_encryption_schema?).to be false
79
+ expect(Crypt::Drawer.instance_variable_defined?(:@requires_encryption_schema)).to be false
80
+ end
81
+ end
82
+
83
+ context 'when an embedded encrypted class is defined after the first check' do
84
+ after do
85
+ if Crypt.const_defined?(:MissingNote, false)
86
+ Mongoid.deregister_model(Crypt::MissingNote)
87
+ Crypt.send(:remove_const, :MissingNote)
88
+ end
89
+ # While the class above existed, resolving it through the association
90
+ # memoized the class on the association, and a true answer on the
91
+ # model. Drop both, so the other examples see the model as it was
92
+ # before the class was defined.
93
+ relation = Crypt::Drawer.relations['missing_note']
94
+ relation.remove_instance_variable(:@klass) if relation.instance_variable_defined?(:@klass)
95
+ if Crypt::Drawer.instance_variable_defined?(:@requires_encryption_schema)
96
+ Crypt::Drawer.remove_instance_variable(:@requires_encryption_schema)
97
+ end
98
+ end
99
+
100
+ it 'returns true once the class is loaded' do
101
+ expect(Crypt::Drawer.requires_encryption_schema?).to be false
102
+
103
+ missing_note = Class.new do
104
+ include Mongoid::Document
105
+
106
+ embedded_in :drawer, class_name: 'Crypt::Drawer'
107
+ field :text, type: String, encrypt: true
108
+ end
109
+ Crypt.const_set(:MissingNote, missing_note)
110
+
111
+ expect(Crypt::Drawer.requires_encryption_schema?).to be true
112
+ end
113
+ end
60
114
  end
61
115
  end
@@ -32,6 +32,12 @@ describe Mongoid::Matcher::RegexpBudget do
32
32
  # exercising one has to hand .open something to bound.
33
33
  let(:regexp_selector) { { 'name' => /\Aabc\z/ } }
34
34
 
35
+ describe 'REGEXP_BUDGET_KEY' do
36
+ it 'is namespaced so a foreign thread-local cannot collide with it' do
37
+ expect(Mongoid::Threaded::REGEXP_BUDGET_KEY).to start_with('[mongoid]:')
38
+ end
39
+ end
40
+
35
41
  describe '.open' do
36
42
  context 'when no limit is configured' do
37
43
  config_override :in_memory_regexp_time_limit, nil
@@ -62,7 +62,7 @@ class SearchIndexHelper
62
62
  end
63
63
 
64
64
  # rubocop:disable RSpec/MultipleMemoizedHelpers
65
- describe Mongoid::SearchIndexable do
65
+ describe Mongoid::SearchIndexable do # rubocop:disable RSpec/MultipleDescribes
66
66
  before do
67
67
  skip "#{described_class} requires at Atlas environment (set ATLAS_URI)" if ENV['ATLAS_URI'].nil?
68
68
  end
@@ -145,3 +145,36 @@ describe Mongoid::SearchIndexable do
145
145
  end
146
146
  end
147
147
  # rubocop:enable RSpec/MultipleMemoizedHelpers
148
+
149
+ # A unit test that does not need an Atlas connection.
150
+ describe Mongoid::SearchIndexable, '.wait_for_search_indexes' do
151
+ # Mongo::Collection#search_indexes was added in driver 2.19; the
152
+ # driver-min and driver-oldstable CI jobs pin the driver to 2.18-stable.
153
+ min_driver_version '2.19'
154
+
155
+ context 'when an index is ready' do
156
+ let(:model) do
157
+ Class.new do
158
+ include Mongoid::Document
159
+
160
+ store_in collection: BSON::ObjectId.new.to_s
161
+ search_index :idx, mappings: { dynamic: false }
162
+ end
163
+ end
164
+
165
+ it 'reads the index status inside collection management' do
166
+ managing = nil
167
+ search_indexes = [ { 'name' => 'idx', 'queryable' => true, 'status' => 'READY' } ]
168
+ collection = instance_double(Mongo::Collection)
169
+ allow(collection).to receive(:search_indexes) do
170
+ managing = Mongoid::Threaded.managing_collection?
171
+ search_indexes
172
+ end
173
+ allow(model).to receive(:collection).and_return(collection)
174
+
175
+ model.wait_for_search_indexes([ 'idx' ])
176
+
177
+ expect(managing).to be true
178
+ end
179
+ end
180
+ end
@@ -265,6 +265,19 @@ describe Mongoid::Tasks::Database do
265
265
  described_class.create_search_indexes([searchable_model_spy], wait: false)
266
266
  end
267
267
  end
268
+
269
+ context 'when waiting for the indexes' do
270
+ it 'runs the wait inside collection management' do
271
+ managing = nil
272
+ allow(described_class).to receive(:wait_for_search_indexes) do
273
+ managing = Mongoid::Threaded.managing_collection?
274
+ end
275
+
276
+ described_class.create_search_indexes([ searchable_model_spy ], wait: true)
277
+
278
+ expect(managing).to be true
279
+ end
280
+ end
268
281
  end
269
282
 
270
283
  describe '.remove_search_indexes' do