bson/raw_array_test.go
2025-03-17 20:58:26 +01:00

473 lines
10 KiB
Go

// Copyright (C) MongoDB, Inc. 2024-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"encoding/binary"
"errors"
"io"
"testing"
"gitea.psichedelico.com/go/bson/internal/assert"
"gitea.psichedelico.com/go/bson/internal/require"
"gitea.psichedelico.com/go/bson/x/bsonx/bsoncore"
"github.com/google/go-cmp/cmp"
)
func TestReadArray(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
ioReader io.Reader
arr RawArray
err error
}{
{
"nil reader",
nil,
nil,
ErrNilReader,
},
{
"premature end of reader",
bytes.NewBuffer([]byte{}),
nil,
io.EOF,
},
{
"empty Array",
bytes.NewBuffer([]byte{5, 0, 0, 0, 0}),
[]byte{5, 0, 0, 0, 0},
nil,
},
{
"non-empty Array",
bytes.NewBuffer([]byte{
'\x1B', '\x00', '\x00', '\x00',
'\x02',
'0', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x72', '\x00',
'\x02',
'1', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x7a', '\x00',
'\x00',
}),
[]byte{
'\x1B', '\x00', '\x00', '\x00',
'\x02',
'0', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x72', '\x00',
'\x02',
'1', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x7a', '\x00',
'\x00',
},
nil,
},
}
for _, tc := range testCases {
tc := tc // Capture range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
arr, err := ReadArray(tc.ioReader)
if !assert.CompareErrors(err, tc.err) {
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
}
if !bytes.Equal(tc.arr, arr) {
t.Errorf("Arrays differ. got %v; want %v", tc.arr, arr)
}
})
}
}
func TestArray_Validate(t *testing.T) {
t.Parallel()
t.Run("TooShort", func(t *testing.T) {
t.Parallel()
want := bsoncore.NewInsufficientBytesError(nil, nil)
got := RawArray{'\x00', '\x00'}.Validate()
if !assert.CompareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("InvalidLength", func(t *testing.T) {
t.Parallel()
want := bsoncore.NewArrayLengthError(200, 5)
r := make(RawArray, 5)
binary.LittleEndian.PutUint32(r[0:4], 200)
got := r.Validate()
if !assert.CompareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("Invalid Element", func(t *testing.T) {
t.Parallel()
want := bsoncore.NewInsufficientBytesError(nil, nil)
r := make(RawArray, 7)
binary.LittleEndian.PutUint32(r[0:4], 7)
r[4], r[5], r[6] = 0x02, 'f', 0x00
got := r.Validate()
if !assert.CompareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("Missing Null Terminator", func(t *testing.T) {
t.Parallel()
want := bsoncore.ErrMissingNull
r := make(RawArray, 6)
binary.LittleEndian.PutUint32(r[0:4], 6)
r[4], r[5] = 0x0A, '0'
got := r.Validate()
if !assert.CompareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
testCases := []struct {
name string
r RawArray
want error
}{
{"array null", RawArray{'\x08', '\x00', '\x00', '\x00', '\x0A', '0', '\x00', '\x00'}, nil},
{"array",
RawArray{
'\x1B', '\x00', '\x00', '\x00',
'\x02',
'0', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x72', '\x00',
'\x02',
'1', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x7a', '\x00',
'\x00',
},
nil,
},
{"subarray",
RawArray{
'\x13', '\x00', '\x00', '\x00',
'\x04',
'0', '\x00',
'\x0B', '\x00', '\x00', '\x00', '\x0A', '0', '\x00',
'\x0A', '1', '\x00', '\x00', '\x00',
},
nil,
},
{"invalid key order",
RawArray{
'\x0B', '\x00', '\x00', '\x00', '\x0A', '2', '\x00',
'\x0A', '0', '\x00', '\x00', '\x00',
},
errors.New(`array key "2" is out of order or invalid`),
},
{"invalid key type",
RawArray{
'\x0B', '\x00', '\x00', '\x00', '\x0A', 'p', '\x00',
'\x0A', 'q', '\x00', '\x00', '\x00',
},
errors.New(`array key "p" is out of order or invalid`),
},
}
for _, tc := range testCases {
tc := tc // Capture the range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := tc.r.Validate()
if !assert.CompareErrors(got, tc.want) {
t.Errorf("Returned error does not match. got %v; want %v", got, tc.want)
}
})
}
}
func TestArray_Index(t *testing.T) {
t.Parallel()
t.Run("Out of bounds", func(t *testing.T) {
t.Parallel()
rdr := RawArray{0xe, 0x0, 0x0, 0x0, 0xa, '0', 0x0, 0xa, '1', 0x0, 0xa, 0x7a, 0x0, 0x0}
_, err := rdr.IndexErr(3)
if !errors.Is(err, bsoncore.ErrOutOfBounds) {
t.Errorf("Out of bounds should be returned when accessing element beyond end of Array. got %v; want %v", err,
bsoncore.ErrOutOfBounds)
}
})
t.Run("Validation Error", func(t *testing.T) {
t.Parallel()
rdr := RawArray{0x07, 0x00, 0x00, 0x00, 0x00}
_, got := rdr.IndexErr(1)
want := bsoncore.NewInsufficientBytesError(nil, nil)
if !assert.CompareErrors(got, want) {
t.Errorf("Did not receive expected error. got %v; want %v", got, want)
}
})
testArray := RawArray{
'\x26', '\x00', '\x00', '\x00',
'\x02',
'0', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x72', '\x00',
'\x02',
'1', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x7a', '\x00',
'\x02',
'2', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x71', '\x75', '\x78', '\x00',
'\x00',
}
testCases := []struct {
name string
index uint
want RawValue
}{
{"first",
0,
RawValue{
Type: TypeString,
Value: []byte{0x04, 0x00, 0x00, 0x00, 0x62, 0x61, 0x72, 0x00},
},
},
{"second",
1,
RawValue{
Type: TypeString,
Value: []byte{0x04, 0x00, 0x00, 0x00, 0x62, 0x61, 0x7a, 0x00},
},
},
{"third",
2,
RawValue{
Type: TypeString,
Value: []byte{0x04, 0x00, 0x00, 0x00, 0x71, 0x75, 0x78, 0x00},
},
},
}
for _, tc := range testCases {
tc := tc // Capture the range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
t.Run("IndexErr", func(t *testing.T) {
t.Parallel()
got, err := testArray.IndexErr(tc.index)
if err != nil {
t.Errorf("Unexpected error from IndexErr: %s", err)
}
if diff := cmp.Diff(got, tc.want); diff != "" {
t.Errorf("Arrays differ: (-got +want)\n%s", diff)
}
})
t.Run("Index", func(t *testing.T) {
t.Parallel()
defer func() {
if err := recover(); err != nil {
t.Errorf("Unexpected error: %v", err)
}
}()
got := testArray.Index(tc.index)
if diff := cmp.Diff(got, tc.want); diff != "" {
t.Errorf("Arrays differ: (-got +want)\n%s", diff)
}
})
})
}
}
func TestRawArray_Stringt(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
arr RawArray
arrayString string
arrayDebugString string
}{
{
"array",
RawArray{
'\x1B', '\x00', '\x00', '\x00',
'\x02',
'0', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x72', '\x00',
'\x02',
'1', '\x00',
'\x04', '\x00', '\x00', '\x00',
'\x62', '\x61', '\x7a', '\x00',
'\x00',
},
`["bar","baz"]`,
`Array(27)["bar","baz"]`,
},
{
"subarray",
RawArray{
'\x13', '\x00', '\x00', '\x00',
'\x04',
'0', '\x00',
'\x0B', '\x00', '\x00', '\x00',
'\x0A', '0', '\x00',
'\x0A', '1', '\x00',
'\x00', '\x00',
},
`[[null,null]]`,
`Array(19)[Array(11)[null,null]]`,
},
{
"malformed--length too small",
RawArray{
'\x04', '\x00', '\x00', '\x00',
'\x00',
},
``,
`Array(4)[]`,
},
{
"malformed--length too large",
RawArray{
'\x13', '\x00', '\x00', '\x00',
'\x00',
},
``,
`Array(19)[<malformed (15)>]`,
},
{
"malformed--missing null byte",
RawArray{
'\x06', '\x00', '\x00', '\x00',
'\x02', '0',
},
``,
`Array(6)[<malformed (2)>]`,
},
}
for _, tc := range testCases {
tc := tc // Capture the range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
arrayString := tc.arr.String()
if arrayString != tc.arrayString {
t.Errorf("array strings do not match. got %q; want %q",
arrayString, tc.arrayString)
}
arrayDebugString := tc.arr.DebugString()
if arrayDebugString != tc.arrayDebugString {
t.Errorf("array debug strings do not match. got %q; want %q",
arrayDebugString, tc.arrayDebugString)
}
})
}
}
func TestRawArray_Values(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
arr RawArray
want []RawValue
}{
{
name: "empty",
arr: []byte{0x05, 0x00, 0x00, 0x00, 0x00},
want: []RawValue{},
},
{
name: "null document",
arr: []byte{0x07, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00}, // [{}]
want: []RawValue{
{
Type: TypeNull,
Value: []byte{},
},
},
},
{
name: "same",
arr: []byte{0x13, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x01, 0x00, 0x00,
0x00, 0x10, 0x31, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00}, // [int32(1), int32(2)]
want: []RawValue{
{ // int32(1)
Type: TypeInt32,
Value: []byte{0x01, 0x00, 0x00, 0x00},
},
{ // int32(2)
Type: TypeInt32,
Value: []byte{0x02, 0x00, 0x00, 0x00},
},
},
},
{
name: "mixed",
arr: []byte{0x17, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x31, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x6F, 0x6F, 0x00, 0x00}, // [int32(1), "foo"]
want: []RawValue{
{ // int32(1)
Type: TypeInt32,
Value: []byte{0x01, 0x00, 0x00, 0x00},
},
{ // "foo"
Type: TypeString,
Value: []byte{0x04, 0x00, 0x00, 0x00, 0x66, 0x6F, 0x6F, 0x00},
},
},
},
}
for _, tcase := range testCases {
tcase := tcase
t.Run(tcase.name, func(t *testing.T) {
t.Parallel()
values, err := tcase.arr.Values()
require.NoError(t, err, "failed to turn array into values")
require.Len(t, values, len(tcase.want), "got len does not match want")
for idx, want := range tcase.want {
assert.True(t, want.Equal(values[idx]), "want: %v, got: %v", want, values[idx])
}
})
}
}