1package bsonx
2
3import (
4	"reflect"
5	"testing"
6	"time"
7
8	"github.com/google/go-cmp/cmp"
9	"go.mongodb.org/mongo-driver/bson"
10	"go.mongodb.org/mongo-driver/bson/primitive"
11	"go.mongodb.org/mongo-driver/internal/testutil/assert"
12)
13
14func TestReflectionFreeDCodec(t *testing.T) {
15	assert.RegisterOpts(reflect.TypeOf(primitive.D{}), cmp.AllowUnexported(primitive.Decimal128{}))
16
17	now := time.Now()
18	oid := primitive.NewObjectID()
19	d128 := primitive.NewDecimal128(10, 20)
20	js := primitive.JavaScript("js")
21	symbol := primitive.Symbol("sybmol")
22	binary := primitive.Binary{Subtype: 2, Data: []byte("binary")}
23	datetime := primitive.NewDateTimeFromTime(now)
24	regex := primitive.Regex{Pattern: "pattern", Options: "i"}
25	dbPointer := primitive.DBPointer{DB: "db", Pointer: oid}
26	timestamp := primitive.Timestamp{T: 5, I: 10}
27	cws := primitive.CodeWithScope{Code: js, Scope: bson.D{{"x", 1}}}
28	noReflectionRegistry := bson.NewRegistryBuilder().RegisterCodec(tPrimitiveD, ReflectionFreeDCodec).Build()
29	docWithAllTypes := primitive.D{
30		{"byteSlice", []byte("foobar")},
31		{"sliceByteSlice", [][]byte{[]byte("foobar")}},
32		{"timeTime", now},
33		{"sliceTimeTime", []time.Time{now}},
34		{"objectID", oid},
35		{"sliceObjectID", []primitive.ObjectID{oid}},
36		{"decimal128", d128},
37		{"sliceDecimal128", []primitive.Decimal128{d128}},
38		{"js", js},
39		{"sliceJS", []primitive.JavaScript{js}},
40		{"symbol", symbol},
41		{"sliceSymbol", []primitive.Symbol{symbol}},
42		{"binary", binary},
43		{"sliceBinary", []primitive.Binary{binary}},
44		{"undefined", primitive.Undefined{}},
45		{"sliceUndefined", []primitive.Undefined{{}}},
46		{"datetime", datetime},
47		{"sliceDateTime", []primitive.DateTime{datetime}},
48		{"null", primitive.Null{}},
49		{"sliceNull", []primitive.Null{{}}},
50		{"regex", regex},
51		{"sliceRegex", []primitive.Regex{regex}},
52		{"dbPointer", dbPointer},
53		{"sliceDBPointer", []primitive.DBPointer{dbPointer}},
54		{"timestamp", timestamp},
55		{"sliceTimestamp", []primitive.Timestamp{timestamp}},
56		{"minKey", primitive.MinKey{}},
57		{"sliceMinKey", []primitive.MinKey{{}}},
58		{"maxKey", primitive.MaxKey{}},
59		{"sliceMaxKey", []primitive.MaxKey{{}}},
60		{"cws", cws},
61		{"sliceCWS", []primitive.CodeWithScope{cws}},
62		{"bool", true},
63		{"sliceBool", []bool{true}},
64		{"int", int(10)},
65		{"sliceInt", []int{10}},
66		{"int8", int8(10)},
67		{"sliceInt8", []int8{10}},
68		{"int16", int16(10)},
69		{"sliceInt16", []int16{10}},
70		{"int32", int32(10)},
71		{"sliceInt32", []int32{10}},
72		{"int64", int64(10)},
73		{"sliceInt64", []int64{10}},
74		{"uint", uint(10)},
75		{"sliceUint", []uint{10}},
76		{"uint8", uint8(10)},
77		{"sliceUint8", []uint8{10}},
78		{"uint16", uint16(10)},
79		{"sliceUint16", []uint16{10}},
80		{"uint32", uint32(10)},
81		{"sliceUint32", []uint32{10}},
82		{"uint64", uint64(10)},
83		{"sliceUint64", []uint64{10}},
84		{"float32", float32(10)},
85		{"sliceFloat32", []float32{10}},
86		{"float64", float64(10)},
87		{"sliceFloat64", []float64{10}},
88		{"primitiveA", primitive.A{"foo", "bar"}},
89	}
90
91	t.Run("encode", func(t *testing.T) {
92		// Assert that bson.Marshal returns the same result when using the default registry and noReflectionRegistry.
93
94		expected, err := bson.Marshal(docWithAllTypes)
95		assert.Nil(t, err, "Marshal error with default registry: %v", err)
96		actual, err := bson.MarshalWithRegistry(noReflectionRegistry, docWithAllTypes)
97		assert.Nil(t, err, "Marshal error with noReflectionRegistry: %v", err)
98		assert.Equal(t, expected, actual, "expected doc %s, got %s", bson.Raw(expected), bson.Raw(actual))
99	})
100	t.Run("decode", func(t *testing.T) {
101		// Assert that bson.Unmarshal returns the same result when using the default registry and noReflectionRegistry.
102
103		// docWithAllTypes contains some types that can't be roundtripped. For example, any slices besides primitive.A
104		// would start of as []T but unmarshal to primitive.A. To get around this, we first marshal docWithAllTypes to
105		// raw bytes and then Unmarshal to another primitive.D rather than asserting directly against docWithAllTypes.
106		docBytes, err := bson.Marshal(docWithAllTypes)
107		assert.Nil(t, err, "Marshal error: %v", err)
108
109		var expected, actual primitive.D
110		err = bson.Unmarshal(docBytes, &expected)
111		assert.Nil(t, err, "Unmarshal error with default registry: %v", err)
112		err = bson.UnmarshalWithRegistry(noReflectionRegistry, docBytes, &actual)
113		assert.Nil(t, err, "Unmarshal error with noReflectionRegistry: %v", err)
114
115		assert.Equal(t, expected, actual, "expected document %v, got %v", expected, actual)
116	})
117}
118