...

Source file src/go.mongodb.org/mongo-driver/mongo/integration/crud_prose_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  	"bytes"
    11  	"context"
    12  	"errors"
    13  	"testing"
    14  
    15  	"go.mongodb.org/mongo-driver/bson"
    16  	"go.mongodb.org/mongo-driver/internal/assert"
    17  	"go.mongodb.org/mongo-driver/mongo"
    18  	"go.mongodb.org/mongo-driver/mongo/integration/mtest"
    19  	"go.mongodb.org/mongo-driver/mongo/options"
    20  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    21  )
    22  
    23  func TestWriteErrorsWithLabels(t *testing.T) {
    24  	clientOpts := options.Client().SetRetryWrites(false).SetWriteConcern(mtest.MajorityWc).
    25  		SetReadConcern(mtest.MajorityRc)
    26  	mtOpts := mtest.NewOptions().ClientOptions(clientOpts).MinServerVersion("4.0").Topologies(mtest.ReplicaSet).
    27  		CreateClient(false)
    28  	mt := mtest.New(t, mtOpts)
    29  
    30  	label := "ExampleError"
    31  	mt.Run("InsertMany errors with label", func(mt *mtest.T) {
    32  		mt.SetFailPoint(mtest.FailPoint{
    33  			ConfigureFailPoint: "failCommand",
    34  			Mode: mtest.FailPointMode{
    35  				Times: 1,
    36  			},
    37  			Data: mtest.FailPointData{
    38  				FailCommands: []string{"insert"},
    39  				WriteConcernError: &mtest.WriteConcernErrorData{
    40  					Code:        100,
    41  					ErrorLabels: &[]string{label},
    42  				},
    43  			},
    44  		})
    45  
    46  		_, err := mt.Coll.InsertMany(context.Background(),
    47  			[]interface{}{
    48  				bson.D{
    49  					{"a", 1},
    50  				},
    51  				bson.D{
    52  					{"a", 2},
    53  				},
    54  			})
    55  		assert.NotNil(mt, err, "expected non-nil error, got nil")
    56  
    57  		we, ok := err.(mongo.BulkWriteException)
    58  		assert.True(mt, ok, "expected mongo.BulkWriteException, got %T", err)
    59  		assert.True(mt, we.HasErrorLabel(label), "expected error to have label: %v", label)
    60  	})
    61  
    62  	mt.Run("WriteException with label", func(mt *mtest.T) {
    63  		mt.SetFailPoint(mtest.FailPoint{
    64  			ConfigureFailPoint: "failCommand",
    65  			Mode: mtest.FailPointMode{
    66  				Times: 1,
    67  			},
    68  			Data: mtest.FailPointData{
    69  				FailCommands: []string{"delete"},
    70  				WriteConcernError: &mtest.WriteConcernErrorData{
    71  					Code:        100,
    72  					ErrorLabels: &[]string{label},
    73  				},
    74  			},
    75  		})
    76  
    77  		_, err := mt.Coll.DeleteMany(context.Background(), bson.D{{"a", 1}})
    78  		assert.NotNil(mt, err, "expected non-nil error, got nil")
    79  
    80  		we, ok := err.(mongo.WriteException)
    81  		assert.True(mt, ok, "expected mongo.WriteException, got %T", err)
    82  		assert.True(mt, we.HasErrorLabel(label), "expected error to have label: %v", label)
    83  	})
    84  
    85  	mt.Run("BulkWriteException with label", func(mt *mtest.T) {
    86  		mt.SetFailPoint(mtest.FailPoint{
    87  			ConfigureFailPoint: "failCommand",
    88  			Mode: mtest.FailPointMode{
    89  				Times: 1,
    90  			},
    91  			Data: mtest.FailPointData{
    92  				FailCommands: []string{"delete"},
    93  				WriteConcernError: &mtest.WriteConcernErrorData{
    94  					Code:        100,
    95  					ErrorLabels: &[]string{label},
    96  				},
    97  			},
    98  		})
    99  
   100  		models := []mongo.WriteModel{
   101  			&mongo.InsertOneModel{bson.D{{"a", 2}}},
   102  			&mongo.DeleteOneModel{bson.D{{"a", 2}}, nil, nil},
   103  		}
   104  		_, err := mt.Coll.BulkWrite(context.Background(), models)
   105  		assert.NotNil(mt, err, "expected non-nil error, got nil")
   106  
   107  		we, ok := err.(mongo.BulkWriteException)
   108  		assert.True(mt, ok, "expected mongo.BulkWriteException, got %T", err)
   109  		assert.True(mt, we.HasErrorLabel(label), "expected error to have label: %v", label)
   110  	})
   111  
   112  }
   113  
   114  func TestWriteErrorsDetails(t *testing.T) {
   115  	clientOpts := options.Client().
   116  		SetRetryWrites(false).
   117  		SetWriteConcern(mtest.MajorityWc).
   118  		SetReadConcern(mtest.MajorityRc)
   119  	mtOpts := mtest.NewOptions().
   120  		ClientOptions(clientOpts).
   121  		MinServerVersion("5.0").
   122  		Topologies(mtest.ReplicaSet, mtest.Single).
   123  		CreateClient(false)
   124  
   125  	mt := mtest.New(t, mtOpts)
   126  
   127  	mt.Run("JSON Schema validation", func(mt *mtest.T) {
   128  		// Create a JSON Schema validator document that requires properties "a" and "b". Use it in
   129  		// the collection creation options so that collections created for subtests have the JSON
   130  		// Schema validator applied.
   131  		validator := bson.M{
   132  			"$jsonSchema": bson.M{
   133  				"bsonType": "object",
   134  				"required": []string{"a", "b"},
   135  				"properties": bson.M{
   136  					"a": bson.M{"bsonType": "string"},
   137  					"b": bson.M{"bsonType": "int"},
   138  				},
   139  			},
   140  		}
   141  
   142  		cco := options.CreateCollection().SetValidator(validator)
   143  		validatorOpts := mtest.NewOptions().CollectionCreateOptions(cco)
   144  
   145  		cases := []struct {
   146  			desc                string
   147  			operation           func(*mongo.Collection) error
   148  			expectBulkError     bool
   149  			expectedCommandName string
   150  		}{
   151  			{
   152  				desc: "InsertOne schema validation errors should include Details",
   153  				operation: func(coll *mongo.Collection) error {
   154  					// Try to insert a document that doesn't contain the required properties.
   155  					_, err := coll.InsertOne(context.Background(), bson.D{{"nope", 1}})
   156  					return err
   157  				},
   158  				expectBulkError:     false,
   159  				expectedCommandName: "insert",
   160  			},
   161  			{
   162  				desc: "InsertMany schema validation errors should include Details",
   163  				operation: func(coll *mongo.Collection) error {
   164  					// Try to insert a document that doesn't contain the required properties.
   165  					_, err := coll.InsertMany(context.Background(), []interface{}{bson.D{{"nope", 1}}})
   166  					return err
   167  				},
   168  				expectBulkError:     true,
   169  				expectedCommandName: "insert",
   170  			},
   171  			{
   172  				desc: "UpdateOne schema validation errors should include Details",
   173  				operation: func(coll *mongo.Collection) error {
   174  					// Try to set "a" to be an int, which violates the string type requirement.
   175  					_, err := coll.UpdateOne(
   176  						context.Background(),
   177  						bson.D{},
   178  						bson.D{{"$set", bson.D{{"a", 1}}}})
   179  					return err
   180  				},
   181  				expectBulkError:     false,
   182  				expectedCommandName: "update",
   183  			},
   184  			{
   185  				desc: "UpdateMany schema validation errors should include Details",
   186  				operation: func(coll *mongo.Collection) error {
   187  					// Try to set "a" to be an int in all documents in the collection, which violates
   188  					// the string type requirement.
   189  					_, err := coll.UpdateMany(
   190  						context.Background(),
   191  						bson.D{},
   192  						bson.D{{"$set", bson.D{{"a", 1}}}})
   193  					return err
   194  				},
   195  				expectBulkError:     false,
   196  				expectedCommandName: "update",
   197  			},
   198  		}
   199  
   200  		for _, tc := range cases {
   201  			mt.RunOpts(tc.desc, validatorOpts, func(mt *mtest.T) {
   202  				// Insert two valid documents so that the Update* tests can try to update them.
   203  				{
   204  					_, err := mt.Coll.InsertMany(
   205  						context.Background(),
   206  						[]interface{}{
   207  							bson.D{{"a", "str1"}, {"b", 1}},
   208  							bson.D{{"a", "str2"}, {"b", 2}},
   209  						})
   210  					assert.Nil(mt, err, "unexpected error inserting valid documents: %s", err)
   211  				}
   212  
   213  				err := tc.operation(mt.Coll)
   214  				assert.NotNil(mt, err, "expected an error from calling the operation")
   215  				sErr := err.(mongo.ServerError)
   216  				assert.True(
   217  					mt,
   218  					sErr.HasErrorCode(121),
   219  					"expected mongo.ServerError to have error code 121 (DocumentValidationFailure)")
   220  
   221  				var details bson.Raw
   222  				if tc.expectBulkError {
   223  					bwe, ok := err.(mongo.BulkWriteException)
   224  					assert.True(
   225  						mt,
   226  						ok,
   227  						"expected error to be type mongo.BulkWriteException, got type %T (error %q)",
   228  						err,
   229  						err)
   230  					// Assert that there is one WriteError and that the Details field is populated.
   231  					assert.Equal(
   232  						mt,
   233  						1,
   234  						len(bwe.WriteErrors),
   235  						"expected exactly 1 write error, but got %d write errors (error %q)",
   236  						len(bwe.WriteErrors),
   237  						err)
   238  					details = bwe.WriteErrors[0].Details
   239  				} else {
   240  					we, ok := err.(mongo.WriteException)
   241  					assert.True(
   242  						mt,
   243  						ok,
   244  						"expected error to be type mongo.WriteException, got type %T (error %q)",
   245  						err,
   246  						err)
   247  					// Assert that there is one WriteError and that the Details field is populated.
   248  					assert.Equal(
   249  						mt,
   250  						1,
   251  						len(we.WriteErrors),
   252  						"expected exactly 1 write error, but got %d write errors (error %q)",
   253  						len(we.WriteErrors),
   254  						err)
   255  					details = we.WriteErrors[0].Details
   256  				}
   257  
   258  				assert.True(
   259  					mt,
   260  					len(details) > 0,
   261  					"expected WriteError.Details to be populated, but is empty")
   262  
   263  				// Assert that the most recent CommandSucceededEvent was triggered by the expected
   264  				// operation and contains the resulting write errors and that
   265  				// "writeErrors[0].errInfo" is the same as "WriteException.WriteErrors[0].Details".
   266  				evts := mt.GetAllSucceededEvents()
   267  				assert.True(
   268  					mt,
   269  					len(evts) >= 2,
   270  					"expected there to be at least 2 CommandSucceededEvent recorded")
   271  				evt := evts[len(evts)-1]
   272  				assert.Equal(
   273  					mt,
   274  					tc.expectedCommandName,
   275  					evt.CommandName,
   276  					"expected the last CommandSucceededEvent to be for %q, was %q",
   277  					tc.expectedCommandName,
   278  					evt.CommandName)
   279  				errInfo, ok := evt.Reply.Lookup("writeErrors", "0", "errInfo").DocumentOK()
   280  				assert.True(
   281  					mt,
   282  					ok,
   283  					"expected evt.Reply to contain writeErrors[0].errInfo but doesn't (evt.Reply = %v)",
   284  					evt.Reply)
   285  				assert.Equal(mt, details, errInfo, "want %v, got %v", details, errInfo)
   286  			})
   287  		}
   288  	})
   289  }
   290  
   291  func TestHintErrors(t *testing.T) {
   292  	mtOpts := mtest.NewOptions().MaxServerVersion("3.2").CreateClient(false)
   293  	mt := mtest.New(t, mtOpts)
   294  
   295  	expected := errors.New("the 'hint' command parameter requires a minimum server wire version of 5")
   296  	mt.Run("UpdateMany", func(mt *mtest.T) {
   297  
   298  		_, got := mt.Coll.UpdateMany(context.Background(), bson.D{{"a", 1}}, bson.D{{"$inc", bson.D{{"a", 1}}}},
   299  			options.Update().SetHint("_id_"))
   300  		assert.NotNil(mt, got, "expected non-nil error, got nil")
   301  		assert.Equal(mt, got, expected, "expected: %v got: %v", expected, got)
   302  	})
   303  
   304  	mt.Run("ReplaceOne", func(mt *mtest.T) {
   305  
   306  		_, got := mt.Coll.ReplaceOne(context.Background(), bson.D{{"a", 1}}, bson.D{{"a", 2}},
   307  			options.Replace().SetHint("_id_"))
   308  		assert.NotNil(mt, got, "expected non-nil error, got nil")
   309  		assert.Equal(mt, got, expected, "expected: %v got: %v", expected, got)
   310  	})
   311  
   312  	mt.Run("BulkWrite", func(mt *mtest.T) {
   313  		models := []mongo.WriteModel{
   314  			&mongo.InsertOneModel{bson.D{{"_id", 2}}},
   315  			&mongo.ReplaceOneModel{Filter: bson.D{{"_id", 2}}, Replacement: bson.D{{"a", 2}}, Hint: "_id_"},
   316  		}
   317  		_, got := mt.Coll.BulkWrite(context.Background(), models)
   318  		assert.NotNil(mt, got, "expected non-nil error, got nil")
   319  		assert.Equal(mt, got, expected, "expected: %v got: %v", expected, got)
   320  	})
   321  }
   322  
   323  func TestWriteConcernError(t *testing.T) {
   324  	mt := mtest.New(t, noClientOpts)
   325  
   326  	errInfoOpts := mtest.NewOptions().MinServerVersion("4.0").Topologies(mtest.ReplicaSet)
   327  	mt.RunOpts("errInfo is propagated", errInfoOpts, func(mt *mtest.T) {
   328  		wcDoc := bsoncore.BuildDocumentFromElements(nil,
   329  			bsoncore.AppendInt32Element(nil, "w", 2),
   330  			bsoncore.AppendInt32Element(nil, "wtimeout", 0),
   331  			bsoncore.AppendStringElement(nil, "provenance", "clientSupplied"),
   332  		)
   333  		errInfoDoc := bsoncore.BuildDocumentFromElements(nil,
   334  			bsoncore.AppendDocumentElement(nil, "writeConcern", wcDoc),
   335  		)
   336  		fp := mtest.FailPoint{
   337  			ConfigureFailPoint: "failCommand",
   338  			Mode: mtest.FailPointMode{
   339  				Times: 1,
   340  			},
   341  			Data: mtest.FailPointData{
   342  				FailCommands: []string{"insert"},
   343  				WriteConcernError: &mtest.WriteConcernErrorData{
   344  					Code:    100,
   345  					Name:    "UnsatisfiableWriteConcern",
   346  					Errmsg:  "Not enough data-bearing nodes",
   347  					ErrInfo: errInfoDoc,
   348  				},
   349  			},
   350  		}
   351  		mt.SetFailPoint(fp)
   352  
   353  		_, err := mt.Coll.InsertOne(context.Background(), bson.D{{"x", 1}})
   354  		assert.NotNil(mt, err, "expected InsertOne error, got nil")
   355  		writeException, ok := err.(mongo.WriteException)
   356  		assert.True(mt, ok, "expected WriteException, got error %v of type %T", err, err)
   357  		wcError := writeException.WriteConcernError
   358  		assert.NotNil(mt, wcError, "expected write-concern error, got %v", err)
   359  		assert.True(mt, bytes.Equal(wcError.Details, errInfoDoc), "expected errInfo document %v, got %v",
   360  			bson.Raw(errInfoDoc), wcError.Details)
   361  	})
   362  }
   363  
   364  func TestErrorsCodeNamePropagated(t *testing.T) {
   365  	// Ensure the codeName field is propagated for both command and write concern errors.
   366  
   367  	mtOpts := mtest.NewOptions().
   368  		Topologies(mtest.ReplicaSet).
   369  		CreateClient(false)
   370  	mt := mtest.New(t, mtOpts)
   371  
   372  	mt.RunOpts("command error", mtest.NewOptions().MinServerVersion("3.4"), func(mt *mtest.T) {
   373  		// codeName is propagated in an ok:0 error.
   374  
   375  		cmd := bson.D{
   376  			{"insert", mt.Coll.Name()},
   377  			{"documents", []bson.D{}},
   378  		}
   379  		err := mt.DB.RunCommand(context.Background(), cmd).Err()
   380  		assert.NotNil(mt, err, "expected RunCommand error, got nil")
   381  
   382  		ce, ok := err.(mongo.CommandError)
   383  		assert.True(mt, ok, "expected error of type %T, got %v of type %T", mongo.CommandError{}, err, err)
   384  		expectedCodeName := "InvalidLength"
   385  		assert.Equal(mt, expectedCodeName, ce.Name, "expected error code name %q, got %q", expectedCodeName, ce.Name)
   386  	})
   387  
   388  	wcCollOpts := options.Collection().
   389  		SetWriteConcern(impossibleWc)
   390  	wcMtOpts := mtest.NewOptions().
   391  		CollectionOptions(wcCollOpts)
   392  	mt.RunOpts("write concern error", wcMtOpts, func(mt *mtest.T) {
   393  		// codeName is propagated for write concern errors.
   394  
   395  		_, err := mt.Coll.InsertOne(context.Background(), bson.D{})
   396  		assert.NotNil(mt, err, "expected InsertOne error, got nil")
   397  
   398  		we, ok := err.(mongo.WriteException)
   399  		assert.True(mt, ok, "expected error of type %T, got %v of type %T", mongo.WriteException{}, err, err)
   400  		wce := we.WriteConcernError
   401  		assert.NotNil(mt, wce, "expected write concern error, got %v", we)
   402  
   403  		var expectedCodeName string
   404  		if codeNameVal, err := mt.GetSucceededEvent().Reply.LookupErr("writeConcernError", "codeName"); err == nil {
   405  			expectedCodeName = codeNameVal.StringValue()
   406  		}
   407  
   408  		assert.Equal(mt, expectedCodeName, wce.Name, "expected code name %q, got %q", expectedCodeName, wce.Name)
   409  	})
   410  }
   411  

View as plain text