Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions internal/integration/mtest/mongotest.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,12 +797,6 @@ func verifyRunOnBlockConstraint(rob RunOnBlock) error {
return err
}

// TODO(GODRIVER-3486): Once auto encryption is supported by the unified test
// format,this check should be removed.
if rob.CSFLEEnabled() && rob.CSFLE.Options != nil {
return fmt.Errorf("Auto encryption required (GODRIVER-3486)")
}

if rob.CSFLEEnabled() && !IsCSFLEEnabled() {
return fmt.Errorf("runOnBlock requires CSFLE to be enabled. Build with the cse tag to enable")
} else if !rob.CSFLEEnabled() && IsCSFLEEnabled() {
Expand Down
98 changes: 93 additions & 5 deletions internal/integration/unified/client_entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ package unified

import (
"context"
"encoding/base64"
"fmt"
"os"
"strings"
"sync"
"sync/atomic"
Expand All @@ -32,11 +34,19 @@ import (
// exceed the default truncation length.
const defaultMaxDocumentLen = 10_000

// Security-sensitive commands that should be ignored in command monitoring by default.
var securitySensitiveCommands = []string{
"authenticate", "saslStart", "saslContinue", "getnonce",
"createUser", "updateUser", "copydbgetnonce", "copydbsaslstart", "copydb",
}
var (
// Security-sensitive commands that should be ignored in command monitoring by default.
securitySensitiveCommands = []string{
"authenticate", "saslStart", "saslContinue", "getnonce",
"createUser", "updateUser", "copydbgetnonce", "copydbsaslstart", "copydb",
}

awsAccessKeyID = os.Getenv("FLE_AWS_KEY")
awsSecretAccessKey = os.Getenv("FLE_AWS_SECRET")
azureTenantID = os.Getenv("FLE_AZURE_TENANTID")
azureClientID = os.Getenv("FLE_AZURE_CLIENTID")
azureClientSecret = os.Getenv("FLE_AZURE_CLIENTSECRET")
)

// clientEntity is a wrapper for a mongo.Client object that also holds additional information required during test
// execution.
Expand Down Expand Up @@ -217,6 +227,13 @@ func newClientEntity(ctx context.Context, em *EntityMap, entityOptions *entityOp
} else {
integtest.AddTestServerAPIVersion(clientOpts)
}
if entityOptions.AutoEncryptOpts != nil {
aeo, err := createAutoEncryptionOptions(entityOptions.AutoEncryptOpts)
if err != nil {
return nil, fmt.Errorf("error parsing auto encryption options: %w", err)
}
clientOpts.SetAutoEncryptionOptions(aeo)
}
for _, cmd := range entityOptions.IgnoredCommands {
entity.ignoredCommands[cmd] = struct{}{}
}
Expand Down Expand Up @@ -251,6 +268,77 @@ func getURIForClient(opts *entityOptions) string {
}
}

func createAutoEncryptionOptions(opts bson.Raw) (*options.AutoEncryptionOptions, error) {
aeo := options.AutoEncryption()
var kvnsFound bool
elems, err := opts.Elements()
if err != nil {
return nil, err
}

for _, elem := range elems {
name := elem.Key()
opt := elem.Value()

switch name {
case "kmsProviders":
providers := make(map[string]map[string]any)
elems, err := opt.Document().Elements()
if err != nil {
return nil, err
}
for _, elem := range elems {
provider := elem.Key()
providerOpt := elem.Value()
switch provider {
case "aws":
providers["aws"] = map[string]any{
"accessKeyId": awsAccessKeyID,
"secretAccessKey": awsSecretAccessKey,
Comment on lines +296 to +297
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded AWS credentials from environment variables may fail if the credentials are not set or if placeholder documents are used in tests. The existing pattern in entity.go uses the getKmsCredential helper function to handle both string values and placeholder documents (e.g., {"$$placeholder": 1}). Consider refactoring to use getKmsCredential for consistency and to properly support test specifications that use placeholders.

Suggested change
"accessKeyId": awsAccessKeyID,
"secretAccessKey": awsSecretAccessKey,
"accessKeyId": getKmsCredential("AWS_ACCESS_KEY_ID", "accessKeyId"),
"secretAccessKey": getKmsCredential("AWS_SECRET_ACCESS_KEY", "secretAccessKey"),

Copilot uses AI. Check for mistakes.
}
case "azure":
providers["azure"] = map[string]any{
"tenantId": azureTenantID,
"clientId": azureClientID,
"clientSecret": azureClientSecret,
}
case "local":
str := providerOpt.Document().Lookup("key").StringValue()
key, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}
providers["local"] = map[string]any{
"key": key,
}
default:
return nil, fmt.Errorf("unrecognized KMS provider: %v", provider)
}
}
aeo.SetKmsProviders(providers)
case "schemaMap":
var schemaMap map[string]any
err := bson.Unmarshal(opt.Document(), &schemaMap)
if err != nil {
return nil, err
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling for BSON unmarshalling is inconsistent with other error messages in this function. Consider wrapping the error with more context, similar to line 297: return nil, fmt.Errorf("error creating schema map: %w", err) for better debugging.

Suggested change
return nil, err
return nil, fmt.Errorf("error creating schema map: %w", err)

Copilot uses AI. Check for mistakes.
}
aeo.SetSchemaMap(schemaMap)
case "keyVaultNamespace":
kvnsFound = true
aeo.SetKeyVaultNamespace(opt.StringValue())
case "bypassQueryAnalysis":
aeo.SetBypassQueryAnalysis(opt.Boolean())
default:
return nil, fmt.Errorf("unrecognized option: %v", name)
}
}
if !kvnsFound {
aeo.SetKeyVaultNamespace("keyvault.datakeys")
}

return aeo, nil
}

// disconnect disconnects the client associated with this entity. It is an
// idempotent operation, unlike the mongo client's disconnect method. This
// property will help avoid unnecessary errors when calling disconnect on a
Expand Down
24 changes: 14 additions & 10 deletions internal/integration/unified/collection_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type collectionData struct {
}

type createOptions struct {
Capped *bool `bson:"capped"`
SizeInBytes *int64 `bson:"size"`
Capped *bool `bson:"capped"`
SizeInBytes *int64 `bson:"size"`
EncryptedFields any `bson:"encryptedFields"`
}

// createCollection configures the collection represented by the receiver using the internal client. This function
Expand All @@ -49,14 +50,15 @@ func (c *collectionData) createCollection(ctx context.Context) error {
if c.Options.SizeInBytes != nil {
createOpts = createOpts.SetSizeInBytes(*c.Options.SizeInBytes)
}
if c.Options.EncryptedFields != nil {
createOpts = createOpts.SetEncryptedFields(c.Options.EncryptedFields)
}

if err := db.CreateCollection(ctx, c.CollectionName, createOpts); err != nil {
return fmt.Errorf("error creating collection: %w", err)
}
}

// If neither documents nor options are provided, still create the collection with write concern "majority".
if len(c.Documents) == 0 && c.Options == nil {
} else {
// If options are provided, still create the collection with write concern "majority".
// The write concern has to be manually specified in the command document because RunCommand does not honor
// the database's write concern.
create := bson.D{
Expand All @@ -68,13 +70,15 @@ func (c *collectionData) createCollection(ctx context.Context) error {
if err := db.RunCommand(ctx, create).Err(); err != nil {
return fmt.Errorf("error creating collection: %w", err)
}
return nil
}

docs := bsonutil.RawToInterfaces(c.Documents...)
if _, err := coll.InsertMany(ctx, docs); err != nil {
return fmt.Errorf("error inserting data: %w", err)
if len(c.Documents) != 0 {
docs := bsonutil.RawToInterfaces(c.Documents...)
if _, err := coll.InsertMany(ctx, docs); err != nil {
return fmt.Errorf("error inserting data: %w", err)
}
}

return nil
}

Expand Down
1 change: 1 addition & 0 deletions internal/integration/unified/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type entityOptions struct {
ID string `bson:"id"`

// Options for client entities.
AutoEncryptOpts bson.Raw `bson:"autoEncryptOpts"`
URIOptions bson.M `bson:"uriOptions"`
UseMultipleMongoses *bool `bson:"useMultipleMongoses"`
ObserveEvents []string `bson:"observeEvents"`
Expand Down
2 changes: 1 addition & 1 deletion internal/integration/unified/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func (op *operation) run(ctx context.Context, loopDone <-chan struct{}) (*operat
return executeDecrypt(ctx, op)

// Unsupported operations
case "count", "listIndexNames":
case "count", "listIndexNames", "mapReduce":
return nil, newSkipTestError(fmt.Sprintf("the %q operation is not supported", op.Name))
default:
return nil, fmt.Errorf("unrecognized entity operation %q", op.Name)
Expand Down
13 changes: 0 additions & 13 deletions internal/spectest/skip.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,19 +392,6 @@ var skipTests = map[string][]string{
"TestClientSideEncryptionSpec/timeoutMS.json/timeoutMS_applied_to_listCollections_to_get_collection_schema",
},

// TODO(GODRIVER-3486): Support auto encryption in unified tests.
"Support auto encryption in unified tests (GODRIVER-3486)": {
"TestUnifiedSpec/unified-test-format/tests/valid-pass/poc-queryable-encryption.json/insert,_replace,_and_find_with_queryable_encryption",
},

// TODO(DRIVERS-3106): Support auto encryption in unified tests.
"Support auto encryption in unified tests (DRIVERS-3106)": {
"TestUnifiedSpec/client-side-encryption/tests/unified/localSchema.json/A_local_schema_should_override",
"TestUnifiedSpec/client-side-encryption/tests/unified/localSchema.json/A_local_schema_with_no_encryption_is_an_error",
"TestUnifiedSpec/client-side-encryption/tests/unified/fle2v2-BypassQueryAnalysis.json/BypassQueryAnalysis_decrypts",
"TestUnifiedSpec/client-side-encryption/tests/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json/encryptedFieldsMap_is_preferred_over_remote_encryptedFields",
},

// TODO(GODRIVER-3076): CSFLE/QE Support for more than 1 KMS provider per
// type.
"Support multiple KMS providers per type (GODRIVER-3076)": {
Expand Down
Loading