Skip to content

Commit 7b0c2bc

Browse files
RUST-2077 Convert all CSFLE tests to unified format (#1491)
1 parent f012a33 commit 7b0c2bc

File tree

404 files changed

+64364
-67243
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

404 files changed

+64364
-67243
lines changed

src/client/csfle/options.rs

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -141,67 +141,6 @@ impl KmsProviders {
141141
pub(crate) fn credentials(&self) -> &HashMap<KmsProvider, Document> {
142142
&self.credentials
143143
}
144-
145-
#[cfg(test)]
146-
pub(crate) fn set_test_options(&mut self) {
147-
use mongocrypt::ctx::KmsProviderType;
148-
149-
use crate::{
150-
bson::doc,
151-
test::csfle::{ALL_KMS_PROVIDERS, AWS_KMS},
152-
};
153-
154-
let all_kms_providers = ALL_KMS_PROVIDERS.clone();
155-
for (provider, test_credentials, tls_options) in all_kms_providers {
156-
if self.credentials.contains_key(&provider)
157-
&& !matches!(provider.provider_type(), KmsProviderType::Local)
158-
{
159-
self.set(provider, test_credentials, tls_options);
160-
}
161-
}
162-
163-
let aws_temp_provider = KmsProvider::other("awsTemporary".to_string());
164-
if self.credentials.contains_key(&aws_temp_provider) {
165-
let aws_credentials = doc! {
166-
"accessKeyId": std::env::var("CSFLE_AWS_TEMP_ACCESS_KEY_ID").unwrap(),
167-
"secretAccessKey": std::env::var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY").unwrap(),
168-
"sessionToken": std::env::var("CSFLE_AWS_TEMP_SESSION_TOKEN").unwrap()
169-
};
170-
self.set(KmsProvider::aws(), aws_credentials, AWS_KMS.clone().2);
171-
self.clear(&aws_temp_provider);
172-
}
173-
174-
let aws_temp_no_session_token_provider = KmsProvider::other("awsTemporaryNoSessionToken");
175-
if self
176-
.credentials
177-
.contains_key(&aws_temp_no_session_token_provider)
178-
{
179-
let aws_credentials = doc! {
180-
"accessKeyId": std::env::var("CSFLE_AWS_TEMP_ACCESS_KEY_ID").unwrap(),
181-
"secretAccessKey": std::env::var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY").unwrap(),
182-
};
183-
self.set(KmsProvider::aws(), aws_credentials, AWS_KMS.clone().2);
184-
self.clear(&aws_temp_no_session_token_provider);
185-
}
186-
}
187-
188-
#[cfg(test)]
189-
pub(crate) fn set(&mut self, provider: KmsProvider, creds: Document, tls: Option<TlsOptions>) {
190-
self.credentials.insert(provider.clone(), creds);
191-
if let Some(tls) = tls {
192-
self.tls_options
193-
.get_or_insert_with(KmsProvidersTlsOptions::new)
194-
.insert(provider, tls);
195-
}
196-
}
197-
198-
#[cfg(test)]
199-
pub(crate) fn clear(&mut self, provider: &KmsProvider) {
200-
self.credentials.remove(provider);
201-
if let Some(tls_opts) = &mut self.tls_options {
202-
tls_opts.remove(provider);
203-
}
204-
}
205144
}
206145

207146
impl AutoEncryptionOptions {

src/client/options.rs

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,54 +1282,6 @@ impl ClientOptions {
12821282
Ok(())
12831283
}
12841284

1285-
/// Applies the options in other to these options if a value is not already present
1286-
#[cfg(test)]
1287-
pub(crate) fn merge(&mut self, other: ClientOptions) {
1288-
if self.hosts.is_empty() {
1289-
self.hosts = other.hosts;
1290-
}
1291-
1292-
#[cfg(any(
1293-
feature = "zstd-compression",
1294-
feature = "zlib-compression",
1295-
feature = "snappy-compression"
1296-
))]
1297-
merge_options!(other, self, [compressors]);
1298-
1299-
merge_options!(
1300-
other,
1301-
self,
1302-
[
1303-
app_name,
1304-
cmap_event_handler,
1305-
command_event_handler,
1306-
connect_timeout,
1307-
credential,
1308-
direct_connection,
1309-
driver_info,
1310-
heartbeat_freq,
1311-
load_balanced,
1312-
local_threshold,
1313-
max_idle_time,
1314-
max_pool_size,
1315-
min_pool_size,
1316-
read_concern,
1317-
repl_set_name,
1318-
retry_reads,
1319-
retry_writes,
1320-
selection_criteria,
1321-
server_api,
1322-
server_selection_timeout,
1323-
socket_timeout,
1324-
test_options,
1325-
tls,
1326-
write_concern,
1327-
original_srv_info,
1328-
original_uri
1329-
]
1330-
);
1331-
}
1332-
13331285
#[cfg(test)]
13341286
pub(crate) fn test_options_mut(&mut self) -> &mut TestOptions {
13351287
self.test_options.get_or_insert_with(Default::default)

src/cmap/test/file.rs

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,80 @@ use crate::{
88
error::Result,
99
event::cmap::CmapEvent,
1010
serde_util,
11-
test::{util::fail_point::FailPoint, RunOn},
11+
test::{
12+
get_topology,
13+
log_uncaptured,
14+
server_version_matches,
15+
util::fail_point::FailPoint,
16+
Serverless,
17+
Topology,
18+
},
1219
};
1320

1421
#[derive(Debug, Deserialize)]
1522
#[serde(rename_all = "camelCase", deny_unknown_fields)]
16-
pub struct TestFile {
23+
pub(super) struct TestFile {
1724
#[serde(rename = "version")]
1825
_version: u8, // can ignore this field as there's only one version
1926
#[serde(rename = "style")]
2027
_style: TestStyle, // we use the presence of fail_point / run_on to determine this
21-
pub description: String,
22-
pub(crate) pool_options: Option<ConnectionPoolOptions>,
23-
pub operations: Vec<ThreadedOperation>,
24-
pub error: Option<Error>,
25-
pub(crate) events: Vec<CmapEvent>,
28+
pub(super) description: String,
29+
pub(super) pool_options: Option<ConnectionPoolOptions>,
30+
pub(super) operations: Vec<ThreadedOperation>,
31+
pub(super) error: Option<Error>,
32+
pub(super) events: Vec<CmapEvent>,
2633
#[serde(default)]
27-
pub ignore: Vec<String>,
28-
pub fail_point: Option<FailPoint>,
29-
pub(crate) run_on: Option<Vec<RunOn>>,
34+
pub(super) ignore: Vec<String>,
35+
pub(super) fail_point: Option<FailPoint>,
36+
pub(super) run_on: Option<Vec<RunOn>>,
37+
}
38+
39+
#[derive(Debug, Deserialize)]
40+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
41+
pub(super) struct RunOn {
42+
pub(super) min_server_version: Option<String>,
43+
pub(super) max_server_version: Option<String>,
44+
pub(super) topology: Option<Vec<Topology>>,
45+
pub(super) serverless: Option<Serverless>,
46+
}
47+
48+
impl RunOn {
49+
pub(super) async fn can_run_on(&self) -> bool {
50+
if let Some(ref min_version) = self.min_server_version {
51+
if !server_version_matches(&format!(">= {min_version}")).await {
52+
log_uncaptured(format!(
53+
"runOn mismatch: required server version >= {min_version}",
54+
));
55+
return false;
56+
}
57+
}
58+
if let Some(ref max_version) = self.max_server_version {
59+
if !server_version_matches(&format!("<= {max_version}")).await {
60+
log_uncaptured(format!(
61+
"runOn mismatch: required server version <= {max_version}",
62+
));
63+
return false;
64+
}
65+
}
66+
if let Some(ref topology) = self.topology {
67+
let actual_topology = get_topology().await;
68+
if !topology.contains(actual_topology) {
69+
log_uncaptured(format!(
70+
"runOn mismatch: required topology in {topology:?}, got {actual_topology:?}"
71+
));
72+
return false;
73+
}
74+
}
75+
if let Some(ref serverless) = self.serverless {
76+
if !serverless.can_run() {
77+
log_uncaptured(format!(
78+
"runOn mismatch: required serverless {serverless:?}"
79+
));
80+
return false;
81+
}
82+
}
83+
true
84+
}
3085
}
3186

3287
#[derive(Debug, Deserialize)]

src/db/options.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ pub struct CreateCollectionOptions {
8484
/// determine the contents of the view.
8585
pub pipeline: Option<Vec<Document>>,
8686

87-
/// The default collation for the collection or view.
87+
/// The default collation for the collection or view.
8888
pub collation: Option<Collation>,
8989

90-
/// The write concern for the operation.
90+
/// The write concern for the operation.
9191
#[serde(skip_serializing_if = "write_concern_is_empty")]
9292
pub write_concern: Option<WriteConcern>,
9393

@@ -155,6 +155,7 @@ pub enum ValidationAction {
155155

156156
/// Specifies options for a clustered collection. Some fields have required values; the `Default`
157157
/// impl uses those values.
158+
#[skip_serializing_none]
158159
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
159160
#[serde(rename_all = "camelCase")]
160161
#[non_exhaustive]
@@ -169,7 +170,6 @@ pub struct ClusteredIndex {
169170
pub name: Option<String>,
170171

171172
/// Optional; currently must be `2` if provided.
172-
#[serde(skip_serializing_if = "Option::is_none")]
173173
pub v: Option<i32>,
174174
}
175175

src/test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub(crate) mod util;
3333
#[cfg(feature = "in-use-encryption")]
3434
pub(crate) use self::csfle_skip_local as csfle;
3535
pub(crate) use self::{
36-
spec::{run_spec_test, RunOn, Serverless, Topology},
36+
spec::{run_spec_test, Serverless, Topology},
3737
util::{
3838
assert_matches,
3939
eq_matches,
@@ -205,7 +205,7 @@ pub(crate) async fn get_max_message_size_bytes() -> usize {
205205
.unwrap()
206206
}
207207

208-
async fn get_topology() -> &'static Topology {
208+
pub(crate) async fn get_topology() -> &'static Topology {
209209
static TOPOLOGY: OnceCell<Topology> = OnceCell::const_new();
210210
TOPOLOGY
211211
.get_or_init(|| async {

src/test/csfle/spec.rs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,21 @@
1-
use crate::test::spec::{unified_runner::run_unified_tests, v2_runner::run_v2_tests};
1+
use crate::test::spec::unified_runner::run_unified_tests;
22

33
#[tokio::test(flavor = "multi_thread")]
44
async fn run_unified() {
55
let mut skipped_tests = vec![];
66
if cfg!(not(feature = "openssl-tls")) {
7-
skipped_tests.push("create datakey with KMIP KMS provider");
8-
skipped_tests.push("create datakey with KMIP delegated KMS provider");
9-
skipped_tests.push("create datakey with named KMIP KMS provider");
7+
skipped_tests.extend_from_slice(&[
8+
"Insert a document with auto encryption using KMIP KMS provider",
9+
"create datakey with KMIP KMS provider",
10+
"create datakey with KMIP delegated KMS provider",
11+
"create datakey with named KMIP KMS provider",
12+
"Insert a document with auto encryption using KMIP delegated KMS provider",
13+
]);
1014
}
1115

1216
run_unified_tests(&["client-side-encryption", "unified"])
1317
.skip_tests(&skipped_tests)
14-
.await;
15-
}
16-
17-
#[tokio::test(flavor = "multi_thread")]
18-
async fn run_legacy() {
19-
let mut skipped_files = vec![
20-
// TODO RUST-528: unskip this file
21-
"timeoutMS.json",
22-
// These files have been migrated to unified tests.
23-
// TODO DRIVERS-3178 remove these once the files are gone.
24-
"fle2v2-BypassQueryAnalysis.json",
25-
"fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json",
26-
"localSchema.json",
27-
"maxWireVersion.json",
28-
];
29-
if cfg!(not(feature = "openssl-tls")) {
30-
skipped_files.push("kmipKMS.json");
31-
}
32-
33-
run_v2_tests(&["client-side-encryption", "legacy"])
34-
.skip_files(&skipped_files)
18+
// TODO RUST-582: unskip this test
19+
.skip_files(&["timeoutMS.json"])
3520
.await;
3621
}

src/test/spec.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ mod sessions;
2222
mod trace;
2323
mod transactions;
2424
pub(crate) mod unified_runner;
25-
pub(crate) mod v2_runner;
2625
mod versioned_api;
2726
mod write_error;
2827

@@ -38,8 +37,7 @@ use serde::{de::DeserializeOwned, Deserialize};
3837

3938
pub(crate) use self::{
4039
oidc_skip_ci as oidc,
41-
unified_runner::{merge_uri_options, ExpectedEventType, Topology},
42-
v2_runner::{operation::Operation, test_file::RunOn},
40+
unified_runner::{ExpectedEventType, Topology},
4341
};
4442
use crate::bson::Bson;
4543

src/test/spec/json/client-side-encryption/README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,8 @@ Using `client_encrypted` perform the following operations:
619619
`{ "_id": "encryption_exceeds_16mib", "unencrypted": < the string "a" repeated (16777216 - 2000) times > }` into
620620
`coll`.
621621

622-
Expect this to fail since encryption results in a document exceeding the `maxBsonObjectSize` limit.
622+
Expect this to fail indicating the document exceeded the `maxBsonObjectSize` limit. If the write is sent to the
623+
server (i.e. does not fail due to a driver-side check), expect a server error with code 2 or 10334.
623624

624625
7. If using MongoDB 8.0+, use MongoClient.bulkWrite to insert the following into `coll2`:
625626

@@ -3787,6 +3788,57 @@ class AutoEncryptionOpts {
37873788

37883789
Assert that an error is thrown.
37893790

3791+
#### Case 4: ClientEncryption with `credentialProviders` and valid environment variables.
3792+
3793+
Ensure a valid `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are present in the environment.
3794+
3795+
Create a MongoClient named `setupClient`.
3796+
3797+
Create a [ClientEncryption](../client-side-encryption.md#clientencryption) object with the following options:
3798+
3799+
```typescript
3800+
class ClientEncryptionOpts {
3801+
keyVaultClient: <setupClient>,
3802+
keyVaultNamespace: "keyvault.datakeys",
3803+
kmsProviders: { "aws": {} },
3804+
credentialProviders: { "aws": <object/function that returns valid credentials from the secrets manager> }
3805+
}
3806+
```
3807+
3808+
Use the client encryption to create a datakey using the "aws" KMS provider. This should successfully load and use the
3809+
AWS credentials that were provided by the secrets manager for the remote provider. Assert the datakey was created and
3810+
that the custom credential provider was called at least once.
3811+
3812+
An example of this in Node.js:
3813+
3814+
```typescript
3815+
import { ClientEncryption, MongoClient } from 'mongodb';
3816+
3817+
let calledCount = 0;
3818+
const masterKey = {
3819+
region: '<aws region>',
3820+
key: '<key for arn>'
3821+
};
3822+
const keyVaultClient = new MongoClient(process.env.MONGODB_URI);
3823+
const options = {
3824+
keyVaultNamespace: 'keyvault.datakeys',
3825+
kmsProviders: { aws: {} },
3826+
credentialProviders: {
3827+
aws: async () => {
3828+
calledCount++;
3829+
return {
3830+
accessKeyId: process.env.FLE_AWS_KEY,
3831+
secretAccessKey: process.env.FLE_AWS_SECRET
3832+
};
3833+
}
3834+
}
3835+
};
3836+
const clientEncryption = new ClientEncryption(keyVaultClient, options);
3837+
const dk = await clientEncryption.createDataKey('aws', { masterKey });
3838+
expect(dk).to.be.a(Binary);
3839+
expect(calledCount).to.be.greaterThan(0);
3840+
```
3841+
37903842
### 27. Text Explicit Encryption
37913843

37923844
The Text Explicit Encryption tests utilize Queryable Encryption (QE) range protocol V2 and require MongoDB server 8.2.0+

0 commit comments

Comments
 (0)