github.com/prebid/prebid-server/v2@v2.18.0/util/jsonutil/merge_test.go (about)

     1  package jsonutil
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"github.com/prebid/prebid-server/v2/util/sliceutil"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func TestMergeClonePtr(t *testing.T) {
    15  	t.Run("root", func(t *testing.T) {
    16  		var (
    17  			banner      = &openrtb2.Banner{ID: "1"}
    18  			imp         = &openrtb2.Imp{Banner: banner}
    19  			impOriginal = imp
    20  		)
    21  
    22  		// root objects are not cloned
    23  		err := MergeClone(imp, []byte(`{"banner":{"id":"4"}}`))
    24  		require.NoError(t, err)
    25  
    26  		assert.Same(t, impOriginal, imp, "imp-ref")
    27  		assert.NotSame(t, imp.Banner, banner, "banner-ref")
    28  	})
    29  
    30  	t.Run("embedded-nil", func(t *testing.T) {
    31  		var (
    32  			banner = &openrtb2.Banner{ID: "1"}
    33  			video  = &openrtb2.Video{PodID: "a"}
    34  			imp    = &openrtb2.Imp{Banner: banner, Video: video}
    35  		)
    36  
    37  		err := MergeClone(imp, []byte(`{"banner":null}`))
    38  		require.NoError(t, err)
    39  
    40  		assert.NotSame(t, banner, imp.Banner, "banner-ref")
    41  		assert.Same(t, video, imp.Video, "video")
    42  		assert.Nil(t, imp.Banner, "banner-nil")
    43  	})
    44  
    45  	t.Run("embedded-struct", func(t *testing.T) {
    46  		var (
    47  			banner = &openrtb2.Banner{ID: "1"}
    48  			video  = &openrtb2.Video{PodID: "a"}
    49  			imp    = &openrtb2.Imp{Banner: banner, Video: video}
    50  		)
    51  
    52  		err := MergeClone(imp, []byte(`{"banner":{"id":"2"}}`))
    53  		require.NoError(t, err)
    54  
    55  		assert.NotSame(t, banner, imp.Banner, "banner-ref")
    56  		assert.Same(t, video, imp.Video, "video-ref")
    57  		assert.Equal(t, "1", banner.ID, "id-original")
    58  		assert.Equal(t, "2", imp.Banner.ID, "id-clone")
    59  	})
    60  
    61  	t.Run("embedded-int", func(t *testing.T) {
    62  		var (
    63  			clickbrowser = int8(1)
    64  			imp          = &openrtb2.Imp{ClickBrowser: &clickbrowser}
    65  		)
    66  
    67  		err := MergeClone(imp, []byte(`{"clickbrowser":2}`))
    68  		require.NoError(t, err)
    69  
    70  		require.NotNil(t, imp.ClickBrowser, "clickbrowser-nil")
    71  		assert.NotSame(t, clickbrowser, imp.ClickBrowser, "clickbrowser-ref")
    72  		assert.Equal(t, int8(2), *imp.ClickBrowser, "clickbrowser-val")
    73  	})
    74  
    75  	t.Run("invalid-null", func(t *testing.T) {
    76  		var (
    77  			banner = &openrtb2.Banner{ID: "1"}
    78  			imp    = &openrtb2.Imp{Banner: banner}
    79  		)
    80  
    81  		err := MergeClone(imp, []byte(`{"banner":nul}`))
    82  
    83  		// json-iter will produce an error since "nul" is not a valid json value. the
    84  		// parsing code will see the "n" and then expect "ull" to follow. the strange
    85  		// "expect ull" error being asserted is generated by json-iter.
    86  		require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.Banner: expect ull")
    87  	})
    88  
    89  	t.Run("invalid-malformed", func(t *testing.T) {
    90  		var (
    91  			banner = &openrtb2.Banner{ID: "1"}
    92  			imp    = &openrtb2.Imp{Banner: banner}
    93  		)
    94  
    95  		err := MergeClone(imp, []byte(`{"banner":malformed}`))
    96  		require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.Banner: expect { or n, but found m")
    97  	})
    98  }
    99  
   100  func TestMergeCloneSlice(t *testing.T) {
   101  	t.Run("null", func(t *testing.T) {
   102  		var (
   103  			iframeBuster = []string{"a", "b"}
   104  			imp          = &openrtb2.Imp{IframeBuster: iframeBuster}
   105  		)
   106  
   107  		err := MergeClone(imp, []byte(`{"iframeBuster":null}`))
   108  		require.NoError(t, err)
   109  
   110  		assert.Equal(t, []string{"a", "b"}, iframeBuster, "iframeBuster-val")
   111  		assert.Nil(t, imp.IframeBuster, "iframeBuster-nil")
   112  	})
   113  
   114  	t.Run("one", func(t *testing.T) {
   115  		var (
   116  			iframeBuster = []string{"a"}
   117  			imp          = &openrtb2.Imp{IframeBuster: iframeBuster}
   118  		)
   119  
   120  		err := MergeClone(imp, []byte(`{"iframeBuster":["b"]}`))
   121  		require.NoError(t, err)
   122  
   123  		assert.NotSame(t, iframeBuster, imp.IframeBuster, "ref")
   124  		assert.Equal(t, []string{"a"}, iframeBuster, "original-val")
   125  		assert.Equal(t, []string{"b"}, imp.IframeBuster, "new-val")
   126  	})
   127  
   128  	t.Run("many", func(t *testing.T) {
   129  		var (
   130  			iframeBuster = []string{"a"}
   131  			imp          = &openrtb2.Imp{IframeBuster: iframeBuster}
   132  		)
   133  
   134  		err := MergeClone(imp, []byte(`{"iframeBuster":["b", "c"]}`))
   135  		require.NoError(t, err)
   136  
   137  		assert.NotSame(t, iframeBuster, imp.IframeBuster, "ref")
   138  		assert.Equal(t, []string{"a"}, iframeBuster, "original-val")
   139  		assert.Equal(t, []string{"b", "c"}, imp.IframeBuster, "new-val")
   140  	})
   141  
   142  	t.Run("invalid-null", func(t *testing.T) {
   143  		var (
   144  			iframeBuster = []string{"a"}
   145  			imp          = &openrtb2.Imp{IframeBuster: iframeBuster}
   146  		)
   147  
   148  		err := MergeClone(imp, []byte(`{"iframeBuster":nul}`))
   149  
   150  		// json-iter will produce an error since "nul" is not a valid json value. the
   151  		// parsing code will see the "n" and then expect "ull" to follow. the strange
   152  		// "expect ull" error being asserted is generated by json-iter.
   153  		require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.IframeBuster: expect ull")
   154  	})
   155  
   156  	t.Run("invalid-malformed", func(t *testing.T) {
   157  		var (
   158  			iframeBuster = []string{"a"}
   159  			imp          = &openrtb2.Imp{IframeBuster: iframeBuster}
   160  		)
   161  
   162  		err := MergeClone(imp, []byte(`{"iframeBuster":malformed}`))
   163  		require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.IframeBuster: decode slice: expect [ or n, but found m")
   164  	})
   165  }
   166  
   167  func TestMergeCloneMap(t *testing.T) {
   168  	t.Run("null", func(t *testing.T) {
   169  		var (
   170  			testMap = map[string]int{"a": 1, "b": 2}
   171  			test    = &struct {
   172  				Foo map[string]int `json:"foo"`
   173  			}{Foo: testMap}
   174  		)
   175  
   176  		err := MergeClone(test, []byte(`{"foo":null}`))
   177  		require.NoError(t, err)
   178  
   179  		assert.NotSame(t, testMap, test.Foo, "ref")
   180  		assert.Equal(t, map[string]int{"a": 1, "b": 2}, testMap, "val")
   181  		assert.Nil(t, test.Foo, "nil")
   182  	})
   183  
   184  	t.Run("key-string", func(t *testing.T) {
   185  		var (
   186  			testMap = map[string]int{"a": 1, "b": 2}
   187  			test    = &struct {
   188  				Foo map[string]int `json:"foo"`
   189  			}{Foo: testMap}
   190  		)
   191  
   192  		err := MergeClone(test, []byte(`{"foo":{"c":3}}`))
   193  		require.NoError(t, err)
   194  
   195  		assert.NotSame(t, testMap, test.Foo)
   196  		assert.Equal(t, map[string]int{"a": 1, "b": 2}, testMap, "original-val")
   197  		assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, test.Foo, "new-val")
   198  
   199  		// verify modifications don't corrupt original
   200  		testMap["a"] = 10
   201  		assert.Equal(t, map[string]int{"a": 10, "b": 2}, testMap, "mod-original-val")
   202  		assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, test.Foo, "mod-ew-val")
   203  	})
   204  
   205  	t.Run("key-numeric", func(t *testing.T) {
   206  		var (
   207  			testMap = map[int]string{1: "a", 2: "b"}
   208  			test    = &struct {
   209  				Foo map[int]string `json:"foo"`
   210  			}{Foo: testMap}
   211  		)
   212  
   213  		err := MergeClone(test, []byte(`{"foo":{"3":"c"}}`))
   214  		require.NoError(t, err)
   215  
   216  		assert.NotSame(t, testMap, test.Foo)
   217  		assert.Equal(t, map[int]string{1: "a", 2: "b"}, testMap, "original-val")
   218  		assert.Equal(t, map[int]string{1: "a", 2: "b", 3: "c"}, test.Foo, "new-val")
   219  
   220  		// verify modifications don't corrupt original
   221  		testMap[1] = "z"
   222  		assert.Equal(t, map[int]string{1: "z", 2: "b"}, testMap, "mod-original-val")
   223  		assert.Equal(t, map[int]string{1: "a", 2: "b", 3: "c"}, test.Foo, "mod-ew-val")
   224  	})
   225  
   226  	t.Run("invalid-null", func(t *testing.T) {
   227  		var (
   228  			testMap = map[int]string{1: "a", 2: "b"}
   229  			test    = &struct {
   230  				Foo map[int]string `json:"foo"`
   231  			}{Foo: testMap}
   232  		)
   233  
   234  		err := MergeClone(test, []byte(`{"foo":nul}`))
   235  
   236  		// json-iter will produce an error since "nul" is not a valid json value. the
   237  		// parsing code will see the "n" and then expect "ull" to follow. the strange
   238  		// "expect ull" error being asserted is generated by json-iter.
   239  		require.EqualError(t, err, "cannot unmarshal Foo: expect ull")
   240  	})
   241  
   242  	t.Run("invalid-malformed", func(t *testing.T) {
   243  		var (
   244  			testMap = map[int]string{1: "a", 2: "b"}
   245  			test    = &struct {
   246  				Foo map[int]string `json:"foo"`
   247  			}{Foo: testMap}
   248  		)
   249  
   250  		err := MergeClone(test, []byte(`{"foo":malformed}`))
   251  		require.EqualError(t, err, "cannot unmarshal Foo: expect { or n, but found m")
   252  	})
   253  }
   254  
   255  func TestMergeCloneExt(t *testing.T) {
   256  	testCases := []struct {
   257  		name          string
   258  		givenExisting json.RawMessage
   259  		givenIncoming json.RawMessage
   260  		expectedExt   json.RawMessage
   261  		expectedErr   string
   262  	}{
   263  		{
   264  			name:          "both-populated",
   265  			givenExisting: json.RawMessage(`{"a":1,"b":2}`),
   266  			givenIncoming: json.RawMessage(`{"b":200,"c":3}`),
   267  			expectedExt:   json.RawMessage(`{"a":1,"b":200,"c":3}`),
   268  		},
   269  		{
   270  			name:          "both-omitted",
   271  			givenExisting: nil,
   272  			givenIncoming: nil,
   273  			expectedExt:   nil,
   274  		},
   275  		{
   276  			name:          "both-nil",
   277  			givenExisting: nil,
   278  			givenIncoming: json.RawMessage(`null`),
   279  			expectedExt:   nil,
   280  		},
   281  		{
   282  			name:          "both-empty",
   283  			givenExisting: nil,
   284  			givenIncoming: json.RawMessage(`{}`),
   285  			expectedExt:   json.RawMessage(`{}`),
   286  		},
   287  		{
   288  			name:          "ext-omitted",
   289  			givenExisting: json.RawMessage(`{"b":2}`),
   290  			givenIncoming: nil,
   291  			expectedExt:   json.RawMessage(`{"b":2}`),
   292  		},
   293  		{
   294  			name:          "ext-nil",
   295  			givenExisting: json.RawMessage(`{"b":2}`),
   296  			givenIncoming: json.RawMessage(`null`),
   297  			expectedExt:   json.RawMessage(`{"b":2}`),
   298  		},
   299  		{
   300  			name:          "ext-empty",
   301  			givenExisting: json.RawMessage(`{"b":2}`),
   302  			givenIncoming: json.RawMessage(`{}`),
   303  			expectedExt:   json.RawMessage(`{"b":2}`),
   304  		},
   305  		{
   306  			name:          "ext-malformed",
   307  			givenExisting: json.RawMessage(`{"b":2}`),
   308  			givenIncoming: json.RawMessage(`malformed`),
   309  			expectedErr:   "openrtb2.BidRequest.Ext",
   310  		},
   311  		{
   312  			name:          "existing-nil",
   313  			givenExisting: nil,
   314  			givenIncoming: json.RawMessage(`{"a":1}`),
   315  			expectedExt:   json.RawMessage(`{"a":1}`),
   316  		},
   317  		{
   318  			name:          "existing-empty",
   319  			givenExisting: json.RawMessage(`{}`),
   320  			givenIncoming: json.RawMessage(`{"a":1}`),
   321  			expectedExt:   json.RawMessage(`{"a":1}`),
   322  		},
   323  		{
   324  			name:          "existing-omitted",
   325  			givenExisting: nil,
   326  			givenIncoming: json.RawMessage(`{"b":2}`),
   327  			expectedExt:   json.RawMessage(`{"b":2}`),
   328  		},
   329  		{
   330  			name:          "existing-malformed",
   331  			givenExisting: json.RawMessage(`malformed`),
   332  			givenIncoming: json.RawMessage(`{"a":1}`),
   333  			expectedErr:   "cannot unmarshal openrtb2.BidRequest.Ext: invalid json on existing object",
   334  		},
   335  	}
   336  
   337  	for _, test := range testCases {
   338  		t.Run(test.name, func(t *testing.T) {
   339  			// copy original values to check at the end for no modification
   340  			originalExisting := sliceutil.Clone(test.givenExisting)
   341  			originalIncoming := sliceutil.Clone(test.givenIncoming)
   342  
   343  			// build request
   344  			request := &openrtb2.BidRequest{Ext: test.givenExisting}
   345  
   346  			// build data
   347  			data := test.givenIncoming
   348  			if len(data) > 0 {
   349  				data = []byte(`{"ext":` + string(data) + `}`) // wrap in ext
   350  			} else {
   351  				data = []byte(`{}`) // omit ext
   352  			}
   353  
   354  			err := MergeClone(request, data)
   355  
   356  			// assert error
   357  			if test.expectedErr == "" {
   358  				assert.NoError(t, err, "err")
   359  			} else {
   360  				assert.ErrorContains(t, err, test.expectedErr, "err")
   361  			}
   362  
   363  			// assert ext
   364  			if test.expectedErr != "" {
   365  				// expect no change in case of error
   366  				assert.Equal(t, string(test.givenExisting), string(request.Ext), "json")
   367  			} else {
   368  				// compare as strings instead of json in case of nil or malformed ext
   369  				assert.Equal(t, string(test.expectedExt), string(request.Ext), "json")
   370  			}
   371  
   372  			// assert no modifications
   373  			// - can't use `assert.Same`` comparison checks since that's expected if
   374  			//   either existing or incoming are nil / omitted / empty.
   375  			assert.Equal(t, originalExisting, []byte(test.givenExisting), "existing")
   376  			assert.Equal(t, originalIncoming, []byte(test.givenIncoming), "incoming")
   377  		})
   378  	}
   379  }
   380  
   381  func TestMergeCloneCombinations(t *testing.T) {
   382  	t.Run("slice-of-ptr", func(t *testing.T) {
   383  		var (
   384  			imp      = &openrtb2.Imp{ID: "1"}
   385  			impSlice = []*openrtb2.Imp{imp}
   386  			test     = &struct {
   387  				Imps []*openrtb2.Imp `json:"imps"`
   388  			}{Imps: impSlice}
   389  		)
   390  
   391  		err := MergeClone(test, []byte(`{"imps":[{"id":"2"}]}`))
   392  		require.NoError(t, err)
   393  
   394  		assert.NotSame(t, impSlice, test.Imps, "slice-ref")
   395  		require.Len(t, test.Imps, 1, "slice-len")
   396  
   397  		assert.NotSame(t, imp, test.Imps[0], "item-ref")
   398  		assert.Equal(t, "1", imp.ID, "original-val")
   399  		assert.Equal(t, "2", test.Imps[0].ID, "new-val")
   400  	})
   401  
   402  	// special case of "slice-of-ptr"
   403  	t.Run("jsonrawmessage-ptr", func(t *testing.T) {
   404  		var (
   405  			testJson = json.RawMessage(`{"a":1}`)
   406  			test     = &struct {
   407  				Foo *json.RawMessage `json:"foo"`
   408  			}{Foo: &testJson}
   409  		)
   410  
   411  		err := MergeClone(test, []byte(`{"foo":{"b":2}}`))
   412  		require.NoError(t, err)
   413  
   414  		assert.NotSame(t, &testJson, test.Foo, "ref")
   415  		assert.Equal(t, json.RawMessage(`{"a":1}`), testJson)
   416  		assert.Equal(t, json.RawMessage(`{"a":1,"b":2}`), *test.Foo)
   417  	})
   418  
   419  	t.Run("struct-ptr", func(t *testing.T) {
   420  		var (
   421  			imp  = &openrtb2.Imp{ID: "1"}
   422  			test = &struct {
   423  				Imp *openrtb2.Imp `json:"imp"`
   424  			}{Imp: imp}
   425  		)
   426  
   427  		err := MergeClone(test, []byte(`{"imp":{"id":"2"}}`))
   428  		require.NoError(t, err)
   429  
   430  		assert.NotSame(t, imp, test.Imp, "ref")
   431  		assert.Equal(t, "1", imp.ID, "original-val")
   432  		assert.Equal(t, "2", test.Imp.ID, "new-val")
   433  	})
   434  
   435  	t.Run("map-of-ptrs", func(t *testing.T) {
   436  		var (
   437  			imp    = &openrtb2.Imp{ID: "1"}
   438  			impMap = map[string]*openrtb2.Imp{"a": imp}
   439  			test   = &struct {
   440  				Imps map[string]*openrtb2.Imp `json:"imps"`
   441  			}{Imps: impMap}
   442  		)
   443  
   444  		err := MergeClone(test, []byte(`{"imps":{"a":{"id":"2"}}}`))
   445  		require.NoError(t, err)
   446  
   447  		assert.NotSame(t, impMap, test.Imps, "map-ref")
   448  		assert.NotSame(t, imp, test.Imps["a"], "imp-ref")
   449  
   450  		assert.Same(t, impMap["a"], imp, "imp-map-ref")
   451  
   452  		assert.Equal(t, "1", imp.ID, "original-val")
   453  		assert.Equal(t, "2", test.Imps["a"].ID, "new-val")
   454  	})
   455  }