...

Source file src/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document_test.go

Documentation: go.mongodb.org/mongo-driver/x/bsonx/bsoncore

     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 bsoncore
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/binary"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  	"go.mongodb.org/mongo-driver/bson/bsontype"
    19  )
    20  
    21  func ExampleDocument_Validate() {
    22  	doc := make(Document, 500)
    23  	doc[250], doc[251], doc[252], doc[253], doc[254] = 0x05, 0x00, 0x00, 0x00, 0x00
    24  	err := doc[250:].Validate()
    25  	fmt.Println(err)
    26  
    27  	// Output: <nil>
    28  }
    29  
    30  func BenchmarkDocumentValidate(b *testing.B) {
    31  	for i := 0; i < b.N; i++ {
    32  		doc := make(Document, 500)
    33  		doc[250], doc[251], doc[252], doc[253], doc[254] = 0x05, 0x00, 0x00, 0x00, 0x00
    34  		_ = doc[250:].Validate()
    35  	}
    36  }
    37  
    38  func TestDocument(t *testing.T) {
    39  	t.Run("Validate", func(t *testing.T) {
    40  		t.Run("TooShort", func(t *testing.T) {
    41  			want := NewInsufficientBytesError(nil, nil)
    42  			got := Document{'\x00', '\x00'}.Validate()
    43  			if !compareErrors(got, want) {
    44  				t.Errorf("Did not get expected error. got %v; want %v", got, want)
    45  			}
    46  		})
    47  		t.Run("InvalidLength", func(t *testing.T) {
    48  			want := NewDocumentLengthError(200, 5)
    49  			r := make(Document, 5)
    50  			binary.LittleEndian.PutUint32(r[0:4], 200)
    51  			got := r.Validate()
    52  			if !compareErrors(got, want) {
    53  				t.Errorf("Did not get expected error. got %v; want %v", got, want)
    54  			}
    55  		})
    56  		t.Run("Invalid Element", func(t *testing.T) {
    57  			want := NewInsufficientBytesError(nil, nil)
    58  			r := make(Document, 9)
    59  			binary.LittleEndian.PutUint32(r[0:4], 9)
    60  			r[4], r[5], r[6], r[7], r[8] = 0x02, 'f', 'o', 'o', 0x00
    61  			got := r.Validate()
    62  			if !compareErrors(got, want) {
    63  				t.Errorf("Did not get expected error. got %v; want %v", got, want)
    64  			}
    65  		})
    66  		t.Run("Missing Null Terminator", func(t *testing.T) {
    67  			want := ErrMissingNull
    68  			r := make(Document, 8)
    69  			binary.LittleEndian.PutUint32(r[0:4], 8)
    70  			r[4], r[5], r[6], r[7] = 0x0A, 'f', 'o', 'o'
    71  			got := r.Validate()
    72  			if !compareErrors(got, want) {
    73  				t.Errorf("Did not get expected error. got %v; want %v", got, want)
    74  			}
    75  		})
    76  		testCases := []struct {
    77  			name string
    78  			r    Document
    79  			want error
    80  		}{
    81  			{"null", Document{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}, nil},
    82  			{"subdocument",
    83  				Document{
    84  					'\x15', '\x00', '\x00', '\x00',
    85  					'\x03',
    86  					'f', 'o', 'o', '\x00',
    87  					'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
    88  					'\x0A', 'b', '\x00', '\x00', '\x00',
    89  				},
    90  				nil,
    91  			},
    92  			{"array",
    93  				Document{
    94  					'\x15', '\x00', '\x00', '\x00',
    95  					'\x04',
    96  					'f', 'o', 'o', '\x00',
    97  					'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
    98  					'\x0A', '2', '\x00', '\x00', '\x00',
    99  				},
   100  				nil,
   101  			},
   102  		}
   103  
   104  		for _, tc := range testCases {
   105  			t.Run(tc.name, func(t *testing.T) {
   106  				got := tc.r.Validate()
   107  				if !compareErrors(got, tc.want) {
   108  					t.Errorf("Returned error does not match. got %v; want %v", got, tc.want)
   109  				}
   110  			})
   111  		}
   112  	})
   113  	t.Run("Lookup", func(t *testing.T) {
   114  		t.Run("empty-key", func(t *testing.T) {
   115  			rdr := Document{'\x05', '\x00', '\x00', '\x00', '\x00'}
   116  			_, err := rdr.LookupErr()
   117  			if !errors.Is(err, ErrEmptyKey) {
   118  				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, ErrEmptyKey)
   119  			}
   120  		})
   121  		t.Run("corrupted-subdocument", func(t *testing.T) {
   122  			rdr := Document{
   123  				'\x0D', '\x00', '\x00', '\x00',
   124  				'\x03', 'x', '\x00',
   125  				'\x06', '\x00', '\x00', '\x00',
   126  				'\x01',
   127  				'\x00',
   128  				'\x00',
   129  			}
   130  			_, got := rdr.LookupErr("x", "y")
   131  			want := NewInsufficientBytesError(nil, nil)
   132  			if !cmp.Equal(got, want) {
   133  				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
   134  			}
   135  		})
   136  		t.Run("corrupted-array", func(t *testing.T) {
   137  			rdr := Document{
   138  				'\x0D', '\x00', '\x00', '\x00',
   139  				'\x04', 'x', '\x00',
   140  				'\x06', '\x00', '\x00', '\x00',
   141  				'\x01',
   142  				'\x00',
   143  				'\x00',
   144  			}
   145  			_, got := rdr.LookupErr("x", "y")
   146  			want := NewInsufficientBytesError(nil, nil)
   147  			if !cmp.Equal(got, want) {
   148  				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
   149  			}
   150  		})
   151  		t.Run("invalid-traversal", func(t *testing.T) {
   152  			rdr := Document{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}
   153  			_, got := rdr.LookupErr("x", "y")
   154  			want := InvalidDepthTraversalError{Key: "x", Type: bsontype.Null}
   155  			if !compareErrors(got, want) {
   156  				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
   157  			}
   158  		})
   159  		testCases := []struct {
   160  			name string
   161  			r    Document
   162  			key  []string
   163  			want Value
   164  			err  error
   165  		}{
   166  			{"first",
   167  				Document{
   168  					'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00',
   169  				},
   170  				[]string{"x"},
   171  				Value{Type: bsontype.Null, Data: []byte{}},
   172  				nil,
   173  			},
   174  			{"first-second",
   175  				Document{
   176  					'\x15', '\x00', '\x00', '\x00',
   177  					'\x03',
   178  					'f', 'o', 'o', '\x00',
   179  					'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
   180  					'\x0A', 'b', '\x00', '\x00', '\x00',
   181  				},
   182  				[]string{"foo", "b"},
   183  				Value{Type: bsontype.Null, Data: []byte{}},
   184  				nil,
   185  			},
   186  			{"first-second-array",
   187  				Document{
   188  					'\x15', '\x00', '\x00', '\x00',
   189  					'\x04',
   190  					'f', 'o', 'o', '\x00',
   191  					'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
   192  					'\x0A', '2', '\x00', '\x00', '\x00',
   193  				},
   194  				[]string{"foo", "2"},
   195  				Value{Type: bsontype.Null, Data: []byte{}},
   196  				nil,
   197  			},
   198  		}
   199  
   200  		for _, tc := range testCases {
   201  			t.Run(tc.name, func(t *testing.T) {
   202  				t.Run("Lookup", func(t *testing.T) {
   203  					got := tc.r.Lookup(tc.key...)
   204  					if !cmp.Equal(got, tc.want) {
   205  						t.Errorf("Returned value does not match expected element. got %v; want %v", got, tc.want)
   206  					}
   207  				})
   208  				t.Run("LookupErr", func(t *testing.T) {
   209  					got, err := tc.r.LookupErr(tc.key...)
   210  					if !errors.Is(err, tc.err) {
   211  						t.Errorf("Returned error does not match. got %v; want %v", err, tc.err)
   212  					}
   213  					if !cmp.Equal(got, tc.want) {
   214  						t.Errorf("Returned value does not match expected element. got %v; want %v", got, tc.want)
   215  					}
   216  				})
   217  			})
   218  		}
   219  	})
   220  	t.Run("Index", func(t *testing.T) {
   221  		t.Run("Out of bounds", func(t *testing.T) {
   222  			rdr := Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0}
   223  			_, err := rdr.IndexErr(3)
   224  			if !errors.Is(err, ErrOutOfBounds) {
   225  				t.Errorf("Out of bounds should be returned when accessing element beyond end of document. got %v; want %v", err, ErrOutOfBounds)
   226  			}
   227  		})
   228  		t.Run("Validation Error", func(t *testing.T) {
   229  			rdr := Document{0x07, 0x00, 0x00, 0x00, 0x00}
   230  			_, got := rdr.IndexErr(1)
   231  			want := NewInsufficientBytesError(nil, nil)
   232  			if !compareErrors(got, want) {
   233  				t.Errorf("Did not receive expected error. got %v; want %v", got, want)
   234  			}
   235  		})
   236  		testCases := []struct {
   237  			name  string
   238  			rdr   Document
   239  			index uint
   240  			want  Element
   241  		}{
   242  			{"first",
   243  				Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
   244  				0, Element{0x0a, 0x78, 0x00},
   245  			},
   246  			{"second",
   247  				Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
   248  				1, Element{0x0a, 0x79, 0x00},
   249  			},
   250  			{"third",
   251  				Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
   252  				2, Element{0x0a, 0x7a, 0x00},
   253  			},
   254  		}
   255  
   256  		for _, tc := range testCases {
   257  			t.Run(tc.name, func(t *testing.T) {
   258  				t.Run("IndexErr", func(t *testing.T) {
   259  					got, err := tc.rdr.IndexErr(tc.index)
   260  					if err != nil {
   261  						t.Errorf("Unexpected error from IndexErr: %s", err)
   262  					}
   263  					if diff := cmp.Diff(got, tc.want); diff != "" {
   264  						t.Errorf("Documents differ: (-got +want)\n%s", diff)
   265  					}
   266  				})
   267  				t.Run("Index", func(t *testing.T) {
   268  					defer func() {
   269  						if err := recover(); err != nil {
   270  							t.Errorf("Unexpected error: %v", err)
   271  						}
   272  					}()
   273  					got := tc.rdr.Index(tc.index)
   274  					if diff := cmp.Diff(got, tc.want); diff != "" {
   275  						t.Errorf("Documents differ: (-got +want)\n%s", diff)
   276  					}
   277  				})
   278  			})
   279  		}
   280  	})
   281  	t.Run("NewDocumentFromReader", func(t *testing.T) {
   282  		testCases := []struct {
   283  			name     string
   284  			ioReader io.Reader
   285  			doc      Document
   286  			err      error
   287  		}{
   288  			{
   289  				"nil reader",
   290  				nil,
   291  				nil,
   292  				ErrNilReader,
   293  			},
   294  			{
   295  				"premature end of reader",
   296  				bytes.NewBuffer([]byte{}),
   297  				nil,
   298  				io.EOF,
   299  			},
   300  			{
   301  				"empty document",
   302  				bytes.NewBuffer([]byte{5, 0, 0, 0, 0}),
   303  				[]byte{5, 0, 0, 0, 0},
   304  				nil,
   305  			},
   306  			{
   307  				"non-empty document",
   308  				bytes.NewBuffer([]byte{
   309  					// length
   310  					0x17, 0x0, 0x0, 0x0,
   311  
   312  					// type - string
   313  					0x2,
   314  					// key - "foo"
   315  					0x66, 0x6f, 0x6f, 0x0,
   316  					// value - string length
   317  					0x4, 0x0, 0x0, 0x0,
   318  					// value - string "bar"
   319  					0x62, 0x61, 0x72, 0x0,
   320  
   321  					// type - null
   322  					0xa,
   323  					// key - "baz"
   324  					0x62, 0x61, 0x7a, 0x0,
   325  
   326  					// null terminator
   327  					0x0,
   328  				}),
   329  				[]byte{
   330  					// length
   331  					0x17, 0x0, 0x0, 0x0,
   332  
   333  					// type - string
   334  					0x2,
   335  					// key - "foo"
   336  					0x66, 0x6f, 0x6f, 0x0,
   337  					// value - string length
   338  					0x4, 0x0, 0x0, 0x0,
   339  					// value - string "bar"
   340  					0x62, 0x61, 0x72, 0x0,
   341  
   342  					// type - null
   343  					0xa,
   344  					// key - "baz"
   345  					0x62, 0x61, 0x7a, 0x0,
   346  
   347  					// null terminator
   348  					0x0,
   349  				},
   350  				nil,
   351  			},
   352  		}
   353  
   354  		for _, tc := range testCases {
   355  			t.Run(tc.name, func(t *testing.T) {
   356  				doc, err := NewDocumentFromReader(tc.ioReader)
   357  				if !compareErrors(err, tc.err) {
   358  					t.Errorf("errors do not match. got %v; want %v", err, tc.err)
   359  				}
   360  				if !bytes.Equal(tc.doc, doc) {
   361  					t.Errorf("documents differ. got %v; want %v", tc.doc, doc)
   362  				}
   363  			})
   364  		}
   365  	})
   366  	t.Run("Elements", func(t *testing.T) {
   367  		invalidElem := BuildDocument(nil, AppendHeader(nil, bsontype.Double, "foo"))
   368  		invalidTwoElem := BuildDocument(nil,
   369  			AppendHeader(
   370  				AppendDoubleElement(nil, "pi", 3.14159),
   371  				bsontype.Double, "foo",
   372  			),
   373  		)
   374  		oneElem := BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159))
   375  		twoElems := BuildDocument(nil,
   376  			AppendStringElement(
   377  				AppendDoubleElement(nil, "pi", 3.14159),
   378  				"hello", "world!",
   379  			),
   380  		)
   381  		testCases := []struct {
   382  			name  string
   383  			doc   Document
   384  			elems []Element
   385  			err   error
   386  		}{
   387  			{"Insufficient Bytes Length", Document{0x03, 0x00, 0x00}, nil, NewInsufficientBytesError(nil, nil)},
   388  			{"Insufficient Bytes First Element", invalidElem, nil, NewInsufficientBytesError(nil, nil)},
   389  			{"Insufficient Bytes Second Element", invalidTwoElem, []Element{AppendDoubleElement(nil, "pi", 3.14159)}, NewInsufficientBytesError(nil, nil)},
   390  			{"Success One Element", oneElem, []Element{AppendDoubleElement(nil, "pi", 3.14159)}, nil},
   391  			{"Success Two Elements", twoElems, []Element{AppendDoubleElement(nil, "pi", 3.14159), AppendStringElement(nil, "hello", "world!")}, nil},
   392  		}
   393  
   394  		for _, tc := range testCases {
   395  			t.Run(tc.name, func(t *testing.T) {
   396  				elems, err := tc.doc.Elements()
   397  				if !compareErrors(err, tc.err) {
   398  					t.Errorf("errors do not match. got %v; want %v", err, tc.err)
   399  				}
   400  				if len(elems) != len(tc.elems) {
   401  					t.Fatalf("number of elements returned does not match. got %d; want %d", len(elems), len(tc.elems))
   402  				}
   403  
   404  				for idx := range elems {
   405  					got, want := elems[idx], tc.elems[idx]
   406  					if !bytes.Equal(got, want) {
   407  						t.Errorf("Elements at index %d differ. got %v; want %v", idx, got.DebugString(), want.DebugString())
   408  					}
   409  				}
   410  			})
   411  		}
   412  	})
   413  }
   414  

View as plain text