1
2
3
4
5
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
129
130
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
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
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
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
188
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
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
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
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
264
265
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
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
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
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