...

Source file src/go.mongodb.org/mongo-driver/mongo/mongo_test.go

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

     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 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  		// TODO: Registry? DecodeOptions?
    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 // Capture range variable.
   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  			// Ensure that if the unmarshaled "_id" value is a
   128  			// primitive.ObjectID that it is a deep copy and does not share any
   129  			// memory with the document byte slice.
   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  	// []byte of [{{"$limit", 12345}}]
   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  	// []byte of {{"x", 1}}
   169  	index, doc := bsoncore.AppendDocumentStart(nil)
   170  	doc = bsoncore.AppendInt32Element(doc, "x", 1)
   171  	doc, _ = bsoncore.AppendDocumentEnd(doc, index)
   172  
   173  	// bsoncore.Array of [{{"$merge", {}}}]
   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  	// bsoncore.Array of [{{"foo", "bar"}}, {{"baz", "qux"}}, {{"$out", "myColl"}}]
   185  	arrOutStage := bsoncore.NewArrayBuilder().
   186  		AppendDocument(fooStage).
   187  		AppendDocument(bazStage).
   188  		AppendDocument(outStage).
   189  		Build()
   190  
   191  	// bsoncore.Array of [{{"foo", "bar"}}, {{"$out", "myColl"}}, {{"baz", "qux"}}]
   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 // Capture range variable.
   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