Skip to content

Commit dc92a39

Browse files
authored
CSHARP-4685: Add e2e testing against Atlas to the search index management API (#1196)
1 parent 19c9f37 commit dc92a39

File tree

5 files changed

+277
-2
lines changed

5 files changed

+277
-2
lines changed

build.cake

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ Task("TestAtlasSearch")
246246
action: (BuildConfig buildConfig, Path testProject) =>
247247
RunTests(buildConfig, testProject, filter: "Category=\"AtlasSearch\""));
248248

249+
Task("TestAtlasSearchIndexHelpers")
250+
.IsDependentOn("Build")
251+
.DoesForEach(
252+
items: GetFiles("./**/MongoDB.Driver.Tests.csproj"),
253+
action: (BuildConfig buildConfig, Path testProject) =>
254+
RunTests(buildConfig, testProject, filter: "Category=\"AtlasSearchIndexHelpers\""));
255+
249256
Task("TestOcsp")
250257
.IsDependentOn("Build")
251258
.DoesForEach(

evergreen/evergreen.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,15 @@ functions:
708708
${PREPARE_SHELL}
709709
ATLAS_SEARCH="${ATLAS_SEARCH}" evergreen/run-atlas-search-test.sh
710710
711+
run-atlas-search-index-helpers-test:
712+
- command: shell.exec
713+
type: test
714+
params:
715+
working_dir: mongo-csharp-driver
716+
script: |
717+
${PREPARE_SHELL}
718+
MONGODB_URI="${MONGODB_URI}" evergreen/run-atlas-search-index-helpers-test.sh
719+
711720
run-ocsp-test:
712721
- command: shell.exec
713722
type: test
@@ -1294,6 +1303,10 @@ tasks:
12941303
commands:
12951304
- func: run-atlas-search-test
12961305

1306+
- name: atlas-search-index-helpers-test
1307+
commands:
1308+
- func: run-atlas-search-index-helpers-test
1309+
12971310
- name: test-serverless-net472
12981311
exec_timeout_secs: 2700 # 45 minutes: 15 for setup + 30 for tests
12991312
commands:
@@ -2026,6 +2039,50 @@ task_groups:
20262039
- packages-create
20272040
- packages-push
20282041

2042+
- name: atlas-search-index-helpers-task-group
2043+
setup_group_can_fail_task: true
2044+
setup_group_timeout_secs: 1800 # 30 minutes
2045+
setup_group:
2046+
- func: fetch-source
2047+
- func: prepare-resources
2048+
- func: fix-absolute-paths
2049+
- func: init-test-results
2050+
- func: make-files-executable
2051+
- command: shell.exec
2052+
params:
2053+
env:
2054+
DRIVERS_ATLAS_PUBLIC_API_KEY: "${DRIVERS_ATLAS_PUBLIC_API_KEY}"
2055+
DRIVERS_ATLAS_PRIVATE_API_KEY: "${DRIVERS_ATLAS_PRIVATE_API_KEY}"
2056+
DRIVERS_ATLAS_GROUP_ID: "${DRIVERS_ATLAS_GROUP_ID}"
2057+
DRIVERS_ATLAS_LAMBDA_USER: "${DRIVERS_ATLAS_LAMBDA_USER}"
2058+
DRIVERS_ATLAS_LAMBDA_PASSWORD: "${DRIVERS_ATLAS_LAMBDA_PASSWORD}"
2059+
LAMBDA_STACK_NAME: "${LAMBDA_STACK_NAME}"
2060+
add_expansions_to_env: true
2061+
shell: "bash"
2062+
script: |
2063+
${PREPARE_SHELL}
2064+
$DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh
2065+
- command: expansions.update
2066+
params:
2067+
file: atlas-expansion.yml
2068+
teardown_group:
2069+
- command: shell.exec
2070+
params:
2071+
env:
2072+
DRIVERS_ATLAS_PUBLIC_API_KEY: "${DRIVERS_ATLAS_PUBLIC_API_KEY}"
2073+
DRIVERS_ATLAS_PRIVATE_API_KEY: "${DRIVERS_ATLAS_PRIVATE_API_KEY}"
2074+
DRIVERS_ATLAS_GROUP_ID: "${DRIVERS_ATLAS_GROUP_ID}"
2075+
DRIVERS_ATLAS_LAMBDA_USER: "${DRIVERS_ATLAS_LAMBDA_USER}"
2076+
DRIVERS_ATLAS_LAMBDA_PASSWORD: "${DRIVERS_ATLAS_LAMBDA_PASSWORD}"
2077+
LAMBDA_STACK_NAME: "${LAMBDA_STACK_NAME}"
2078+
add_expansions_to_env: true
2079+
shell: "bash"
2080+
script: |
2081+
${PREPARE_SHELL}
2082+
$DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh
2083+
tasks:
2084+
- atlas-search-index-helpers-test
2085+
20292086
buildvariants:
20302087
- matrix_name: stable-api-tests
20312088
matrix_spec: { version: ["5.0", "6.0", "7.0", "rapid", "latest"], topology: "standalone", auth: "auth", ssl: "nossl", os: "windows-64" }
@@ -2244,6 +2301,13 @@ buildvariants:
22442301
tasks:
22452302
- name: atlas-search-test
22462303

2304+
- name: atlas-search-index-helpers-test
2305+
display_name: "Atlas Search Index Helpers Tests"
2306+
run_on:
2307+
- ubuntu1804-test
2308+
tasks:
2309+
- name: atlas-search-index-helpers-task-group
2310+
22472311
# CSFLE tests
22482312
- matrix_name: "csfle-with-mocked-kms-tests-windows"
22492313
matrix_spec: { os: "windows-64", ssl: "nossl", version: ["4.2", "4.4", "5.0", "6.0", "7.0", "rapid", "latest"], topology: ["replicaset"] }
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
set -o xtrace
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
# Environment variables produced as output
7+
# ATLAS_SEARCH_INDEX_HELPERS_TESTS_ENABLED Enable atlas search index helpers tests.
8+
9+
############################################
10+
# Main Program #
11+
############################################
12+
13+
echo "Running Atlas Search Index Helpers driver tests"
14+
15+
export ATLAS_SEARCH_INDEX_HELPERS_TESTS_ENABLED=true
16+
17+
./build.sh --target=TestAtlasSearchIndexHelpers

src/MongoDB.Driver.Core/Core/Operations/DropSearchIndexOperation.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ public BsonDocument Execute(IWriteBinding binding, CancellationToken cancellatio
7373
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork()))
7474
{
7575
var operation = CreateOperation();
76-
return operation.Execute(channelBinding, cancellationToken);
76+
77+
try
78+
{
79+
return operation.Execute(channelBinding, cancellationToken);
80+
}
81+
catch (MongoCommandException ex) when (ShouldIgnoreException(ex))
82+
{
83+
return ex.Result;
84+
}
7785
}
7886
}
7987

@@ -88,8 +96,20 @@ public async Task<BsonDocument> ExecuteAsync(IWriteBinding binding, Cancellation
8896
using (var channelBinding = new ChannelReadWriteBinding(channelSource.Server, channel, binding.Session.Fork()))
8997
{
9098
var operation = CreateOperation();
91-
return await operation.ExecuteAsync(channelBinding, cancellationToken).ConfigureAwait(false);
99+
100+
try
101+
{
102+
return await operation.ExecuteAsync(channelBinding, cancellationToken).ConfigureAwait(false);
103+
}
104+
catch (MongoCommandException ex) when (ShouldIgnoreException(ex))
105+
{
106+
return ex.Result;
107+
}
92108
}
93109
}
110+
111+
private bool ShouldIgnoreException(MongoCommandException ex) =>
112+
ex?.Code == (int)ServerErrorCode.NamespaceNotFound ||
113+
ex?.ErrorMessage == "ns not found";
94114
}
95115
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using System.Threading;
19+
using System.Threading.Tasks;
20+
using FluentAssertions;
21+
using MongoDB.Bson;
22+
using MongoDB.Driver.Core.TestHelpers.Logging;
23+
using MongoDB.Driver.TestHelpers;
24+
using MongoDB.TestHelpers.XunitExtensions;
25+
using Xunit;
26+
using Xunit.Abstractions;
27+
28+
namespace MongoDB.Driver.Tests.Search
29+
{
30+
[Category("AtlasSearchIndexHelpers")]
31+
public class AtlasSearchIndexManagementTests : LoggableTestClass
32+
{
33+
private const int Timeout = 5 * 60 * 1000;
34+
private const int IndexesPollPeriod = 5000;
35+
36+
private readonly IMongoCollection<BsonDocument> _collection;
37+
private readonly IMongoDatabase _database;
38+
private readonly DisposableMongoClient _disposableMongoClient;
39+
private readonly BsonDocument _indexDefinition = BsonDocument.Parse("{ mappings: { dynamic: false } }");
40+
41+
public AtlasSearchIndexManagementTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
42+
{
43+
RequireEnvironment.Check().EnvironmentVariable("ATLAS_SEARCH_INDEX_HELPERS_TESTS_ENABLED");
44+
45+
var atlasSearchUri = CoreTestConfiguration.ConnectionString.ToString();
46+
47+
_disposableMongoClient = new(new MongoClient(atlasSearchUri), CreateLogger<DisposableMongoClient>());
48+
_database = _disposableMongoClient.GetDatabase("dotnet-test");
49+
var collectionName = GetRandomName();
50+
51+
_database.CreateCollection(collectionName);
52+
_collection = _database.GetCollection<BsonDocument>(collectionName);
53+
}
54+
55+
protected override void DisposeInternal()
56+
{
57+
_collection.Database.DropCollection(_collection.CollectionNamespace.CollectionName);
58+
_disposableMongoClient.Dispose();
59+
}
60+
61+
[Fact(Timeout = Timeout)]
62+
public Task Case1_driver_should_successfully_create_and_list_search_indexes() =>
63+
CreateIndexAndValidate("test-search-index");
64+
65+
[Fact(Timeout = Timeout)]
66+
public async Task Case2_driver_should_successfully_create_multiple_indexes_in_batch()
67+
{
68+
var indexDefinition1 = new CreateSearchIndexModel("test-search-index-1", _indexDefinition);
69+
var indexDefinition2 = new CreateSearchIndexModel("test-search-index-2", _indexDefinition);
70+
71+
var indexNamesActual = await _collection.SearchIndexes.CreateManyAsync(new[] { indexDefinition1, indexDefinition2 });
72+
73+
indexNamesActual.Should().BeEquivalentTo(indexDefinition1.Name, indexDefinition2.Name);
74+
75+
var indexes = await GetIndexes(indexDefinition1.Name, indexDefinition2.Name);
76+
77+
indexes[0]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinition);
78+
indexes[1]["latestDefinition"].AsBsonDocument.Should().Be(_indexDefinition);
79+
}
80+
81+
[Fact(Timeout = Timeout)]
82+
public async Task Case3_driver_can_successfully_drop_search_indexes()
83+
{
84+
const string indexName = "test-search-index";
85+
86+
await CreateIndexAndValidate(indexName);
87+
88+
await _collection.SearchIndexes.DropOneAsync(indexName);
89+
90+
while (true)
91+
{
92+
var cursor = await _collection.SearchIndexes.ListAsync();
93+
var indexes = await cursor.ToListAsync();
94+
if (indexes.Count == 0)
95+
{
96+
return;
97+
}
98+
99+
Thread.Sleep(IndexesPollPeriod);
100+
}
101+
}
102+
103+
[Fact(Timeout = Timeout)]
104+
public async Task Case4_driver_can_update_a_search_index()
105+
{
106+
const string indexName = "test-search-index";
107+
var indexNewDefinition = BsonDocument.Parse("{ mappings: { dynamic: true }}");
108+
109+
await CreateIndexAndValidate(indexName);
110+
111+
await _collection.SearchIndexes.UpdateAsync(indexName, indexNewDefinition);
112+
113+
var updatedIndex = await GetIndexes(indexName);
114+
updatedIndex[0]["latestDefinition"].AsBsonDocument.Should().Be(indexNewDefinition);
115+
}
116+
117+
[Fact(Timeout = Timeout)]
118+
public async Task Case5_dropSearchIndex_suppresses_namespace_not_found_errors()
119+
{
120+
var collection = _database.GetCollection<BsonDocument>("non_existent_collection");
121+
await collection.SearchIndexes.DropOneAsync("non_existing_index");
122+
}
123+
124+
private async Task<BsonDocument> CreateIndexAndValidate(string indexName)
125+
{
126+
var indexNameActual = await _collection.SearchIndexes.CreateOneAsync(_indexDefinition, indexName);
127+
indexNameActual.Should().Be(indexName);
128+
129+
var result = await GetIndexes(indexName);
130+
131+
return result[0];
132+
}
133+
134+
private async Task<BsonDocument[]> GetIndexes(params string[] indexNames)
135+
{
136+
while (true)
137+
{
138+
var cursor = await _collection.SearchIndexes.ListAsync();
139+
var indexes = await cursor.ToListAsync();
140+
141+
var indexesFiltered = indexes
142+
.Where(i => indexNames.Contains(TryGetValue<string>(i, "name")) && TryGetValue<bool>(i, "queryable"))
143+
.ToArray();
144+
145+
if (indexesFiltered.Length == indexNames.Length)
146+
{
147+
return indexesFiltered;
148+
}
149+
150+
Thread.Sleep(IndexesPollPeriod);
151+
}
152+
}
153+
154+
private static string GetRandomName() => $"test_{Guid.NewGuid():N}";
155+
156+
private static T TryGetValue<T>(BsonDocument document, string name)
157+
{
158+
if (!document.TryGetValue(name, out var value))
159+
{
160+
return default;
161+
}
162+
163+
var result = BsonTypeMapper.MapToDotNetValue(value);
164+
return (T)result;
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)