Skip to content

Commit

Permalink
Use testing library for client side encryption spec tests.
Browse files Browse the repository at this point in the history
GODRIVER-1270

Change-Id: I9fcc5e280e11ef8a4fa1b4923b90807646b00df7
  • Loading branch information
Divjot Arora committed Sep 14, 2019
1 parent 8ec63ac commit ffed2da
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 1,209 deletions.
1,130 changes: 0 additions & 1,130 deletions mongo/client_side_encryption_spec_test.go

This file was deleted.

26 changes: 26 additions & 0 deletions mongo/integration/client_side_encryption_spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

// +build cse

package integration

import (
"path"
"testing"
)

const (
encryptionSpecName = "client-side-encryption"
)

func TestClientSideEncryption(t *testing.T) {
for _, fileName := range jsonFilesInDir(t, path.Join(dataPath, encryptionSpecName)) {
t.Run(fileName, func(t *testing.T) {
runSpecTestFile(t, encryptionSpecName, fileName)
})
}
}
85 changes: 80 additions & 5 deletions mongo/integration/cmd_monitoring_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package integration

import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"go.mongodb.org/mongo-driver/x/bsonx"
Expand Down Expand Up @@ -54,6 +55,13 @@ func compareValues(mt *mtest.T, key string, expected, actual bson.RawValue) {
}
case bson.TypeEmbeddedDocument:
e := expected.Document()
if typeVal, err := e.LookupErr("$$type"); err == nil {
// $$type represents a type assertion
// for example {field: {$$type: "binData"}} should assert that "field" is an element with a binary value
assertType(mt, actual.Type, typeVal.StringValue())
return
}

a := actual.Document()
compareDocs(mt, e, a)
case bson.TypeArray:
Expand All @@ -66,6 +74,61 @@ func compareValues(mt *mtest.T, key string, expected, actual bson.RawValue) {
}
}

// helper for $$type assertions
func assertType(mt *mtest.T, actual bsontype.Type, typeStr string) {
mt.Helper()

var expected bsontype.Type
switch typeStr {
case "double":
expected = bsontype.Double
case "string":
expected = bsontype.String
case "object":
expected = bsontype.EmbeddedDocument
case "array":
expected = bsontype.Array
case "binData":
expected = bsontype.Binary
case "undefined":
expected = bsontype.Undefined
case "objectId":
expected = bsontype.ObjectID
case "boolean":
expected = bsontype.Boolean
case "date":
expected = bsontype.DateTime
case "null":
expected = bsontype.Null
case "regex":
expected = bsontype.Regex
case "dbPointer":
expected = bsontype.DBPointer
case "javascript":
expected = bsontype.JavaScript
case "symbol":
expected = bsontype.Symbol
case "javascriptWithScope":
expected = bsontype.CodeWithScope
case "int":
expected = bsontype.Int32
case "timestamp":
expected = bsontype.Timestamp
case "long":
expected = bsontype.Int64
case "decimal":
expected = bsontype.Decimal128
case "minKey":
expected = bsontype.MinKey
case "maxKey":
expected = bsontype.MaxKey
default:
mt.Fatalf("unrecognized type string: %v", typeStr)
}

assert.Equal(mt, expected, actual, "BSON type mismatch; expected %v, got %v", expected, actual)
}

// compare expected and actual BSON documents. comparison succeeds if actual contains each element in expected.
func compareDocs(mt *mtest.T, expected, actual bson.Raw) {
mt.Helper()
Expand All @@ -80,6 +143,12 @@ func compareDocs(mt *mtest.T, expected, actual bson.Raw) {

eVal := e.Value()
if doc, ok := eVal.DocumentOK(); ok {
// special $$type assertion
if typeVal, err := doc.LookupErr("$$type"); err == nil {
assertType(mt, aVal.Type, typeVal.StringValue())
continue
}

// nested doc
compareDocs(mt, doc, aVal.Document())
continue
Expand Down Expand Up @@ -124,8 +193,9 @@ func checkExpectations(mt *mtest.T, expectations []*expectation, id0 bsonx.Doc,
assert.Equal(mt, bson.RawValue{}, actualVal, "expected value for key %s to be nil but got %v", key, actualVal)
continue
}
if key == "ordered" {
if key == "ordered" || key == "cursor" {
// TODO: some tests specify that "ordered" must be a key in the event but ordered isn't a valid option for some of these cases (e.g. insertOne)
// TODO: some FLE tests specify "cursor" subdocument for listCollections
continue
}

Expand Down Expand Up @@ -153,12 +223,17 @@ func checkExpectations(mt *mtest.T, expectations []*expectation, id0 bsonx.Doc,
assert.Equal(mt, expectedID, actualID,
"session ID mismatch for session %v; expected %v, got %v", sessName, expectedID, actualID)
case "getMore":
expectedID := val.Int64()
// ignore placeholder cursor ID (42)
if expectedID != 42 {
expectedID, ok := val.Int64OK()
if ok {
// ignore placeholder ID (42)
if expectedID == 42 {
continue
}
actualID := actualVal.Int64()
assert.Equal(mt, expectedID, actualID, "cursor ID mismatch; expected %v, got %v", expectedID, actualID)
assert.Equal(mt, expectedID, actualID, "expected cursor ID; expected %v, got %v", expectedID, actualID)
continue
}
compareValues(mt, key, val, actualVal)
case "readConcern":
expectedRc := val.Document()
actualRc := actualVal.Document()
Expand Down
15 changes: 12 additions & 3 deletions mongo/integration/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ func TestCollection(t *testing.T) {
})
mt.Run("write error", func(mt *mtest.T) {
cappedOpts := bson.D{{"capped", true}, {"size", 64 * 1024}}
capped := mt.CreateCollectionWithOptions("deleteOne_capped", cappedOpts)
capped := mt.CreateCollection(mtest.Collection{
Name: "deleteOne_capped",
CreateOpts: cappedOpts,
}, true)
_, err := capped.DeleteOne(mtest.Background, bson.D{{"x", 1}})

we, ok := err.(mongo.WriteException)
Expand Down Expand Up @@ -225,7 +228,10 @@ func TestCollection(t *testing.T) {
})
mt.Run("write error", func(mt *mtest.T) {
cappedOpts := bson.D{{"capped", true}, {"size", 64 * 1024}}
capped := mt.CreateCollectionWithOptions("deleteMany_capped", cappedOpts)
capped := mt.CreateCollection(mtest.Collection{
Name: "deleteMany_capped",
CreateOpts: cappedOpts,
}, true)
_, err := capped.DeleteMany(mtest.Background, bson.D{{"x", 1}})

we, ok := err.(mongo.WriteException)
Expand Down Expand Up @@ -882,7 +888,10 @@ func TestCollection(t *testing.T) {
doc := mongo.NewDeleteOneModel().SetFilter(bson.D{{"x", 1}})
models := []mongo.WriteModel{doc, doc}
cappedOpts := bson.D{{"capped", true}, {"size", 64 * 1024}}
capped := mt.CreateCollectionWithOptions("delete_write_errors", cappedOpts)
capped := mt.CreateCollection(mtest.Collection{
Name: "delete_write_errors",
CreateOpts: cappedOpts,
}, true)

testCases := []struct {
name string
Expand Down
4 changes: 3 additions & 1 deletion mongo/integration/crud_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ func createUpdate(mt *mtest.T, updateVal bson.RawValue) interface{} {
return nil
}

// kill all open sessions on the server. This function uses mt.GlobalClient() because killAllSessions is not allowed
// for clients configured with specific options (e.g. client side encryption).
func killSessions(mt *mtest.T) {
mt.Helper()

err := mt.Client.Database("admin").RunCommand(mtest.Background, bson.D{
err := mt.GlobalClient().Database("admin").RunCommand(mtest.Background, bson.D{
{"killAllSessions", bson.A{}},
}, options.RunCmd().SetReadPreference(mtest.PrimaryRp)).Err()
if err == nil {
Expand Down
12 changes: 6 additions & 6 deletions mongo/integration/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestDatabase(t *testing.T) {
lcNamesOpts := mtest.NewOptions().MinServerVersion("4.0")
mt.RunOpts("list collection names", lcNamesOpts, func(mt *mtest.T) {
collName := "lcNamesCollection"
mt.CreateCollection(collName, true)
mt.CreateCollection(mtest.Collection{Name: collName}, true)

testCases := []struct {
name string
Expand Down Expand Up @@ -115,11 +115,11 @@ func TestDatabase(t *testing.T) {
for _, tc := range testCases {
tcOpts := mtest.NewOptions().Topologies(tc.expectedTopology)
mt.RunOpts(tc.name, tcOpts, func(mt *mtest.T) {
mt.CreateCollection(listCollUncapped, true)
mt.CreateCollectionWithOptions(listCollCapped, bson.D{
{"capped", true},
{"size", 64 * 1024},
})
mt.CreateCollection(mtest.Collection{Name: listCollUncapped}, true)
mt.CreateCollection(mtest.Collection{
Name: listCollCapped,
CreateOpts: bson.D{{"capped", true}, {"size", 64 * 1024}},
}, true)

filter := bson.D{}
if tc.cappedOnly {
Expand Down
89 changes: 89 additions & 0 deletions mongo/integration/json_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package integration
import (
"io/ioutil"
"math"
"os"
"path"
"strings"
"testing"
Expand All @@ -23,6 +24,11 @@ import (
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)

const (
awsAccessKeyID = "AWS_ACCESS_KEY_ID"
awsSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
)

// Helper functions to do read JSON spec test files and convert JSON objects into the appropriate driver types.
// Functions in this file should take testing.TB rather than testing.T/mtest.T for generality because they
// do not do any database communication.
Expand Down Expand Up @@ -82,6 +88,8 @@ func createClientOptions(t testing.TB, opts bson.Raw) *options.ClientOptions {
clientOpts.SetHeartbeatInterval(hfms)
case "retryReads":
clientOpts.SetRetryReads(opt.Boolean())
case "autoEncryptOpts":
clientOpts.SetAutoEncryptionOptions(createAutoEncryptionOptions(t, opt.Document()))
default:
t.Fatalf("unrecognized client option: %v", name)
}
Expand All @@ -90,6 +98,87 @@ func createClientOptions(t testing.TB, opts bson.Raw) *options.ClientOptions {
return clientOpts
}

func createAutoEncryptionOptions(t testing.TB, opts bson.Raw) *options.AutoEncryptionOptions {
t.Helper()

aeo := options.AutoEncryption()
var kvnsFound bool
elems, _ := opts.Elements()

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

switch name {
case "kmsProviders":
aeo.SetKmsProviders(createKmsProvidersMap(t, opt.Document()))
case "schemaMap":
var schemaMap map[string]interface{}
err := bson.Unmarshal(opt.Document(), &schemaMap)
if err != nil {
t.Fatalf("error creating schema map: %v", err)
}

aeo.SetSchemaMap(schemaMap)
case "keyVaultNamespace":
kvnsFound = true
aeo.SetKeyVaultNamespace(opt.StringValue())
case "bypassAutoEncryption":
aeo.SetBypassAutoEncryption(opt.Boolean())
default:
t.Fatalf("unrecognized auto encryption option: %v", name)
}
}
if !kvnsFound {
aeo.SetKeyVaultNamespace("admin.datakeys")
}

return aeo
}

func createKmsProvidersMap(t testing.TB, opts bson.Raw) map[string]map[string]interface{} {
t.Helper()

// aws: value is always empty object. create new map value from access key ID and secret access key
// local: value is {"key": primitive.Binary}. transform to {"key": []byte}

kmsMap := make(map[string]map[string]interface{})
elems, _ := opts.Elements()

for _, elem := range elems {
provider := elem.Key()
providerOpt := elem.Value()

switch provider {
case "aws":
keyID := os.Getenv(awsAccessKeyID)
if keyID == "" {
t.Fatalf("%s env var not set", awsAccessKeyID)
}
secretAccessKey := os.Getenv(awsSecretAccessKey)
if secretAccessKey == "" {
t.Fatalf("%s env var not set", awsSecretAccessKey)
}

awsMap := map[string]interface{}{
"accessKeyId": keyID,
"secretAccessKey": secretAccessKey,
}
kmsMap["aws"] = awsMap
case "local":
_, key := providerOpt.Document().Lookup("key").Binary()
localMap := map[string]interface{}{
"key": key,
}
kmsMap["local"] = localMap
default:
t.Fatalf("unrecognized KMS provider: %v", provider)
}
}

return kmsMap
}

// create session options from a map
func createSessionOptions(t testing.TB, opts bson.Raw) *options.SessionOptions {
t.Helper()
Expand Down
Loading

0 comments on commit ffed2da

Please sign in to comment.