...

Source file src/go.mongodb.org/mongo-driver/mongo/integration/database_test.go

Documentation: go.mongodb.org/mongo-driver/mongo/integration

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package integration
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"reflect"
    14  	"testing"
    15  
    16  	"go.mongodb.org/mongo-driver/bson"
    17  	"go.mongodb.org/mongo-driver/bson/bsontype"
    18  	"go.mongodb.org/mongo-driver/bson/primitive"
    19  	"go.mongodb.org/mongo-driver/internal/assert"
    20  	"go.mongodb.org/mongo-driver/internal/handshake"
    21  	"go.mongodb.org/mongo-driver/mongo"
    22  	"go.mongodb.org/mongo-driver/mongo/integration/mtest"
    23  	"go.mongodb.org/mongo-driver/mongo/options"
    24  	"go.mongodb.org/mongo-driver/mongo/readpref"
    25  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    26  )
    27  
    28  const (
    29  	listCollCapped   = "listcoll_capped"
    30  	listCollUncapped = "listcoll_uncapped"
    31  )
    32  
    33  var (
    34  	interfaceAsMapRegistry = bson.NewRegistryBuilder().
    35  		RegisterTypeMapEntry(bsontype.EmbeddedDocument, reflect.TypeOf(bson.M{})).
    36  		Build()
    37  )
    38  
    39  func TestDatabase(t *testing.T) {
    40  	mt := mtest.New(t, mtest.NewOptions().CreateClient(false))
    41  
    42  	mt.RunOpts("run command", noClientOpts, func(mt *mtest.T) {
    43  		mt.Run("decode raw", func(mt *mtest.T) {
    44  			res, err := mt.DB.RunCommand(context.Background(), bson.D{{handshake.LegacyHello, 1}}).Raw()
    45  			assert.Nil(mt, err, "RunCommand error: %v", err)
    46  
    47  			ok, err := res.LookupErr("ok")
    48  			assert.Nil(mt, err, "ok field not found in result")
    49  			assert.Equal(mt, bson.TypeDouble, ok.Type, "expected ok type %v, got %v", bson.TypeDouble, ok.Type)
    50  			assert.Equal(mt, 1.0, ok.Double(), "expected ok value 1.0, got %v", ok.Double())
    51  
    52  			hello, err := res.LookupErr(handshake.LegacyHelloLowercase)
    53  			assert.Nil(mt, err, "legacy hello response field not found in result")
    54  			assert.Equal(mt, bson.TypeBoolean, hello.Type, "expected hello type %v, got %v", bson.TypeBoolean, hello.Type)
    55  			assert.True(mt, hello.Boolean(), "expected hello value true, got false")
    56  		})
    57  		mt.Run("decode struct", func(mt *mtest.T) {
    58  			result := struct {
    59  				Ok float64 `bson:"ok"`
    60  			}{}
    61  			err := mt.DB.RunCommand(context.Background(), bson.D{{"ping", 1}}).Decode(&result)
    62  			assert.Nil(mt, err, "RunCommand error: %v", err)
    63  			assert.Equal(mt, 1.0, result.Ok, "expected ok value 1.0, got %v", result.Ok)
    64  		})
    65  
    66  		// We set min server version 3.6 because pre-3.6 servers use OP_QUERY, so the command document will look like
    67  		// {$query: {...}, $readPreference: {...}}. Per the command monitoring spec, the $query subdocument is unwrapped
    68  		// and the $readPreference is dropped for monitoring purposes, so we can't examine it.
    69  		readPrefOpts := mtest.NewOptions().
    70  			Topologies(mtest.Sharded).
    71  			MinServerVersion("3.6")
    72  		mt.RunOpts("read pref passed to mongos", readPrefOpts, func(mt *mtest.T) {
    73  			// When communicating with a mongos, the supplied read preference should be passed down to the operations
    74  			// layer, which should add a top-level $readPreference field to the command.
    75  
    76  			runCmdOpts := options.RunCmd().
    77  				SetReadPreference(readpref.SecondaryPreferred())
    78  			err := mt.DB.RunCommand(context.Background(), bson.D{{handshake.LegacyHello, 1}}, runCmdOpts).Err()
    79  			assert.Nil(mt, err, "RunCommand error: %v", err)
    80  
    81  			expected := bson.Raw(bsoncore.NewDocumentBuilder().
    82  				AppendString("mode", "secondaryPreferred").
    83  				Build())
    84  			evt := mt.GetStartedEvent()
    85  			assert.Equal(mt, handshake.LegacyHello, evt.CommandName, "expected legacy hello command to be sent, got %q", evt.CommandName)
    86  			actual, ok := evt.Command.Lookup("$readPreference").DocumentOK()
    87  			assert.True(mt, ok, "expected command %v to contain a $readPreference document", evt.Command)
    88  			assert.Equal(mt, expected, actual, "expected $readPreference document %v, got %v", expected, actual)
    89  		})
    90  		failpointOpts := mtest.NewOptions().MinServerVersion("4.0").Topologies(mtest.ReplicaSet)
    91  		mt.RunOpts("gets result and error", failpointOpts, func(mt *mtest.T) {
    92  			mt.SetFailPoint(mtest.FailPoint{
    93  				ConfigureFailPoint: "failCommand",
    94  				Mode: mtest.FailPointMode{
    95  					Times: 1,
    96  				},
    97  				Data: mtest.FailPointData{
    98  					FailCommands: []string{"insert"},
    99  					WriteConcernError: &mtest.WriteConcernErrorData{
   100  						Code: 100,
   101  					},
   102  				},
   103  			})
   104  			cmd := bson.D{
   105  				{"insert", "test"},
   106  				{"documents", bson.A{bson.D{{"a", 1}}}},
   107  			}
   108  			res, gotErr := mt.DB.RunCommand(context.Background(), cmd).Raw()
   109  
   110  			n, ok := res.Lookup("n").Int32OK()
   111  			assert.True(mt, ok, "expected n in response")
   112  			assert.Equal(mt, int32(1), n, "expected n value 1, got %v", n)
   113  
   114  			writeExcept, ok := gotErr.(mongo.WriteException)
   115  			assert.True(mt, ok, "expected WriteCommandError, got %T", gotErr)
   116  			assert.NotNil(mt, writeExcept.WriteConcernError, "expected WriteConcernError to be non-nil")
   117  			assert.Equal(mt, writeExcept.WriteConcernError.Code, 100, "expected error code 100, got %v", writeExcept.WriteConcernError.Code)
   118  		})
   119  		mt.Run("multi key map command", func(mt *mtest.T) {
   120  			err := mt.DB.RunCommand(context.Background(), bson.M{"insert": "test", "documents": bson.A{bson.D{{"a", 1}}}}).Err()
   121  			assert.Equal(mt, mongo.ErrMapForOrderedArgument{"cmd"}, err, "expected error %v, got %v", mongo.ErrMapForOrderedArgument{"cmd"}, err)
   122  		})
   123  	})
   124  
   125  	dropOpts := mtest.NewOptions().DatabaseName("dropDb")
   126  	mt.RunOpts("drop", dropOpts, func(mt *mtest.T) {
   127  		err := mt.DB.Drop(context.Background())
   128  		assert.Nil(mt, err, "Drop error: %v", err)
   129  
   130  		list, err := mt.Client.ListDatabaseNames(context.Background(), bson.D{})
   131  		assert.Nil(mt, err, "ListDatabaseNames error: %v", err)
   132  		for _, db := range list {
   133  			if db == "dropDb" {
   134  				mt.Fatal("dropped database 'dropDb' found in database names")
   135  			}
   136  		}
   137  	})
   138  
   139  	lcNamesOpts := mtest.NewOptions().MinServerVersion("4.0")
   140  	mt.RunOpts("list collection names", lcNamesOpts, func(mt *mtest.T) {
   141  		collName := "lcNamesCollection"
   142  		mt.CreateCollection(mtest.Collection{Name: collName}, true)
   143  
   144  		testCases := []struct {
   145  			name   string
   146  			filter bson.D
   147  			found  bool
   148  		}{
   149  			{"no filter", bson.D{}, true},
   150  			{"filter", bson.D{{"name", "lcNamesCollection"}}, true},
   151  			{"filter not found", bson.D{{"name", "123"}}, false},
   152  		}
   153  		for _, tc := range testCases {
   154  			mt.Run(tc.name, func(mt *mtest.T) {
   155  				colls, err := mt.DB.ListCollectionNames(context.Background(), tc.filter)
   156  				assert.Nil(mt, err, "ListCollectionNames error: %v", err)
   157  
   158  				var found bool
   159  				for _, coll := range colls {
   160  					if coll == collName {
   161  						found = true
   162  						break
   163  					}
   164  				}
   165  
   166  				assert.Equal(mt, tc.found, found, "expected to find collection: %v, found collection: %v", tc.found, found)
   167  			})
   168  		}
   169  	})
   170  
   171  	mt.RunOpts("list collections", noClientOpts, func(mt *mtest.T) {
   172  		createCollections := func(mt *mtest.T, numCollections int) {
   173  			mt.Helper()
   174  
   175  			for i := 0; i < numCollections; i++ {
   176  				mt.CreateCollection(mtest.Collection{
   177  					Name: fmt.Sprintf("list-collections-test-%d", i),
   178  				}, true)
   179  			}
   180  		}
   181  
   182  		mt.RunOpts("verify results", noClientOpts, func(mt *mtest.T) {
   183  			testCases := []struct {
   184  				name             string
   185  				expectedTopology mtest.TopologyKind
   186  				cappedOnly       bool
   187  			}{
   188  				{"standalone no filter", mtest.Single, false},
   189  				{"standalone filter", mtest.Single, true},
   190  				{"replica set no filter", mtest.ReplicaSet, false},
   191  				{"replica set filter", mtest.ReplicaSet, true},
   192  				{"sharded no filter", mtest.Sharded, false},
   193  				{"sharded filter", mtest.Sharded, true},
   194  			}
   195  			for _, tc := range testCases {
   196  				tcOpts := mtest.NewOptions().Topologies(tc.expectedTopology)
   197  				mt.RunOpts(tc.name, tcOpts, func(mt *mtest.T) {
   198  					mt.CreateCollection(mtest.Collection{Name: listCollUncapped}, true)
   199  					mt.CreateCollection(mtest.Collection{
   200  						Name:       listCollCapped,
   201  						CreateOpts: options.CreateCollection().SetCapped(true).SetSizeInBytes(64 * 1024),
   202  					}, true)
   203  
   204  					filter := bson.D{}
   205  					if tc.cappedOnly {
   206  						filter = bson.D{{"options.capped", true}}
   207  					}
   208  
   209  					var err error
   210  					for i := 0; i < 1; i++ {
   211  						cursor, err := mt.DB.ListCollections(context.Background(), filter)
   212  						assert.Nil(mt, err, "ListCollections error (iteration %v): %v", i, err)
   213  
   214  						err = verifyListCollections(cursor, tc.cappedOnly)
   215  						if err == nil {
   216  							return
   217  						}
   218  					}
   219  					mt.Fatalf("error verifying list collections result: %v", err)
   220  				})
   221  			}
   222  		})
   223  
   224  		// For server versions below 3.0, we internally execute ListCollections() as a legacy
   225  		// OP_QUERY against the system.namespaces collection. Command monitoring upconversions
   226  		// translate this to a "find" command rather than "listCollections".
   227  		cmdMonitoringCmdName := "listCollections"
   228  		if mtest.CompareServerVersions(mtest.ServerVersion(), "3.0") < 0 {
   229  			cmdMonitoringCmdName = "find"
   230  		}
   231  		mt.Run("batch size", func(mt *mtest.T) {
   232  			// Create two new collections so there will be three total.
   233  			createCollections(mt, 2)
   234  
   235  			mt.ClearEvents()
   236  			lcOpts := options.ListCollections().SetBatchSize(2)
   237  			_, err := mt.DB.ListCollectionNames(context.Background(), bson.D{}, lcOpts)
   238  			assert.Nil(mt, err, "ListCollectionNames error: %v", err)
   239  
   240  			evt := mt.GetStartedEvent()
   241  			assert.Equal(
   242  				mt,
   243  				cmdMonitoringCmdName,
   244  				evt.CommandName,
   245  				"expected %q command to be sent, got %q",
   246  				cmdMonitoringCmdName,
   247  				evt.CommandName)
   248  			_, err = evt.Command.LookupErr("cursor", "batchSize")
   249  			assert.Nil(mt, err, "expected command %s to contain key 'batchSize'", evt.Command)
   250  		})
   251  		mt.RunOpts("authorizedCollections", mtest.NewOptions().MinServerVersion("4.0"), func(mt *mtest.T) {
   252  			mt.ClearEvents()
   253  			lcOpts := options.ListCollections().SetAuthorizedCollections(true)
   254  			_, err := mt.DB.ListCollections(context.Background(), bson.D{}, lcOpts)
   255  			assert.Nil(mt, err, "ListCollections error: %v", err)
   256  
   257  			evt := mt.GetStartedEvent()
   258  			assert.Equal(mt, "listCollections", evt.CommandName,
   259  				"expected 'listCollections' command to be sent, got %q", evt.CommandName)
   260  			_, err = evt.Command.LookupErr("authorizedCollections")
   261  			assert.Nil(mt, err, "expected command to contain key 'authorizedCollections'")
   262  
   263  		})
   264  		mt.Run("getMore commands are monitored", func(mt *mtest.T) {
   265  			createCollections(mt, 2)
   266  			assertGetMoreCommandsAreMonitored(mt, cmdMonitoringCmdName, func() (*mongo.Cursor, error) {
   267  				return mt.DB.ListCollections(context.Background(), bson.D{}, options.ListCollections().SetBatchSize(2))
   268  			})
   269  		})
   270  		mt.Run("killCursors commands are monitored", func(mt *mtest.T) {
   271  			createCollections(mt, 2)
   272  			assertKillCursorsCommandsAreMonitored(mt, cmdMonitoringCmdName, func() (*mongo.Cursor, error) {
   273  				return mt.DB.ListCollections(context.Background(), bson.D{}, options.ListCollections().SetBatchSize(2))
   274  			})
   275  		})
   276  	})
   277  
   278  	mt.RunOpts("list collection specifications", noClientOpts, func(mt *mtest.T) {
   279  		mt.Run("filter passed to listCollections", func(mt *mtest.T) {
   280  			// Test that ListCollectionSpecifications correctly uses the supplied filter.
   281  			cappedName := "list-collection-specs-capped"
   282  			mt.CreateCollection(mtest.Collection{
   283  				Name:       cappedName,
   284  				CreateOpts: options.CreateCollection().SetCapped(true).SetSizeInBytes(4096),
   285  			}, true)
   286  
   287  			filter := bson.M{
   288  				"options.capped": true,
   289  			}
   290  			cursor, err := mt.DB.ListCollections(context.Background(), filter)
   291  			assert.Nil(mt, err, "ListCollections error: %v", err)
   292  			defer cursor.Close(context.Background())
   293  			assert.True(mt, cursor.Next(context.Background()), "expected Next to return true, got false; cursor error: %v",
   294  				cursor.Err())
   295  
   296  			optionsDoc := bsoncore.NewDocumentBuilder().
   297  				AppendBoolean("capped", true).
   298  				AppendInt32("size", 4096).
   299  				Build()
   300  
   301  			expectedSpec := &mongo.CollectionSpecification{
   302  				Name:     cappedName,
   303  				Type:     "collection",
   304  				ReadOnly: false,
   305  				Options:  bson.Raw(optionsDoc),
   306  			}
   307  			if mtest.CompareServerVersions(mtest.ServerVersion(), "3.6") >= 0 {
   308  				uuidSubtype, uuidData := cursor.Current.Lookup("info", "uuid").Binary()
   309  				expectedSpec.UUID = &primitive.Binary{Subtype: uuidSubtype, Data: uuidData}
   310  			}
   311  			if mtest.CompareServerVersions(mtest.ServerVersion(), "3.4") >= 0 {
   312  				keysDoc := bsoncore.NewDocumentBuilder().
   313  					AppendInt32("_id", 1).
   314  					Build()
   315  				expectedSpec.IDIndex = &mongo.IndexSpecification{
   316  					Name:         "_id_",
   317  					Namespace:    mt.DB.Name() + "." + cappedName,
   318  					KeysDocument: bson.Raw(keysDoc),
   319  					Version:      2,
   320  				}
   321  			}
   322  
   323  			specs, err := mt.DB.ListCollectionSpecifications(context.Background(), filter)
   324  			assert.Nil(mt, err, "ListCollectionSpecifications error: %v", err)
   325  			assert.Equal(mt, 1, len(specs), "expected 1 CollectionSpecification, got %d", len(specs))
   326  			assert.Equal(mt, expectedSpec, specs[0], "expected specification %v, got %v", expectedSpec, specs[0])
   327  		})
   328  
   329  		mt.RunOpts("options passed to listCollections", mtest.NewOptions().MinServerVersion("3.0"), func(mt *mtest.T) {
   330  			// Test that ListCollectionSpecifications correctly uses the supplied options.
   331  
   332  			opts := options.ListCollections().SetNameOnly(true)
   333  			_, err := mt.DB.ListCollectionSpecifications(context.Background(), bson.D{}, opts)
   334  			assert.Nil(mt, err, "ListCollectionSpecifications error: %v", err)
   335  
   336  			evt := mt.GetStartedEvent()
   337  			assert.Equal(mt, "listCollections", evt.CommandName, "expected %q command to be sent, got %q",
   338  				"listCollections", evt.CommandName)
   339  			nameOnly, ok := evt.Command.Lookup("nameOnly").BooleanOK()
   340  			assert.True(mt, ok, "expected command %v to contain %q field", evt.Command, "nameOnly")
   341  			assert.True(mt, nameOnly, "expected nameOnly value to be true, got false")
   342  		})
   343  	})
   344  
   345  	mt.RunOpts("run command cursor", noClientOpts, func(mt *mtest.T) {
   346  		var data []interface{}
   347  		for i := 0; i < 5; i++ {
   348  			data = append(data, bson.D{{"x", i}})
   349  		}
   350  		findCollName := "runcommandcursor_find"
   351  		findCmd := bson.D{{"find", findCollName}}
   352  		aggCollName := "runcommandcursor_agg"
   353  		aggCmd := bson.D{
   354  			{"aggregate", aggCollName},
   355  			{"pipeline", bson.A{}},
   356  			{"cursor", bson.D{}},
   357  		}
   358  		pingCmd := bson.D{{"ping", 1}}
   359  		pingErr := errors.New("database response does not contain a cursor; try using RunCommand instead")
   360  
   361  		testCases := []struct {
   362  			name        string
   363  			collName    string
   364  			cmd         interface{}
   365  			toInsert    []interface{}
   366  			expectedErr error
   367  			numExpected int
   368  			minVersion  string
   369  		}{
   370  			{"success find", findCollName, findCmd, data, nil, 5, "3.2"},
   371  			{"success aggregate", aggCollName, aggCmd, data, nil, 5, ""},
   372  			{"failures", "runcommandcursor_ping", pingCmd, nil, pingErr, 0, ""},
   373  		}
   374  		for _, tc := range testCases {
   375  			tcOpts := mtest.NewOptions().CollectionName(tc.collName)
   376  			if tc.minVersion != "" {
   377  				tcOpts.MinServerVersion(tc.minVersion)
   378  			}
   379  
   380  			mt.RunOpts(tc.name, tcOpts, func(mt *mtest.T) {
   381  				if len(tc.toInsert) > 0 {
   382  					_, err := mt.Coll.InsertMany(context.Background(), tc.toInsert)
   383  					assert.Nil(mt, err, "InsertMany error: %v", err)
   384  				}
   385  
   386  				cursor, err := mt.DB.RunCommandCursor(context.Background(), tc.cmd)
   387  				assert.Equal(mt, tc.expectedErr, err, "expected error %v, got %v", tc.expectedErr, err)
   388  				if tc.expectedErr != nil {
   389  					return
   390  				}
   391  
   392  				var count int
   393  				for cursor.Next(context.Background()) {
   394  					count++
   395  				}
   396  				assert.Equal(mt, tc.numExpected, count, "expected document count %v, got %v", tc.numExpected, count)
   397  			})
   398  		}
   399  
   400  		// The find command does not exist on server versions below 3.2.
   401  		cmdMonitoringMtOpts := mtest.NewOptions().MinServerVersion("3.2")
   402  		mt.RunOpts("getMore commands are monitored", cmdMonitoringMtOpts, func(mt *mtest.T) {
   403  			initCollection(mt, mt.Coll)
   404  			assertGetMoreCommandsAreMonitored(mt, "find", func() (*mongo.Cursor, error) {
   405  				findCmd := bson.D{
   406  					{"find", mt.Coll.Name()},
   407  					{"batchSize", 2},
   408  				}
   409  				return mt.DB.RunCommandCursor(context.Background(), findCmd)
   410  			})
   411  		})
   412  		mt.RunOpts("killCursors commands are monitored", cmdMonitoringMtOpts, func(mt *mtest.T) {
   413  			initCollection(mt, mt.Coll)
   414  			assertKillCursorsCommandsAreMonitored(mt, "find", func() (*mongo.Cursor, error) {
   415  				findCmd := bson.D{
   416  					{"find", mt.Coll.Name()},
   417  					{"batchSize", 2},
   418  				}
   419  				return mt.DB.RunCommandCursor(context.Background(), findCmd)
   420  			})
   421  		})
   422  	})
   423  
   424  	mt.RunOpts("create collection", noClientOpts, func(mt *mtest.T) {
   425  		collectionName := "create-collection-test"
   426  
   427  		mt.RunOpts("options", noClientOpts, func(mt *mtest.T) {
   428  			// Tests for various options combinations. The test creates a collection with some options and then verifies
   429  			// the result using the options document reported by listCollections.
   430  
   431  			// All possible options except collation and changeStreamPreAndPostImages. The collation is omitted here and tested below because the
   432  			// collation document reported by listCollections fills in extra fields and includes a "version" field that's not described in
   433  			// https://www.mongodb.com/docs/manual/reference/collation/. changeStreamPreAndPostImages is omitted here and tested in another testcase
   434  			// because it is only an available option on 6.0+.
   435  			storageEngine := bson.M{
   436  				"wiredTiger": bson.M{
   437  					"configString": "block_compressor=zlib",
   438  				},
   439  			}
   440  			defaultIndexOpts := options.DefaultIndex().SetStorageEngine(storageEngine)
   441  			validator := bson.M{
   442  				"$or": bson.A{
   443  					bson.M{
   444  						"phone": bson.M{"$type": "string"},
   445  					},
   446  					bson.M{
   447  						"email": bson.M{"$type": "string"},
   448  					},
   449  				},
   450  			}
   451  			nonCollationOpts := options.CreateCollection().
   452  				SetCapped(true).
   453  				SetDefaultIndexOptions(defaultIndexOpts).
   454  				SetMaxDocuments(100).
   455  				SetSizeInBytes(1024).
   456  				SetStorageEngine(storageEngine).
   457  				SetValidator(validator).
   458  				SetValidationAction("warn").
   459  				SetValidationLevel("moderate")
   460  			nonCollationExpected := bson.M{
   461  				"capped": true,
   462  				"indexOptionDefaults": bson.M{
   463  					"storageEngine": storageEngine,
   464  				},
   465  				"max":              int32(100),
   466  				"size":             int32(1024),
   467  				"storageEngine":    storageEngine,
   468  				"validator":        validator,
   469  				"validationAction": "warn",
   470  				"validationLevel":  "moderate",
   471  			}
   472  
   473  			csppiOpts := options.CreateCollection().SetChangeStreamPreAndPostImages(bson.M{"enabled": true})
   474  			csppiExpected := bson.M{
   475  				"changeStreamPreAndPostImages": bson.M{
   476  					"enabled": true,
   477  				},
   478  			}
   479  
   480  			testCases := []struct {
   481  				name             string
   482  				minServerVersion string
   483  				maxServerVersion string
   484  				createOpts       *options.CreateCollectionOptions
   485  				expectedOpts     bson.M
   486  			}{
   487  				{"all options except collation and csppi", "3.2", "", nonCollationOpts, nonCollationExpected},
   488  				{"changeStreamPreAndPostImages", "6.0", "", csppiOpts, csppiExpected},
   489  			}
   490  
   491  			for _, tc := range testCases {
   492  				mtOpts := mtest.NewOptions().
   493  					MinServerVersion(tc.minServerVersion).
   494  					MaxServerVersion(tc.maxServerVersion)
   495  
   496  				mt.RunOpts(tc.name, mtOpts, func(mt *mtest.T) {
   497  					mt.CreateCollection(mtest.Collection{
   498  						Name: collectionName,
   499  					}, false)
   500  
   501  					err := mt.DB.CreateCollection(context.Background(), collectionName, tc.createOpts)
   502  					assert.Nil(mt, err, "CreateCollection error: %v", err)
   503  
   504  					actualOpts := getCollectionOptions(mt, collectionName)
   505  					assert.Equal(mt, tc.expectedOpts, actualOpts, "options mismatch; expected %v, got %v",
   506  						tc.expectedOpts, actualOpts)
   507  				})
   508  			}
   509  		})
   510  		mt.RunOpts("collation", mtest.NewOptions().MinServerVersion("3.4"), func(mt *mtest.T) {
   511  			mt.CreateCollection(mtest.Collection{
   512  				Name: collectionName,
   513  			}, false)
   514  
   515  			locale := "en_US"
   516  			createOpts := options.CreateCollection().SetCollation(&options.Collation{
   517  				Locale: locale,
   518  			})
   519  			err := mt.DB.CreateCollection(context.Background(), collectionName, createOpts)
   520  			assert.Nil(mt, err, "CreateCollection error: %v", err)
   521  
   522  			actualOpts := getCollectionOptions(mt, collectionName)
   523  			collationVal, ok := actualOpts["collation"]
   524  			assert.True(mt, ok, "expected key 'collation' in collection options %v", actualOpts)
   525  			collation := collationVal.(bson.M)
   526  			assert.Equal(mt, locale, collation["locale"], "expected locale %v, got %v", locale, collation["locale"])
   527  		})
   528  		mt.Run("write concern", func(mt *mtest.T) {
   529  			mt.CreateCollection(mtest.Collection{
   530  				Name: collectionName,
   531  			}, false)
   532  
   533  			err := mt.DB.CreateCollection(context.Background(), collectionName)
   534  			assert.Nil(mt, err, "CreateCollection error: %v", err)
   535  
   536  			evt := mt.GetStartedEvent()
   537  			assert.Equal(mt, evt.CommandName, "create", "expected event for 'create', got '%v'", evt.CommandName)
   538  			_, err = evt.Command.LookupErr("writeConcern")
   539  			assert.Nil(mt, err, "expected write concern to be included in command %v", evt.Command)
   540  		})
   541  	})
   542  
   543  	mt.RunOpts("create view", mtest.NewOptions().CreateClient(false).MinServerVersion("3.4"), func(mt *mtest.T) {
   544  		sourceCollectionName := "create-view-test-collection"
   545  		viewName := "create-view-test-view"
   546  		projectStage := bson.M{
   547  			"$project": bson.M{
   548  				"projectedField": "foo",
   549  			},
   550  		}
   551  		pipeline := []bson.M{projectStage}
   552  
   553  		mt.Run("function parameters are translated into options", func(mt *mtest.T) {
   554  			mt.CreateCollection(mtest.Collection{
   555  				Name: viewName,
   556  			}, false)
   557  
   558  			err := mt.DB.CreateView(context.Background(), viewName, sourceCollectionName, pipeline)
   559  			assert.Nil(mt, err, "CreateView error: %v", err)
   560  
   561  			expectedOpts := bson.M{
   562  				"viewOn":   sourceCollectionName,
   563  				"pipeline": bson.A{projectStage},
   564  			}
   565  			actualOpts := getCollectionOptions(mt, viewName)
   566  			assert.Equal(mt, expectedOpts, actualOpts, "options mismatch; expected %v, got %v", expectedOpts,
   567  				actualOpts)
   568  		})
   569  		mt.Run("collation", func(mt *mtest.T) {
   570  			mt.CreateCollection(mtest.Collection{
   571  				Name: viewName,
   572  			}, false)
   573  
   574  			locale := "en_US"
   575  			viewOpts := options.CreateView().SetCollation(&options.Collation{
   576  				Locale: locale,
   577  			})
   578  			err := mt.DB.CreateView(context.Background(), viewName, sourceCollectionName, mongo.Pipeline{}, viewOpts)
   579  			assert.Nil(mt, err, "CreateView error: %v", err)
   580  
   581  			actualOpts := getCollectionOptions(mt, viewName)
   582  			collationVal, ok := actualOpts["collation"]
   583  			assert.True(mt, ok, "expected key 'collation' in view options %v", actualOpts)
   584  			collation := collationVal.(bson.M)
   585  			assert.Equal(mt, locale, collation["locale"], "expected locale %v, got %v", locale, collation["locale"])
   586  		})
   587  	})
   588  }
   589  
   590  func getCollectionOptions(mt *mtest.T, collectionName string) bson.M {
   591  	mt.Helper()
   592  
   593  	filter := bson.M{
   594  		"name": collectionName,
   595  	}
   596  	cursor, err := mt.DB.ListCollections(context.Background(), filter)
   597  	assert.Nil(mt, err, "ListCollections error: %v", err)
   598  	defer cursor.Close(context.Background())
   599  	assert.True(mt, cursor.Next(context.Background()), "expected Next to return true, got false")
   600  
   601  	var actualOpts bson.M
   602  	err = bson.UnmarshalWithRegistry(interfaceAsMapRegistry, cursor.Current.Lookup("options").Document(), &actualOpts)
   603  	assert.Nil(mt, err, "UnmarshalWithRegistry error: %v", err)
   604  
   605  	return actualOpts
   606  }
   607  
   608  func verifyListCollections(cursor *mongo.Cursor, cappedOnly bool) error {
   609  	var cappedFound, uncappedFound bool
   610  
   611  	for cursor.Next(context.Background()) {
   612  		nameElem, err := cursor.Current.LookupErr("name")
   613  		if err != nil {
   614  			return fmt.Errorf("name element not found in document %v", cursor.Current)
   615  		}
   616  		if nameElem.Type != bson.TypeString {
   617  			return fmt.Errorf("expected name type %v, got %v", bson.TypeString, nameElem.Type)
   618  		}
   619  
   620  		name := nameElem.StringValue()
   621  		// legacy servers can return an indexes collection that shouldn't be considered here
   622  		if name != listCollUncapped && name != listCollCapped {
   623  			continue
   624  		}
   625  
   626  		if name == listCollUncapped && !uncappedFound {
   627  			if cappedOnly {
   628  				return fmt.Errorf("found uncapped collection %v but expected only capped collections", listCollUncapped)
   629  			}
   630  
   631  			uncappedFound = true
   632  			continue
   633  		}
   634  		if name == listCollCapped && !cappedFound {
   635  			cappedFound = true
   636  			continue
   637  		}
   638  
   639  		// duplicate found
   640  		return fmt.Errorf("found duplicate collection %v", name)
   641  	}
   642  
   643  	if !cappedFound {
   644  		return fmt.Errorf("capped collection %v not found", listCollCapped)
   645  	}
   646  	if !cappedOnly && !uncappedFound {
   647  		return fmt.Errorf("uncapped collection %v not found", listCollUncapped)
   648  	}
   649  	return nil
   650  }
   651  

View as plain text