1
2
3
4
5
6
7 package unified
8
9 import (
10 "context"
11 "encoding/hex"
12 "testing"
13
14 "go.mongodb.org/mongo-driver/bson"
15 "go.mongodb.org/mongo-driver/bson/bsontype"
16 "go.mongodb.org/mongo-driver/internal/assert"
17 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
18 )
19
20 func TestMatches(t *testing.T) {
21 ctx := context.Background()
22 unmarshalExtJSONValue := func(t *testing.T, str string) bson.RawValue {
23 t.Helper()
24
25 if str == "" {
26 return bson.RawValue{}
27 }
28
29 var val bson.RawValue
30 err := bson.UnmarshalExtJSON([]byte(str), false, &val)
31 assert.Nil(t, err, "UnmarshalExtJSON error: %v", err)
32 return val
33 }
34 marshalValue := func(t *testing.T, val interface{}) bson.RawValue {
35 t.Helper()
36
37 valType, data, err := bson.MarshalValue(val)
38 assert.Nil(t, err, "MarshalValue error: %v", err)
39 return bson.RawValue{
40 Type: valType,
41 Value: data,
42 }
43 }
44
45 assertMatches := func(t *testing.T, expected, actual bson.RawValue, shouldMatch bool) {
46 t.Helper()
47
48 err := verifyValuesMatch(ctx, expected, actual, true)
49 if shouldMatch {
50 assert.Nil(t, err, "expected values to match, but got comparison error %v", err)
51 return
52 }
53 assert.NotNil(t, err, "expected values to not match, but got no error")
54 }
55
56 t.Run("documents with extra keys allowed", func(t *testing.T) {
57 expectedDoc := `{"x": 1, "y": {"a": 1, "b": 2}}`
58 expectedVal := unmarshalExtJSONValue(t, expectedDoc)
59
60 extraKeysAtRoot := `{"x": 1, "y": {"a": 1, "b": 2}, "z": 3}`
61 extraKeysInEmbedded := `{"x": 1, "y": {"a": 1, "b": 2, "c": 3}}`
62
63 testCases := []struct {
64 name string
65 actual string
66 matches bool
67 }{
68 {"exact match", expectedDoc, true},
69 {"extra keys allowed at root-level", extraKeysAtRoot, true},
70 {"incorrect type for y", `{"x": 1, "y": 2}`, false},
71 {"extra keys prohibited in embedded documents", extraKeysInEmbedded, false},
72 }
73 for _, tc := range testCases {
74 t.Run(tc.name, func(t *testing.T) {
75 assertMatches(t, expectedVal, unmarshalExtJSONValue(t, tc.actual), tc.matches)
76 })
77 }
78 })
79 t.Run("documents with extra keys not allowed", func(t *testing.T) {
80 expected := unmarshalExtJSONValue(t, `{"x": 1}`)
81 actual := unmarshalExtJSONValue(t, `{"x": 1, "y": 1}`)
82 err := verifyValuesMatch(ctx, expected, actual, false)
83 assert.NotNil(t, err, "expected values to not match, but got no error")
84 })
85 t.Run("exists operator", func(t *testing.T) {
86 rootExists := unmarshalExtJSONValue(t, `{"x": {"$$exists": true}}`)
87 rootNotExists := unmarshalExtJSONValue(t, `{"x": {"$$exists": false}}`)
88 embeddedExists := unmarshalExtJSONValue(t, `{"x": {"y": {"$$exists": true}}}`)
89 embeddedNotExists := unmarshalExtJSONValue(t, `{"x": {"y": {"$$exists": false}}}`)
90
91 testCases := []struct {
92 name string
93 expected bson.RawValue
94 actual string
95 matches bool
96 }{
97 {"root - should exist and does", rootExists, `{"x": 1}`, true},
98 {"root - should exist and does not", rootExists, `{"y": 1}`, false},
99 {"root - should not exist and does", rootNotExists, `{"x": 1}`, false},
100 {"root - should not exist and does not", rootNotExists, `{"y": 1}`, true},
101 {"embedded - should exist and does", embeddedExists, `{"x": {"y": 1}}`, true},
102 {"embedded - should exist and does not", embeddedExists, `{"x": {}}`, false},
103 {"embedded - should not exist and does", embeddedNotExists, `{"x": {"y": 1}}`, false},
104 {"embedded - should not exist and does not", embeddedNotExists, `{"x": {}}`, true},
105 }
106 for _, tc := range testCases {
107 t.Run(tc.name, func(t *testing.T) {
108 assertMatches(t, tc.expected, unmarshalExtJSONValue(t, tc.actual), tc.matches)
109 })
110 }
111 })
112 t.Run("type operator", func(t *testing.T) {
113 singleType := unmarshalExtJSONValue(t, `{"x": {"$$type": "string"}}`)
114 arrayTypes := unmarshalExtJSONValue(t, `{"x": {"$$type": ["string", "bool"]}}`)
115
116 testCases := []struct {
117 name string
118 expected bson.RawValue
119 actual string
120 matches bool
121 }{
122 {"single type matches", singleType, `{"x": "foo"}`, true},
123 {"single type does not match", singleType, `{"x": 1}`, false},
124 {"multiple types matches first", arrayTypes, `{"x": "foo"}`, true},
125 {"multiple types matches second", arrayTypes, `{"x": true}`, true},
126 {"multiple types does not match", arrayTypes, `{"x": 1}`, false},
127 }
128 for _, tc := range testCases {
129 t.Run(tc.name, func(t *testing.T) {
130 assertMatches(t, tc.expected, unmarshalExtJSONValue(t, tc.actual), tc.matches)
131 })
132 }
133 })
134 t.Run("matchesHexBytes operator", func(t *testing.T) {
135 singleValue := unmarshalExtJSONValue(t, `{"$$matchesHexBytes": "DEADBEEF"}`)
136 document := unmarshalExtJSONValue(t, `{"x": {"$$matchesHexBytes": "DEADBEEF"}}`)
137
138 stringToBinary := func(str string) bson.RawValue {
139 hexBytes, err := hex.DecodeString(str)
140 assert.Nil(t, err, "hex.DecodeString error: %v", err)
141 return bson.RawValue{
142 Type: bsontype.Binary,
143 Value: bsoncore.AppendBinary(nil, 0, hexBytes),
144 }
145 }
146
147 testCases := []struct {
148 name string
149 expected bson.RawValue
150 actual bson.RawValue
151 matches bool
152 }{
153 {"single value matches", singleValue, stringToBinary("DEADBEEF"), true},
154 {"single value does not match", singleValue, stringToBinary("BEEF"), false},
155 {"document matches", document, marshalValue(t, bson.M{"x": stringToBinary("DEADBEEF")}), true},
156 {"document does not match", document, marshalValue(t, bson.M{"x": stringToBinary("BEEF")}), false},
157 }
158 for _, tc := range testCases {
159 t.Run(tc.name, func(t *testing.T) {
160 assertMatches(t, tc.expected, tc.actual, tc.matches)
161 })
162 }
163 })
164 t.Run("unsetOrMatches operator", func(t *testing.T) {
165 topLevel := unmarshalExtJSONValue(t, `{"$$unsetOrMatches": {"x": 1}}`)
166 nested := unmarshalExtJSONValue(t, `{"x": {"$$unsetOrMatches": {"y": 1}}}`)
167
168 testCases := []struct {
169 name string
170 expected bson.RawValue
171 actual string
172 matches bool
173 }{
174 {"top-level unset", topLevel, "", true},
175 {"top-level matches", topLevel, `{"x": 1}`, true},
176 {"top-level matches with extra keys", topLevel, `{"x": 1, "y": 1}`, true},
177 {"top-level does not match", topLevel, `{"x": 2}`, false},
178 {"nested unset", nested, `{}`, true},
179 {"nested matches", nested, `{"x": {"y": 1}}`, true},
180 {"nested field exists but is null", nested, `{"x": null}`, false},
181 {"nested does not match", nested, `{"x": {"y": 2}}`, false},
182 {"nested does not match due to extra keys", nested, `{"x": {"y": 1, "z": 1}}`, false},
183 }
184 for _, tc := range testCases {
185 t.Run(tc.name, func(t *testing.T) {
186 assertMatches(t, tc.expected, unmarshalExtJSONValue(t, tc.actual), tc.matches)
187 })
188 }
189 })
190 }
191
View as plain text