...

Source file src/go.mongodb.org/mongo-driver/bson/encoder_test.go

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

     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 bson
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"reflect"
    13  	"testing"
    14  
    15  	"go.mongodb.org/mongo-driver/bson/bsoncodec"
    16  	"go.mongodb.org/mongo-driver/bson/bsonrw"
    17  	"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
    18  	"go.mongodb.org/mongo-driver/bson/bsontype"
    19  	"go.mongodb.org/mongo-driver/internal/assert"
    20  	"go.mongodb.org/mongo-driver/internal/require"
    21  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    22  )
    23  
    24  func TestBasicEncode(t *testing.T) {
    25  	for _, tc := range marshalingTestCases {
    26  		t.Run(tc.name, func(t *testing.T) {
    27  			got := make(bsonrw.SliceWriter, 0, 1024)
    28  			vw, err := bsonrw.NewBSONValueWriter(&got)
    29  			noerr(t, err)
    30  			reg := DefaultRegistry
    31  			encoder, err := reg.LookupEncoder(reflect.TypeOf(tc.val))
    32  			noerr(t, err)
    33  			err = encoder.EncodeValue(bsoncodec.EncodeContext{Registry: reg}, vw, reflect.ValueOf(tc.val))
    34  			noerr(t, err)
    35  
    36  			if !bytes.Equal(got, tc.want) {
    37  				t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
    38  				t.Errorf("Bytes:\n%v\n%v", got, tc.want)
    39  			}
    40  		})
    41  	}
    42  }
    43  
    44  func TestEncoderEncode(t *testing.T) {
    45  	for _, tc := range marshalingTestCases {
    46  		t.Run(tc.name, func(t *testing.T) {
    47  			got := make(bsonrw.SliceWriter, 0, 1024)
    48  			vw, err := bsonrw.NewBSONValueWriter(&got)
    49  			noerr(t, err)
    50  			enc, err := NewEncoder(vw)
    51  			noerr(t, err)
    52  			err = enc.Encode(tc.val)
    53  			noerr(t, err)
    54  
    55  			if !bytes.Equal(got, tc.want) {
    56  				t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
    57  				t.Errorf("Bytes:\n%v\n%v", got, tc.want)
    58  			}
    59  		})
    60  	}
    61  
    62  	t.Run("Marshaler", func(t *testing.T) {
    63  		testCases := []struct {
    64  			name    string
    65  			buf     []byte
    66  			err     error
    67  			wanterr error
    68  			vw      bsonrw.ValueWriter
    69  		}{
    70  			{
    71  				"error",
    72  				nil,
    73  				errors.New("Marshaler error"),
    74  				errors.New("Marshaler error"),
    75  				&bsonrwtest.ValueReaderWriter{},
    76  			},
    77  			{
    78  				"copy error",
    79  				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
    80  				nil,
    81  				errors.New("copy error"),
    82  				&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.WriteDocument},
    83  			},
    84  			{
    85  				"success",
    86  				[]byte{0x07, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00},
    87  				nil,
    88  				nil,
    89  				nil,
    90  			},
    91  		}
    92  
    93  		for _, tc := range testCases {
    94  			t.Run(tc.name, func(t *testing.T) {
    95  				marshaler := testMarshaler{buf: tc.buf, err: tc.err}
    96  
    97  				var vw bsonrw.ValueWriter
    98  				var err error
    99  				b := make(bsonrw.SliceWriter, 0, 100)
   100  				compareVW := false
   101  				if tc.vw != nil {
   102  					vw = tc.vw
   103  				} else {
   104  					compareVW = true
   105  					vw, err = bsonrw.NewBSONValueWriter(&b)
   106  					noerr(t, err)
   107  				}
   108  				enc, err := NewEncoder(vw)
   109  				noerr(t, err)
   110  				got := enc.Encode(marshaler)
   111  				want := tc.wanterr
   112  				if !compareErrors(got, want) {
   113  					t.Errorf("Did not receive expected error. got %v; want %v", got, want)
   114  				}
   115  				if compareVW {
   116  					buf := b
   117  					if !bytes.Equal(buf, tc.buf) {
   118  						t.Errorf("Copied bytes do not match. got %v; want %v", buf, tc.buf)
   119  					}
   120  				}
   121  			})
   122  		}
   123  	})
   124  }
   125  
   126  type testMarshaler struct {
   127  	buf []byte
   128  	err error
   129  }
   130  
   131  func (tm testMarshaler) MarshalBSON() ([]byte, error) { return tm.buf, tm.err }
   132  
   133  func docToBytes(d interface{}) []byte {
   134  	b, err := Marshal(d)
   135  	if err != nil {
   136  		panic(err)
   137  	}
   138  	return b
   139  }
   140  
   141  type stringerTest struct{}
   142  
   143  func (stringerTest) String() string {
   144  	return "test key"
   145  }
   146  
   147  func TestEncoderConfiguration(t *testing.T) {
   148  	type inlineDuplicateInner struct {
   149  		Duplicate string
   150  	}
   151  
   152  	type inlineDuplicateOuter struct {
   153  		Inline    inlineDuplicateInner `bson:",inline"`
   154  		Duplicate string
   155  	}
   156  
   157  	type zeroStruct struct {
   158  		MyString string
   159  	}
   160  
   161  	testCases := []struct {
   162  		description string
   163  		configure   func(*Encoder)
   164  		input       interface{}
   165  		want        []byte
   166  		wantErr     error
   167  	}{
   168  		// Test that ErrorOnInlineDuplicates causes the Encoder to return an error if there are any
   169  		// duplicate fields in the marshaled document caused by using the "inline" struct tag.
   170  		{
   171  			description: "ErrorOnInlineDuplicates",
   172  			configure: func(enc *Encoder) {
   173  				enc.ErrorOnInlineDuplicates()
   174  			},
   175  			input: inlineDuplicateOuter{
   176  				Inline:    inlineDuplicateInner{Duplicate: "inner"},
   177  				Duplicate: "outer",
   178  			},
   179  			wantErr: errors.New("struct bson.inlineDuplicateOuter has duplicated key duplicate"),
   180  		},
   181  		// Test that IntMinSize encodes Go int and int64 values as BSON int32 if the value is small
   182  		// enough.
   183  		{
   184  			description: "IntMinSize",
   185  			configure: func(enc *Encoder) {
   186  				enc.IntMinSize()
   187  			},
   188  			input: D{
   189  				{Key: "myInt", Value: int(1)},
   190  				{Key: "myInt64", Value: int64(1)},
   191  				{Key: "myUint", Value: uint(1)},
   192  				{Key: "myUint32", Value: uint32(1)},
   193  				{Key: "myUint64", Value: uint64(1)},
   194  			},
   195  			want: bsoncore.NewDocumentBuilder().
   196  				AppendInt32("myInt", 1).
   197  				AppendInt32("myInt64", 1).
   198  				AppendInt32("myUint", 1).
   199  				AppendInt32("myUint32", 1).
   200  				AppendInt32("myUint64", 1).
   201  				Build(),
   202  		},
   203  		// Test that StringifyMapKeysWithFmt uses fmt.Sprint to convert map keys to BSON field names.
   204  		{
   205  			description: "StringifyMapKeysWithFmt",
   206  			configure: func(enc *Encoder) {
   207  				enc.StringifyMapKeysWithFmt()
   208  			},
   209  			input: map[stringerTest]string{
   210  				{}: "test value",
   211  			},
   212  			want: bsoncore.NewDocumentBuilder().
   213  				AppendString("test key", "test value").
   214  				Build(),
   215  		},
   216  		// Test that NilMapAsEmpty encodes nil Go maps as empty BSON documents.
   217  		{
   218  			description: "NilMapAsEmpty",
   219  			configure: func(enc *Encoder) {
   220  				enc.NilMapAsEmpty()
   221  			},
   222  			input: D{{Key: "myMap", Value: map[string]string(nil)}},
   223  			want: bsoncore.NewDocumentBuilder().
   224  				AppendDocument("myMap", bsoncore.NewDocumentBuilder().Build()).
   225  				Build(),
   226  		},
   227  		// Test that NilSliceAsEmpty encodes nil Go slices as empty BSON arrays.
   228  		{
   229  			description: "NilSliceAsEmpty",
   230  			configure: func(enc *Encoder) {
   231  				enc.NilSliceAsEmpty()
   232  			},
   233  			input: D{{Key: "mySlice", Value: []string(nil)}},
   234  			want: bsoncore.NewDocumentBuilder().
   235  				AppendArray("mySlice", bsoncore.NewArrayBuilder().Build()).
   236  				Build(),
   237  		},
   238  		// Test that NilByteSliceAsEmpty encodes nil Go byte slices as empty BSON binary elements.
   239  		{
   240  			description: "NilByteSliceAsEmpty",
   241  			configure: func(enc *Encoder) {
   242  				enc.NilByteSliceAsEmpty()
   243  			},
   244  			input: D{{Key: "myBytes", Value: []byte(nil)}},
   245  			want: bsoncore.NewDocumentBuilder().
   246  				AppendBinary("myBytes", bsontype.BinaryGeneric, []byte{}).
   247  				Build(),
   248  		},
   249  		// Test that OmitZeroStruct omits empty structs from the marshaled document if the
   250  		// "omitempty" struct tag is used.
   251  		{
   252  			description: "OmitZeroStruct",
   253  			configure: func(enc *Encoder) {
   254  				enc.OmitZeroStruct()
   255  			},
   256  			input: struct {
   257  				Zero zeroStruct `bson:",omitempty"`
   258  			}{},
   259  			want: bsoncore.NewDocumentBuilder().Build(),
   260  		},
   261  		// Test that UseJSONStructTags causes the Encoder to fall back to "json" struct tags if
   262  		// "bson" struct tags are not available.
   263  		{
   264  			description: "UseJSONStructTags",
   265  			configure: func(enc *Encoder) {
   266  				enc.UseJSONStructTags()
   267  			},
   268  			input: struct {
   269  				StructFieldName string `json:"jsonFieldName"`
   270  			}{
   271  				StructFieldName: "test value",
   272  			},
   273  			want: bsoncore.NewDocumentBuilder().
   274  				AppendString("jsonFieldName", "test value").
   275  				Build(),
   276  		},
   277  	}
   278  
   279  	for _, tc := range testCases {
   280  		tc := tc // Capture range variable.
   281  
   282  		t.Run(tc.description, func(t *testing.T) {
   283  			t.Parallel()
   284  
   285  			got := new(bytes.Buffer)
   286  			vw, err := bsonrw.NewBSONValueWriter(got)
   287  			require.NoError(t, err, "bsonrw.NewBSONValueWriter error")
   288  			enc, err := NewEncoder(vw)
   289  			require.NoError(t, err, "NewEncoder error")
   290  
   291  			tc.configure(enc)
   292  
   293  			err = enc.Encode(tc.input)
   294  			if tc.wantErr != nil {
   295  				assert.Equal(t, tc.wantErr, err, "expected and actual errors do not match")
   296  				return
   297  			}
   298  			require.NoError(t, err, "Encode error")
   299  
   300  			assert.Equal(t, tc.want, got.Bytes(), "expected and actual encoded BSON do not match")
   301  
   302  			// After we compare the raw bytes, also decode the expected and actual BSON as a bson.D
   303  			// and compare them. The goal is to make assertion failures easier to debug because
   304  			// binary diffs are very difficult to understand.
   305  			var wantDoc D
   306  			err = Unmarshal(tc.want, &wantDoc)
   307  			require.NoError(t, err, "Unmarshal error")
   308  			var gotDoc D
   309  			err = Unmarshal(got.Bytes(), &gotDoc)
   310  			require.NoError(t, err, "Unmarshal error")
   311  
   312  			assert.Equal(t, wantDoc, gotDoc, "expected and actual decoded documents do not match")
   313  		})
   314  	}
   315  }
   316  

View as plain text