1
2
3
4
5
6
7 package mongo
8
9 import (
10 "errors"
11 "fmt"
12 "testing"
13
14 "go.mongodb.org/mongo-driver/bson"
15 "go.mongodb.org/mongo-driver/bson/bsoncodec"
16 "go.mongodb.org/mongo-driver/bson/bsontype"
17 "go.mongodb.org/mongo-driver/bson/primitive"
18 "go.mongodb.org/mongo-driver/internal/assert"
19 "go.mongodb.org/mongo-driver/internal/codecutil"
20 "go.mongodb.org/mongo-driver/internal/require"
21 "go.mongodb.org/mongo-driver/mongo/options"
22 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
23 )
24
25 func TestEnsureID(t *testing.T) {
26 t.Parallel()
27
28 oid := primitive.NewObjectID()
29
30 testCases := []struct {
31 description string
32
33 doc bsoncore.Document
34 oid primitive.ObjectID
35 want bsoncore.Document
36 wantID interface{}
37 }{
38 {
39 description: "missing _id should be first element",
40 doc: bsoncore.NewDocumentBuilder().
41 AppendString("foo", "bar").
42 AppendString("baz", "quix").
43 AppendString("hello", "world").
44 Build(),
45 want: bsoncore.NewDocumentBuilder().
46 AppendObjectID("_id", oid).
47 AppendString("foo", "bar").
48 AppendString("baz", "quix").
49 AppendString("hello", "world").
50 Build(),
51 wantID: oid,
52 },
53 {
54 description: "existing ObjectID _id as should remain in place",
55 doc: bsoncore.NewDocumentBuilder().
56 AppendString("foo", "bar").
57 AppendObjectID("_id", oid).
58 AppendString("baz", "quix").
59 AppendString("hello", "world").
60 Build(),
61 want: bsoncore.NewDocumentBuilder().
62 AppendString("foo", "bar").
63 AppendObjectID("_id", oid).
64 AppendString("baz", "quix").
65 AppendString("hello", "world").
66 Build(),
67 wantID: oid,
68 },
69 {
70 description: "existing float _id as should remain in place",
71 doc: bsoncore.NewDocumentBuilder().
72 AppendString("foo", "bar").
73 AppendDouble("_id", 3.14159).
74 AppendString("baz", "quix").
75 AppendString("hello", "world").
76 Build(),
77 want: bsoncore.NewDocumentBuilder().
78 AppendString("foo", "bar").
79 AppendDouble("_id", 3.14159).
80 AppendString("baz", "quix").
81 AppendString("hello", "world").
82 Build(),
83 wantID: 3.14159,
84 },
85 {
86 description: "existing float _id as first element should remain first element",
87 doc: bsoncore.NewDocumentBuilder().
88 AppendDouble("_id", 3.14159).
89 AppendString("foo", "bar").
90 AppendString("baz", "quix").
91 AppendString("hello", "world").
92 Build(),
93 want: bsoncore.NewDocumentBuilder().
94 AppendDouble("_id", 3.14159).
95 AppendString("foo", "bar").
96 AppendString("baz", "quix").
97 AppendString("hello", "world").
98 Build(),
99 wantID: 3.14159,
100 },
101 {
102 description: "existing binary _id as first field should not be overwritten",
103 doc: bsoncore.NewDocumentBuilder().
104 AppendBinary("bin", 0, []byte{0, 0, 0}).
105 AppendString("_id", "LongEnoughIdentifier").
106 Build(),
107 want: bsoncore.NewDocumentBuilder().
108 AppendBinary("bin", 0, []byte{0, 0, 0}).
109 AppendString("_id", "LongEnoughIdentifier").
110 Build(),
111 wantID: "LongEnoughIdentifier",
112 },
113 }
114
115 for _, tc := range testCases {
116 tc := tc
117
118 t.Run(tc.description, func(t *testing.T) {
119 t.Parallel()
120
121 got, gotID, err := ensureID(tc.doc, oid, nil, nil)
122 require.NoError(t, err, "ensureID error")
123
124 assert.Equal(t, tc.want, got, "expected and actual documents are different")
125 assert.Equal(t, tc.wantID, gotID, "expected and actual IDs are different")
126
127
128
129
130 if oid, ok := gotID.(primitive.ObjectID); ok {
131 assert.DifferentAddressRanges(t, tc.doc, oid[:])
132 }
133 })
134 }
135 }
136
137 func TestEnsureID_NilObjectID(t *testing.T) {
138 t.Parallel()
139
140 doc := bsoncore.NewDocumentBuilder().
141 AppendString("foo", "bar").
142 Build()
143
144 got, gotIDI, err := ensureID(doc, primitive.NilObjectID, nil, nil)
145 assert.NoError(t, err)
146
147 gotID, ok := gotIDI.(primitive.ObjectID)
148
149 assert.True(t, ok)
150 assert.NotEqual(t, primitive.NilObjectID, gotID)
151
152 want := bsoncore.NewDocumentBuilder().
153 AppendObjectID("_id", gotID).
154 AppendString("foo", "bar").
155 Build()
156
157 assert.Equal(t, want, got)
158 }
159
160 func TestMarshalAggregatePipeline(t *testing.T) {
161
162 index, arr := bsoncore.AppendArrayStart(nil)
163 dindex, arr := bsoncore.AppendDocumentElementStart(arr, "0")
164 arr = bsoncore.AppendInt32Element(arr, "$limit", 12345)
165 arr, _ = bsoncore.AppendDocumentEnd(arr, dindex)
166 arr, _ = bsoncore.AppendArrayEnd(arr, index)
167
168
169 index, doc := bsoncore.AppendDocumentStart(nil)
170 doc = bsoncore.AppendInt32Element(doc, "x", 1)
171 doc, _ = bsoncore.AppendDocumentEnd(doc, index)
172
173
174 mergeStage := bsoncore.NewDocumentBuilder().
175 StartDocument("$merge").
176 FinishDocument().
177 Build()
178 arrMergeStage := bsoncore.NewArrayBuilder().AppendDocument(mergeStage).Build()
179
180 fooStage := bsoncore.NewDocumentBuilder().AppendString("foo", "bar").Build()
181 bazStage := bsoncore.NewDocumentBuilder().AppendString("baz", "qux").Build()
182 outStage := bsoncore.NewDocumentBuilder().AppendString("$out", "myColl").Build()
183
184
185 arrOutStage := bsoncore.NewArrayBuilder().
186 AppendDocument(fooStage).
187 AppendDocument(bazStage).
188 AppendDocument(outStage).
189 Build()
190
191
192 arrMiddleOutStage := bsoncore.NewArrayBuilder().
193 AppendDocument(fooStage).
194 AppendDocument(outStage).
195 AppendDocument(bazStage).
196 Build()
197
198 testCases := []struct {
199 name string
200 pipeline interface{}
201 arr bson.A
202 hasOutputStage bool
203 err error
204 }{
205 {
206 "Pipeline/error",
207 Pipeline{{{"hello", func() {}}}},
208 nil,
209 false,
210 MarshalError{Value: primitive.D{}, Err: errors.New("no encoder found for func()")},
211 },
212 {
213 "Pipeline/success",
214 Pipeline{{{"hello", "world"}}, {{"pi", 3.14159}}},
215 bson.A{
216 bson.D{{"hello", "world"}},
217 bson.D{{"pi", 3.14159}},
218 },
219 false,
220 nil,
221 },
222 {
223 "bson.A",
224 bson.A{
225 bson.D{{"$limit", 12345}},
226 },
227 bson.A{
228 bson.D{{"$limit", 12345}},
229 },
230 false,
231 nil,
232 },
233 {
234 "[]bson.D",
235 []bson.D{{{"$limit", 12345}}},
236 bson.A{
237 bson.D{{"$limit", 12345}},
238 },
239 false,
240 nil,
241 },
242 {
243 "primitive.A/error",
244 primitive.A{"5"},
245 nil,
246 false,
247 MarshalError{Value: "", Err: errors.New("WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel")},
248 },
249 {
250 "primitive.A/success",
251 primitive.A{bson.D{{"$limit", int32(12345)}}, map[string]interface{}{"$count": "foobar"}},
252 bson.A{
253 bson.D{{"$limit", int(12345)}},
254 bson.D{{"$count", "foobar"}},
255 },
256 false,
257 nil,
258 },
259 {
260 "bson.A/error",
261 bson.A{"5"},
262 nil,
263 false,
264 MarshalError{Value: "", Err: errors.New("WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel")},
265 },
266 {
267 "bson.A/success",
268 bson.A{bson.D{{"$limit", int32(12345)}}, map[string]interface{}{"$count": "foobar"}},
269 bson.A{
270 bson.D{{"$limit", int32(12345)}},
271 bson.D{{"$count", "foobar"}},
272 },
273 false,
274 nil,
275 },
276 {
277 "[]interface{}/error",
278 []interface{}{"5"},
279 nil,
280 false,
281 MarshalError{Value: "", Err: errors.New("WriteString can only write while positioned on a Element or Value but is positioned on a TopLevel")},
282 },
283 {
284 "[]interface{}/success",
285 []interface{}{bson.D{{"$limit", int32(12345)}}, map[string]interface{}{"$count": "foobar"}},
286 bson.A{
287 bson.D{{"$limit", int32(12345)}},
288 bson.D{{"$count", "foobar"}},
289 },
290 false,
291 nil,
292 },
293 {
294 "bsoncodec.ValueMarshaler/MarshalBSONValue error",
295 bvMarsh{err: errors.New("MarshalBSONValue error")},
296 nil,
297 false,
298 errors.New("MarshalBSONValue error"),
299 },
300 {
301 "bsoncodec.ValueMarshaler/not array",
302 bvMarsh{t: bsontype.String},
303 nil,
304 false,
305 fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v", bsontype.String, bsontype.Array),
306 },
307 {
308 "bsoncodec.ValueMarshaler/UnmarshalBSONValue error",
309 bvMarsh{err: errors.New("UnmarshalBSONValue error")},
310 nil,
311 false,
312 errors.New("UnmarshalBSONValue error"),
313 },
314 {
315 "bsoncodec.ValueMarshaler/success",
316 bvMarsh{t: bsontype.Array, data: arr},
317 bson.A{
318 bson.D{{"$limit", int32(12345)}},
319 },
320 false,
321 nil,
322 },
323 {
324 "bsoncodec.ValueMarshaler/success nil",
325 bvMarsh{t: bsontype.Array},
326 nil,
327 false,
328 nil,
329 },
330 {
331 "nil",
332 nil,
333 nil,
334 false,
335 errors.New("can only marshal slices and arrays into aggregation pipelines, but got invalid"),
336 },
337 {
338 "not array or slice",
339 int64(42),
340 nil,
341 false,
342 errors.New("can only marshal slices and arrays into aggregation pipelines, but got int64"),
343 },
344 {
345 "array/error",
346 [1]interface{}{int64(42)},
347 nil,
348 false,
349 MarshalError{Value: int64(0), Err: errors.New("WriteInt64 can only write while positioned on a Element or Value but is positioned on a TopLevel")},
350 },
351 {
352 "array/success",
353 [1]interface{}{primitive.D{{"$limit", int64(12345)}}},
354 bson.A{
355 bson.D{{"$limit", int64(12345)}},
356 },
357 false,
358 nil,
359 },
360 {
361 "slice/error",
362 []interface{}{int64(42)},
363 nil,
364 false,
365 MarshalError{Value: int64(0), Err: errors.New("WriteInt64 can only write while positioned on a Element or Value but is positioned on a TopLevel")},
366 },
367 {
368 "slice/success",
369 []interface{}{primitive.D{{"$limit", int64(12345)}}},
370 bson.A{
371 bson.D{{"$limit", int64(12345)}},
372 },
373 false,
374 nil,
375 },
376 {
377 "hasOutputStage/out",
378 bson.A{
379 bson.D{{"$out", bson.D{
380 {"db", "output-db"},
381 {"coll", "output-collection"},
382 }}},
383 },
384 bson.A{
385 bson.D{{"$out", bson.D{
386 {"db", "output-db"},
387 {"coll", "output-collection"},
388 }}},
389 },
390 true,
391 nil,
392 },
393 {
394 "hasOutputStage/merge",
395 bson.A{
396 bson.D{{"$merge", bson.D{
397 {"into", bson.D{
398 {"db", "output-db"},
399 {"coll", "output-collection"},
400 }},
401 }}},
402 },
403 bson.A{
404 bson.D{{"$merge", bson.D{
405 {"into", bson.D{
406 {"db", "output-db"},
407 {"coll", "output-collection"},
408 }},
409 }}},
410 },
411 true,
412 nil,
413 },
414 {
415 "semantic single document/bson.D",
416 bson.D{{"x", 1}},
417 nil,
418 false,
419 errors.New("primitive.D is not an allowed pipeline type as it represents a single document. Use bson.A or mongo.Pipeline instead"),
420 },
421 {
422 "semantic single document/bson.Raw",
423 bson.Raw(doc),
424 nil,
425 false,
426 errors.New("bson.Raw is not an allowed pipeline type as it represents a single document. Use bson.A or mongo.Pipeline instead"),
427 },
428 {
429 "semantic single document/bsoncore.Document",
430 bsoncore.Document(doc),
431 nil,
432 false,
433 errors.New("bsoncore.Document is not an allowed pipeline type as it represents a single document. Use bson.A or mongo.Pipeline instead"),
434 },
435 {
436 "semantic single document/empty bson.D",
437 bson.D{},
438 bson.A{},
439 false,
440 nil,
441 },
442 {
443 "semantic single document/empty bson.Raw",
444 bson.Raw{},
445 bson.A{},
446 false,
447 nil,
448 },
449 {
450 "semantic single document/empty bsoncore.Document",
451 bsoncore.Document{},
452 bson.A{},
453 false,
454 nil,
455 },
456 {
457 "bsoncore.Array/success",
458 bsoncore.Array(arr),
459 bson.A{
460 bson.D{{"$limit", int32(12345)}},
461 },
462 false,
463 nil,
464 },
465 {
466 "bsoncore.Array/mergeStage",
467 arrMergeStage,
468 bson.A{
469 bson.D{{"$merge", bson.D{}}},
470 },
471 true,
472 nil,
473 },
474 {
475 "bsoncore.Array/outStage",
476 arrOutStage,
477 bson.A{
478 bson.D{{"foo", "bar"}},
479 bson.D{{"baz", "qux"}},
480 bson.D{{"$out", "myColl"}},
481 },
482 true,
483 nil,
484 },
485 {
486 "bsoncore.Array/middleOutStage",
487 arrMiddleOutStage,
488 bson.A{
489 bson.D{{"foo", "bar"}},
490 bson.D{{"$out", "myColl"}},
491 bson.D{{"baz", "qux"}},
492 },
493 false,
494 nil,
495 },
496 }
497
498 for _, tc := range testCases {
499 t.Run(tc.name, func(t *testing.T) {
500 arr, hasOutputStage, err := marshalAggregatePipeline(tc.pipeline, nil, nil)
501 assert.Equal(t, tc.hasOutputStage, hasOutputStage, "expected hasOutputStage %v, got %v",
502 tc.hasOutputStage, hasOutputStage)
503 if tc.err != nil {
504 assert.NotNil(t, err)
505 assert.EqualError(t, err, tc.err.Error())
506 } else {
507 assert.Nil(t, err)
508 }
509
510 var expected bsoncore.Document
511 if tc.arr != nil {
512 _, expectedBSON, err := bson.MarshalValue(tc.arr)
513 assert.Nil(t, err, "MarshalValue error: %v", err)
514 expected = bsoncore.Document(expectedBSON)
515 }
516 assert.Equal(t, expected, arr, "expected array %v, got %v", expected, arr)
517 })
518 }
519 }
520
521 func TestMarshalValue(t *testing.T) {
522 t.Parallel()
523
524 valueMarshaler := bvMarsh{
525 t: bson.TypeString,
526 data: bsoncore.AppendString(nil, "foo"),
527 }
528
529 testCases := []struct {
530 name string
531 value interface{}
532 bsonOpts *options.BSONOptions
533 registry *bsoncodec.Registry
534 want bsoncore.Value
535 wantErr error
536 }{
537 {
538 name: "nil document",
539 value: nil,
540 wantErr: codecutil.ErrNilValue,
541 },
542 {
543 name: "value marshaler",
544 value: valueMarshaler,
545 want: bsoncore.Value{
546 Type: valueMarshaler.t,
547 Data: valueMarshaler.data,
548 },
549 },
550 {
551 name: "document",
552 value: bson.D{{Key: "x", Value: int64(1)}},
553 want: bsoncore.Value{
554 Type: bson.TypeEmbeddedDocument,
555 Data: bsoncore.NewDocumentBuilder().
556 AppendInt64("x", 1).
557 Build(),
558 },
559 },
560 {
561 name: "custom encode options",
562 value: struct {
563 Int int64
564 NilBytes []byte
565 NilMap map[string]interface{}
566 NilStrings []string
567 ZeroStruct struct{ X int } `bson:"_,omitempty"`
568 StringerMap map[*bson.RawValue]bool
569 BSONField string `json:"jsonField"`
570 }{
571 Int: 1,
572 NilBytes: nil,
573 NilMap: nil,
574 NilStrings: nil,
575 StringerMap: map[*bson.RawValue]bool{{}: true},
576 },
577 bsonOpts: &options.BSONOptions{
578 IntMinSize: true,
579 NilByteSliceAsEmpty: true,
580 NilMapAsEmpty: true,
581 NilSliceAsEmpty: true,
582 OmitZeroStruct: true,
583 StringifyMapKeysWithFmt: true,
584 UseJSONStructTags: true,
585 },
586 want: bsoncore.Value{
587 Type: bson.TypeEmbeddedDocument,
588 Data: bsoncore.NewDocumentBuilder().
589 AppendInt32("int", 1).
590 AppendBinary("nilbytes", 0, []byte{}).
591 AppendDocument("nilmap", bsoncore.NewDocumentBuilder().Build()).
592 AppendArray("nilstrings", bsoncore.NewArrayBuilder().Build()).
593 AppendDocument("stringermap", bsoncore.NewDocumentBuilder().
594 AppendBoolean("", true).
595 Build()).
596 AppendString("jsonField", "").
597 Build(),
598 },
599 },
600 }
601 for _, tc := range testCases {
602 tc := tc
603
604 t.Run(tc.name, func(t *testing.T) {
605 t.Parallel()
606
607 got, err := marshalValue(tc.value, tc.bsonOpts, tc.registry)
608 assert.EqualBSON(t, tc.want, got)
609 assert.Equal(t, tc.wantErr, err, "expected and actual error do not match")
610 })
611 }
612 }
613
614 var _ bsoncodec.ValueMarshaler = bvMarsh{}
615
616 type bvMarsh struct {
617 t bsontype.Type
618 data []byte
619 err error
620 }
621
622 func (b bvMarsh) MarshalBSONValue() (bsontype.Type, []byte, error) {
623 return b.t, b.data, b.err
624 }
625
View as plain text