...

Source file src/go.mongodb.org/mongo-driver/mongo/integration/unified/matches_test.go

Documentation: go.mongodb.org/mongo-driver/mongo/integration/unified

     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 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}, // null should not be considered unset
   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