mongo 2.19.2 → 2.19.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/mongo/collection.rb +23 -1
  4. data/lib/mongo/operation/create_search_indexes/op_msg.rb +31 -0
  5. data/lib/mongo/operation/create_search_indexes.rb +15 -0
  6. data/lib/mongo/operation/drop_search_index/op_msg.rb +33 -0
  7. data/lib/mongo/operation/drop_search_index.rb +15 -0
  8. data/lib/mongo/operation/shared/specifiable.rb +7 -0
  9. data/lib/mongo/operation/update_search_index/op_msg.rb +34 -0
  10. data/lib/mongo/operation/update_search_index.rb +15 -0
  11. data/lib/mongo/operation.rb +3 -0
  12. data/lib/mongo/search_index/view.rb +232 -0
  13. data/lib/mongo/version.rb +1 -1
  14. data/lib/mongo.rb +1 -0
  15. data/spec/atlas/atlas_connectivity_spec.rb +1 -5
  16. data/spec/atlas/operations_spec.rb +1 -5
  17. data/spec/integration/search_indexes_prose_spec.rb +168 -0
  18. data/spec/lite_spec_helper.rb +32 -10
  19. data/spec/runners/unified/search_index_operations.rb +63 -0
  20. data/spec/runners/unified/test.rb +3 -1
  21. data/spec/spec_helper.rb +1 -1
  22. data/spec/spec_tests/data/index_management/createSearchIndex.yml +62 -0
  23. data/spec/spec_tests/data/index_management/createSearchIndexes.yml +83 -0
  24. data/spec/spec_tests/data/index_management/dropSearchIndex.yml +42 -0
  25. data/spec/spec_tests/data/index_management/listSearchIndexes.yml +85 -0
  26. data/spec/spec_tests/data/index_management/updateSearchIndex.yml +45 -0
  27. data/spec/spec_tests/index_management_unified_spec.rb +13 -0
  28. data/spec/support/faas/app/aws_lambda/mongodb/Gemfile.lock +19 -0
  29. data/spec/support/spec_config.rb +5 -0
  30. data.tar.gz.sig +0 -0
  31. metadata +1276 -1251
  32. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 455d5b9685fd2cefa18a1c37e81ddb97a177ac8e40b9298945e4f126ac9ce093
4
- data.tar.gz: fab0630edde803ab5125536159e8fbd12d3f0bac233e87d40ea1e414287145ac
3
+ metadata.gz: '0916a285c62c2e70c94532ca2ed61af6bfcb1ead971a2be825aefbde560209c3'
4
+ data.tar.gz: a9db987bb6f1e2b7e9882c86472bc2c8b10eca0d4bdca9de2a37968de086d8f3
5
5
  SHA512:
6
- metadata.gz: f3db945520bf825c31fcd7a21295b03257b1ee01d3d2eb6903f69ae6dfdbb0834516c8214227196702f88e087754780a876941eb5a3ac8f0861bff40a3e55a07
7
- data.tar.gz: 73ea3f0a4fc86ba9922c1054f925db03eab8ac3da47bd9b9daae5006af1f32299c3d1a45104623126db5c89e41bf3ddbfe82dd641ea77dce6d8936a5297a1880
6
+ metadata.gz: ab6a8e7e008174f346f1acd96bb073a39cd4fbdd2a70e44f63f8dcdc421c4d38ba9277cc9c50099c944599886e53230597bdab8bd4c861de29af2bd66dd2be76
7
+ data.tar.gz: 8fe9155b17bbb9b57021eabd7e2211baf2cb0002b211900e8564e0b1c2f9e894bf726a037b1e8b72e6dd55251edefe1e81fd8c3ea61aacd0fd5d7eb76cbc4163
checksums.yaml.gz.sig CHANGED
Binary file
@@ -725,13 +725,35 @@ module Mongo
725
725
  #
726
726
  # @option options [ Session ] :session The session to use.
727
727
  #
728
- # @return [ View::Index ] The index view.
728
+ # @return [ Index::View ] The index view.
729
729
  #
730
730
  # @since 2.0.0
731
731
  def indexes(options = {})
732
732
  Index::View.new(self, options)
733
733
  end
734
734
 
735
+ # Get a view of all search indexes for this collection. Can be iterated or
736
+ # operated on directly. If id or name are given, the iterator will return
737
+ # only the indicated index. For all other operations, id and name are
738
+ # ignored.
739
+ #
740
+ # @note Only one of id or name may be given; it is an error to specify both,
741
+ # although both may be omitted safely.
742
+ #
743
+ # @param [ Hash ] options The options to use to configure the view.
744
+ #
745
+ # @option options [ String ] :id The id of the specific index to query (optional)
746
+ # @option options [ String ] :name The name of the specific index to query (optional)
747
+ # @option options [ Hash ] :aggregate The options hash to pass to the
748
+ # aggregate command (optional)
749
+ #
750
+ # @return [ SearchIndex::View ] The search index view.
751
+ #
752
+ # @since 2.0.0
753
+ def search_indexes(options = {})
754
+ SearchIndex::View.new(self, options)
755
+ end
756
+
735
757
  # Get a pretty printed string inspection for the collection.
736
758
  #
737
759
  # @example Inspect the collection.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ module Operation
5
+ class CreateSearchIndexes
6
+ # A MongoDB createSearchIndexes operation sent as an op message.
7
+ #
8
+ # @api private
9
+ class OpMsg < OpMsgBase
10
+ include ExecutableTransactionLabel
11
+
12
+ private
13
+
14
+ # Returns the command to send to the database, describing the
15
+ # desired createSearchIndexes operation.
16
+ #
17
+ # @param [ Mongo::Server ] _server the server that will receive the
18
+ # command
19
+ #
20
+ # @return [ Hash ] the selector
21
+ def selector(_server)
22
+ {
23
+ createSearchIndexes: coll_name,
24
+ :$db => db_name,
25
+ indexes: indexes,
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo/operation/create_search_indexes/op_msg'
4
+
5
+ module Mongo
6
+ module Operation
7
+ # A MongoDB createSearchIndexes command operation.
8
+ #
9
+ # @api private
10
+ class CreateSearchIndexes
11
+ include Specifiable
12
+ include OpMsgExecutable
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ module Operation
5
+ class DropSearchIndex
6
+ # A MongoDB createSearchIndexes operation sent as an op message.
7
+ #
8
+ # @api private
9
+ class OpMsg < OpMsgBase
10
+ include ExecutableTransactionLabel
11
+
12
+ private
13
+
14
+ # Returns the command to send to the database, describing the
15
+ # desired dropSearchIndex operation.
16
+ #
17
+ # @param [ Mongo::Server ] _server the server that will receive the
18
+ # command
19
+ #
20
+ # @return [ Hash ] the selector
21
+ def selector(_server)
22
+ {
23
+ dropSearchIndex: coll_name,
24
+ :$db => db_name,
25
+ }.tap do |sel|
26
+ sel[:id] = index_id if index_id
27
+ sel[:name] = index_name if index_name
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo/operation/drop_search_index/op_msg'
4
+
5
+ module Mongo
6
+ module Operation
7
+ # A MongoDB dropSearchIndex command operation.
8
+ #
9
+ # @api private
10
+ class DropSearchIndex
11
+ include Specifiable
12
+ include OpMsgExecutable
13
+ end
14
+ end
15
+ end
@@ -260,6 +260,13 @@ module Mongo
260
260
  spec[INDEX]
261
261
  end
262
262
 
263
+ # Get the index id from the spec.
264
+ #
265
+ # @return [ String ] The index id.
266
+ def index_id
267
+ spec[:index_id]
268
+ end
269
+
263
270
  # Get the index name from the spec.
264
271
  #
265
272
  # @example Get the index name.
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ module Operation
5
+ class UpdateSearchIndex
6
+ # A MongoDB updateSearchIndex operation sent as an op message.
7
+ #
8
+ # @api private
9
+ class OpMsg < OpMsgBase
10
+ include ExecutableTransactionLabel
11
+
12
+ private
13
+
14
+ # Returns the command to send to the database, describing the
15
+ # desired updateSearchIndex operation.
16
+ #
17
+ # @param [ Mongo::Server ] _server the server that will receive the
18
+ # command
19
+ #
20
+ # @return [ Hash ] the selector
21
+ def selector(_server)
22
+ {
23
+ updateSearchIndex: coll_name,
24
+ :$db => db_name,
25
+ definition: index,
26
+ }.tap do |sel|
27
+ sel[:id] = index_id if index_id
28
+ sel[:name] = index_name if index_name
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo/operation/update_search_index/op_msg'
4
+
5
+ module Mongo
6
+ module Operation
7
+ # A MongoDB updateSearchIndex command operation.
8
+ #
9
+ # @api private
10
+ class UpdateSearchIndex
11
+ include Specifiable
12
+ include OpMsgExecutable
13
+ end
14
+ end
15
+ end
@@ -51,6 +51,9 @@ require 'mongo/operation/update_user'
51
51
  require 'mongo/operation/remove_user'
52
52
  require 'mongo/operation/create_index'
53
53
  require 'mongo/operation/drop_index'
54
+ require 'mongo/operation/create_search_indexes'
55
+ require 'mongo/operation/drop_search_index'
56
+ require 'mongo/operation/update_search_index'
54
57
 
55
58
  module Mongo
56
59
 
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ module SearchIndex
5
+ # A class representing a view of search indexes.
6
+ class View
7
+ include Enumerable
8
+ include Retryable
9
+ include Collection::Helpers
10
+
11
+ # @return [ Mongo::Collection ] the collection this view belongs to
12
+ attr_reader :collection
13
+
14
+ # @return [ nil | String ] the index id to query
15
+ attr_reader :requested_index_id
16
+
17
+ # @return [ nil | String ] the index name to query
18
+ attr_reader :requested_index_name
19
+
20
+ # @return [ Hash ] the options hash to use for the aggregate command
21
+ # when querying the available indexes.
22
+ attr_reader :aggregate_options
23
+
24
+ # Create the new search index view.
25
+ #
26
+ # @param [ Collection ] collection The collection.
27
+ # @param [ Hash ] options The options that configure the behavior of the view.
28
+ #
29
+ # @option options [ String ] :id The specific index id to query (optional)
30
+ # @option options [ String ] :name The name of the specific index to query (optional)
31
+ # @option options [ Hash ] :aggregate The options hash to send to the
32
+ # aggregate command when querying the available indexes.
33
+ def initialize(collection, options = {})
34
+ @collection = collection
35
+ @requested_index_id = options[:id]
36
+ @requested_index_name = options[:name]
37
+ @aggregate_options = options[:aggregate] || {}
38
+
39
+ return if @aggregate_options.is_a?(Hash)
40
+
41
+ raise ArgumentError, "The :aggregate option must be a Hash (got a #{@aggregate_options.class})"
42
+ end
43
+
44
+ # Create a single search index with the given definition. If the name is
45
+ # provided, the new index will be given that name.
46
+ #
47
+ # @param [ Hash ] definition The definition of the search index.
48
+ # @param [ nil | String ] name The name to give the new search index.
49
+ #
50
+ # @return [ String ] the name of the new search index.
51
+ def create_one(definition, name: nil)
52
+ create_many([ { name: name, definition: definition } ]).first
53
+ end
54
+
55
+ # Create multiple search indexes with a single command.
56
+ #
57
+ # @param [ Array<Hash> ] indexes The description of the indexes to
58
+ # create. Each element of the list must be a hash with a definition
59
+ # key, and an optional name key.
60
+ #
61
+ # @return [ Array<String> ] the names of the new search indexes.
62
+ def create_many(indexes)
63
+ spec = spec_with(indexes: indexes.map { |v| validate_search_index!(v) })
64
+ result = Operation::CreateSearchIndexes.new(spec).execute(next_primary, context: execution_context)
65
+ result.first['indexesCreated'].map { |idx| idx['name'] }
66
+ end
67
+
68
+ # Drop the search index with the given id, or name. One or the other must
69
+ # be specified, but not both.
70
+ #
71
+ # @param [ String ] id the id of the index to drop
72
+ # @param [ String ] name the name of the index to drop
73
+ #
74
+ # @return [ Mongo::Operation::Result | false ] the result of the
75
+ # operation, or false if the given index does not exist.
76
+ def drop_one(id: nil, name: nil)
77
+ validate_id_or_name!(id, name)
78
+
79
+ spec = spec_with(index_id: id, index_name: name)
80
+ op = Operation::DropSearchIndex.new(spec)
81
+
82
+ # per the spec:
83
+ # Drivers MUST suppress NamespaceNotFound errors for the
84
+ # ``dropSearchIndex`` helper. Drop operations should be idempotent.
85
+ do_drop(op, nil, execution_context)
86
+ end
87
+
88
+ # Iterate over the search indexes.
89
+ #
90
+ # @param [ Proc ] block if given, each search index will be yieleded to
91
+ # the block.
92
+ #
93
+ # @return [ self | Enumerator ] if a block is given, self is returned.
94
+ # Otherwise, an enumerator will be returned.
95
+ def each(&block)
96
+ @result ||= begin
97
+ spec = {}.tap do |s|
98
+ s[:id] = requested_index_id if requested_index_id
99
+ s[:name] = requested_index_name if requested_index_name
100
+ end
101
+
102
+ collection.aggregate(
103
+ [ { '$listSearchIndexes' => spec } ],
104
+ aggregate_options
105
+ )
106
+ end
107
+
108
+ return @result.to_enum unless block
109
+
110
+ @result.each(&block)
111
+ self
112
+ end
113
+
114
+ # Update the search index with the given id or name. One or the other
115
+ # must be provided, but not both.
116
+ #
117
+ # @param [ Hash ] definition the definition to replace the given search
118
+ # index with.
119
+ # @param [ nil | String ] id the id of the search index to update
120
+ # @param [ nil | String ] name the name of the search index to update
121
+ #
122
+ # @return [ Mongo::Operation::Result ] the result of the operation
123
+ def update_one(definition, id: nil, name: nil)
124
+ validate_id_or_name!(id, name)
125
+
126
+ spec = spec_with(index_id: id, index_name: name, index: definition)
127
+ Operation::UpdateSearchIndex.new(spec).execute(next_primary, context: execution_context)
128
+ end
129
+
130
+ # The following methods are to make the view act more like an array,
131
+ # without having to explicitly make it an array...
132
+
133
+ # Queries whether the search index enumerable is empty.
134
+ #
135
+ # @return [ true | false ] whether the enumerable is empty or not.
136
+ def empty?
137
+ count.zero?
138
+ end
139
+
140
+ private
141
+
142
+ # A helper method for building the specification document with certain
143
+ # values pre-populated.
144
+ #
145
+ # @param [ Hash ] extras the values to put into the specification
146
+ #
147
+ # @return [ Hash ] the specification document
148
+ def spec_with(extras)
149
+ {
150
+ coll_name: collection.name,
151
+ db_name: collection.database.name,
152
+ }.merge(extras)
153
+ end
154
+
155
+ # A helper method for retrieving the primary server from the cluster.
156
+ #
157
+ # @return [ Mongo::Server ] the server to use
158
+ def next_primary(ping = nil, session = nil)
159
+ collection.cluster.next_primary(ping, session)
160
+ end
161
+
162
+ # A helper method for constructing a new operation context for executing
163
+ # an operation.
164
+ #
165
+ # @return [ Mongo::Operation::Context ] the operation context
166
+ def execution_context
167
+ Operation::Context.new(client: collection.client)
168
+ end
169
+
170
+ # Validates the given id and name, ensuring that exactly one of them
171
+ # is non-nil.
172
+ #
173
+ # @param [ nil | String ] id the id to validate
174
+ # @param [ nil | String ] name the name to validate
175
+ #
176
+ # @raise [ ArgumentError ] if neither or both arguments are nil
177
+ def validate_id_or_name!(id, name)
178
+ return unless (id.nil? && name.nil?) || (!id.nil? && !name.nil?)
179
+
180
+ raise ArgumentError, 'exactly one of id or name must be specified'
181
+ end
182
+
183
+ # Validates the given search index document, ensuring that it has no
184
+ # extra keys, and that the name and definition are valid.
185
+ #
186
+ # @param [ Hash ] doc the document to validate
187
+ #
188
+ # @raise [ ArgumentError ] if the document is invalid.
189
+ def validate_search_index!(doc)
190
+ validate_search_index_keys!(doc.keys)
191
+ validate_search_index_name!(doc[:name] || doc['name'])
192
+ validate_search_index_definition!(doc[:definition] || doc['definition'])
193
+ doc
194
+ end
195
+
196
+ # Validates the keys of a search index document, ensuring that
197
+ # they are all valid.
198
+ #
199
+ # @param [ Array<String | Hash> ] keys the keys of a search index document
200
+ #
201
+ # @raise [ ArgumentError ] if the list contains any invalid keys
202
+ def validate_search_index_keys!(keys)
203
+ extras = keys - [ 'name', 'definition', :name, :definition ]
204
+
205
+ raise ArgumentError, "invalid keys in search index creation: #{extras.inspect}" if extras.any?
206
+ end
207
+
208
+ # Validates the name of a search index, ensuring that it is either a
209
+ # String or nil.
210
+ #
211
+ # @param [ nil | String ] name the name of a search index
212
+ #
213
+ # @raise [ ArgumentError ] if the name is not valid
214
+ def validate_search_index_name!(name)
215
+ return if name.nil? || name.is_a?(String)
216
+
217
+ raise ArgumentError, "search index name must be nil or a string (got #{name.inspect})"
218
+ end
219
+
220
+ # Validates the definition of a search index.
221
+ #
222
+ # @param [ Hash ] definition the definition of a search index
223
+ #
224
+ # @raise [ ArgumentError ] if the definition is not valid
225
+ def validate_search_index_definition!(definition)
226
+ return if definition.is_a?(Hash)
227
+
228
+ raise ArgumentError, "search index definition must be a Hash (got #{definition.inspect})"
229
+ end
230
+ end
231
+ end
232
+ end
data/lib/mongo/version.rb CHANGED
@@ -20,5 +20,5 @@ module Mongo
20
20
  # The current version of the driver.
21
21
  #
22
22
  # @since 2.0.0
23
- VERSION = '2.19.2'.freeze
23
+ VERSION = '2.19.3'.freeze
24
24
  end
data/lib/mongo.rb CHANGED
@@ -64,6 +64,7 @@ require 'mongo/client_encryption'
64
64
  require 'mongo/dbref'
65
65
  require 'mongo/grid'
66
66
  require 'mongo/index'
67
+ require 'mongo/search_index/view'
67
68
  require 'mongo/lint'
68
69
  require 'mongo/query_cache'
69
70
  require 'mongo/server'
@@ -7,11 +7,7 @@ describe 'Atlas connectivity' do
7
7
  let(:uri) { ENV['ATLAS_URI'] }
8
8
  let(:client) { Mongo::Client.new(uri) }
9
9
 
10
- before do
11
- if uri.nil?
12
- skip "ATLAS_URI not set in environment"
13
- end
14
- end
10
+ require_atlas
15
11
 
16
12
  describe 'connection to Atlas' do
17
13
  it 'runs ismaster successfully' do
@@ -7,11 +7,7 @@ describe 'Operations' do
7
7
  let(:uri) { ENV['ATLAS_URI'] }
8
8
  let(:client) { Mongo::Client.new(uri) }
9
9
 
10
- before do
11
- if uri.nil?
12
- skip "ATLAS_URI not set in environment"
13
- end
14
- end
10
+ require_atlas
15
11
 
16
12
  describe 'ping' do
17
13
  it 'works' do
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class SearchIndexHelper
6
+ attr_reader :client, :collection_name
7
+
8
+ def initialize(client)
9
+ @client = client
10
+
11
+ # https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id4
12
+ # "...each test uses a randomly generated collection name. Drivers may
13
+ # generate this collection name however they like, but a suggested
14
+ # implementation is a hex representation of an ObjectId..."
15
+ @collection_name = BSON::ObjectId.new.to_s
16
+ end
17
+
18
+ # `soft_create` means to create the collection object without forcing it to
19
+ # be created in the database.
20
+ def collection(soft_create: false)
21
+ @collection ||= client.database[collection_name].tap do |collection|
22
+ collection.create unless soft_create
23
+ end
24
+ end
25
+
26
+ # Wait for all of the indexes with the given names to be ready; then return
27
+ # the list of index definitions corresponding to those names.
28
+ def wait_for(*names, &condition)
29
+ timeboxed_wait do
30
+ result = collection.search_indexes
31
+ return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) }
32
+ end
33
+ end
34
+
35
+ # Wait until all of the indexes with the given names are absent from the
36
+ # search index list.
37
+ def wait_for_absense_of(*names)
38
+ names.each do |name|
39
+ timeboxed_wait do
40
+ break if collection.search_indexes(name: name).empty?
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def timeboxed_wait(step: 5, max: 300)
48
+ start = Mongo::Utils.monotonic_time
49
+
50
+ loop do
51
+ yield
52
+
53
+ sleep step
54
+ raise Timeout::Error, 'wait took too long' if Mongo::Utils.monotonic_time - start > max
55
+ end
56
+ end
57
+
58
+ # Returns true if the list of search indexes includes one with the given name,
59
+ # which is ready to be queried.
60
+ def ready?(list, name, &condition)
61
+ condition ||= ->(index) { index['queryable'] }
62
+ list.any? { |index| index['name'] == name && condition[index] }
63
+ end
64
+
65
+ def filter_results(result, names)
66
+ result.select { |index| names.include?(index['name']) }
67
+ end
68
+ end
69
+
70
+ describe 'Mongo::Collection#search_indexes prose tests' do
71
+ # https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id5
72
+ # "These tests must run against an Atlas cluster with a 7.0+ server."
73
+ require_atlas
74
+
75
+ let(:client) do
76
+ Mongo::Client.new(
77
+ ENV['ATLAS_URI'],
78
+ database: SpecConfig.instance.test_db,
79
+ ssl: true,
80
+ ssl_verify: true
81
+ )
82
+ end
83
+
84
+ let(:helper) { SearchIndexHelper.new(client) }
85
+
86
+ let(:name) { 'test-search-index' }
87
+ let(:definition) { { 'mappings' => { 'dynamic' => false } } }
88
+ let(:create_index) { helper.collection.search_indexes.create_one(definition, name: name) }
89
+
90
+ # Case 1: Driver can successfully create and list search indexes
91
+ context 'when creating and listing search indexes' do
92
+ let(:index) { helper.wait_for(name).first }
93
+
94
+ it 'succeeds' do
95
+ expect(create_index).to be == name
96
+ expect(index['latestDefinition']).to be == definition
97
+ end
98
+ end
99
+
100
+ # Case 2: Driver can successfully create multiple indexes in batch
101
+ context 'when creating multiple indexes in batch' do
102
+ let(:specs) do
103
+ [
104
+ { 'name' => 'test-search-index-1', 'definition' => definition },
105
+ { 'name' => 'test-search-index-2', 'definition' => definition }
106
+ ]
107
+ end
108
+
109
+ let(:names) { specs.map { |spec| spec['name'] } }
110
+ let(:create_indexes) { helper.collection.search_indexes.create_many(specs) }
111
+
112
+ let(:indexes) { helper.wait_for(*names) }
113
+
114
+ let(:index1) { indexes[0] }
115
+ let(:index2) { indexes[1] }
116
+
117
+ it 'succeeds' do
118
+ expect(create_indexes).to be == names
119
+ expect(index1['latestDefinition']).to be == specs[0]['definition']
120
+ expect(index2['latestDefinition']).to be == specs[1]['definition']
121
+ end
122
+ end
123
+
124
+ # Case 3: Driver can successfully drop search indexes
125
+ context 'when dropping search indexes' do
126
+ it 'succeeds' do
127
+ expect(create_index).to be == name
128
+ helper.wait_for(name)
129
+
130
+ helper.collection.search_indexes.drop_one(name: name)
131
+
132
+ expect { helper.wait_for_absense_of(name) }.not_to raise_error
133
+ end
134
+ end
135
+
136
+ # Case 4: Driver can update a search index
137
+ context 'when updating search indexes' do
138
+ let(:new_definition) { { 'mappings' => { 'dynamic' => true } } }
139
+
140
+ let(:index) do
141
+ helper
142
+ .wait_for(name) { |idx| idx['queryable'] && idx['status'] == 'READY' }
143
+ .first
144
+ end
145
+
146
+ # rubocop:disable RSpec/ExampleLength
147
+ it 'succeeds' do
148
+ expect(create_index).to be == name
149
+ helper.wait_for(name)
150
+
151
+ expect do
152
+ helper.collection.search_indexes.update_one(new_definition, name: name)
153
+ end.not_to raise_error
154
+
155
+ expect(index['latestDefinition']).to be == new_definition
156
+ end
157
+ # rubocop:enable RSpec/ExampleLength
158
+ end
159
+
160
+ # Case 5: dropSearchIndex suppresses namespace not found errors
161
+ context 'when dropping a non-existent search index' do
162
+ it 'ignores `namespace not found` errors' do
163
+ collection = helper.collection(soft_create: true)
164
+ expect { collection.search_indexes.drop_one(name: name) }
165
+ .not_to raise_error
166
+ end
167
+ end
168
+ end