1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
# frozen_string_literal: true
require 'spec_helper'
class SearchIndexHelper
attr_reader :client, :collection_name
def initialize(client)
@client = client
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#search-index-management-helpers
# "...each test uses a randomly generated collection name. Drivers may
# generate this collection name however they like, but a suggested
# implementation is a hex representation of an ObjectId..."
@collection_name = BSON::ObjectId.new.to_s
end
# `soft_create` means to create the collection object without forcing it to
# be created in the database.
def collection(soft_create: false)
@collection ||= client.database[collection_name].tap do |collection|
collection.create unless soft_create
end
end
# Wait for all of the indexes with the given names to be ready; then return
# the list of index definitions corresponding to those names.
def wait_for(*names, &condition)
timeboxed_wait do
result = collection.search_indexes
return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) }
end
end
# Wait until all of the indexes with the given names are absent from the
# search index list.
def wait_for_absense_of(*names)
names.each do |name|
timeboxed_wait do
break if collection.search_indexes(name: name).empty?
end
end
end
private
def timeboxed_wait(step: 5, max: 300)
start = Mongo::Utils.monotonic_time
loop do
yield
sleep step
raise Timeout::Error, 'wait took too long' if Mongo::Utils.monotonic_time - start > max
end
end
# Returns true if the list of search indexes includes one with the given name,
# which is ready to be queried.
def ready?(list, name, &condition)
condition ||= ->(index) { index['queryable'] }
list.any? { |index| index['name'] == name && condition[index] }
end
def filter_results(result, names)
result.select { |index| names.include?(index['name']) }
end
end
describe 'Mongo::Collection#search_indexes prose tests' do
# https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#setup
# "These tests must run against an Atlas cluster with a 7.0+ server."
require_atlas
let(:client) do
Mongo::Client.new(
ENV['ATLAS_URI'],
database: SpecConfig.instance.test_db,
ssl: true,
ssl_verify: true
)
end
let(:helper) { SearchIndexHelper.new(client) }
let(:name) { 'test-search-index' }
let(:definition) { { 'mappings' => { 'dynamic' => false } } }
let(:create_index) { helper.collection.search_indexes.create_one(definition, name: name) }
after do
client.close
end
# Case 1: Driver can successfully create and list search indexes
context 'when creating and listing search indexes' do
let(:index) { helper.wait_for(name).first }
it 'succeeds' do
expect(create_index).to be == name
expect(index['latestDefinition']).to be == definition
end
end
# Case 2: Driver can successfully create multiple indexes in batch
context 'when creating multiple indexes in batch' do
let(:specs) do
[
{ 'name' => 'test-search-index-1', 'definition' => definition },
{ 'name' => 'test-search-index-2', 'definition' => definition }
]
end
let(:names) { specs.map { |spec| spec['name'] } }
let(:create_indexes) { helper.collection.search_indexes.create_many(specs) }
let(:indexes) { helper.wait_for(*names) }
let(:index1) { indexes[0] }
let(:index2) { indexes[1] }
it 'succeeds' do
expect(create_indexes).to be == names
expect(index1['latestDefinition']).to be == specs[0]['definition']
expect(index2['latestDefinition']).to be == specs[1]['definition']
end
end
# Case 3: Driver can successfully drop search indexes
context 'when dropping search indexes' do
it 'succeeds' do
expect(create_index).to be == name
helper.wait_for(name)
helper.collection.search_indexes.drop_one(name: name)
expect { helper.wait_for_absense_of(name) }.not_to raise_error
end
end
# Case 4: Driver can update a search index
context 'when updating search indexes' do
let(:new_definition) { { 'mappings' => { 'dynamic' => true } } }
let(:index) do
helper
.wait_for(name) { |idx| idx['queryable'] && idx['status'] == 'READY' }
.first
end
it 'succeeds' do
expect(create_index).to be == name
helper.wait_for(name)
expect do
helper.collection.search_indexes.update_one(new_definition, name: name)
end.not_to raise_error
expect(index['latestDefinition']).to be == new_definition
end
end
# Case 5: dropSearchIndex suppresses namespace not found errors
context 'when dropping a non-existent search index' do
it 'ignores `namespace not found` errors' do
collection = helper.collection(soft_create: true)
expect { collection.search_indexes.drop_one(name: name) }
.not_to raise_error
end
end
end
|