github.com/prebid/prebid-server@v0.275.0/openrtb_ext/request_wrapper_test.go (about)

     1  package openrtb_ext
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"github.com/prebid/openrtb/v19/openrtb2"
     8  	"github.com/prebid/prebid-server/util/ptrutil"
     9  	"github.com/stretchr/testify/assert"
    10  )
    11  
    12  func TestCloneRequestWrapper(t *testing.T) {
    13  	testCases := []struct {
    14  		name        string
    15  		reqWrap     *RequestWrapper
    16  		reqWrapCopy *RequestWrapper                             // manual copy of above ext object to verify against
    17  		mutator     func(t *testing.T, reqWrap *RequestWrapper) // function to modify the Ext object
    18  	}{
    19  		{
    20  			name:        "Nil", // Verify the nil case
    21  			reqWrap:     nil,
    22  			reqWrapCopy: nil,
    23  			mutator:     func(t *testing.T, reqWrap *RequestWrapper) {},
    24  		},
    25  		{
    26  			name: "NoMutate",
    27  			reqWrap: &RequestWrapper{
    28  				impWrappers: []*ImpWrapper{
    29  					{
    30  						impExt: &ImpExt{prebid: &ExtImpPrebid{Options: &Options{EchoVideoAttrs: true}}, prebidDirty: true, tid: "fun"},
    31  					},
    32  					{
    33  						impExt: &ImpExt{tid: "star"},
    34  					},
    35  				},
    36  				userExt:   &UserExt{consentDirty: true},
    37  				deviceExt: &DeviceExt{extDirty: true},
    38  				requestExt: &RequestExt{
    39  					prebid: &ExtRequestPrebid{Integration: "derivative"},
    40  				},
    41  				appExt:    &AppExt{prebidDirty: true},
    42  				regExt:    &RegExt{usPrivacy: "foo"},
    43  				siteExt:   &SiteExt{amp: ptrutil.ToPtr[int8](1)},
    44  				sourceExt: &SourceExt{schainDirty: true},
    45  			},
    46  			reqWrapCopy: &RequestWrapper{
    47  				impWrappers: []*ImpWrapper{
    48  					{
    49  						impExt: &ImpExt{prebid: &ExtImpPrebid{Options: &Options{EchoVideoAttrs: true}}, prebidDirty: true, tid: "fun"},
    50  					},
    51  					{
    52  						impExt: &ImpExt{tid: "star"},
    53  					},
    54  				},
    55  				userExt:   &UserExt{consentDirty: true},
    56  				deviceExt: &DeviceExt{extDirty: true},
    57  				requestExt: &RequestExt{
    58  					prebid: &ExtRequestPrebid{Integration: "derivative"},
    59  				},
    60  				appExt:    &AppExt{prebidDirty: true},
    61  				regExt:    &RegExt{usPrivacy: "foo"},
    62  				siteExt:   &SiteExt{amp: ptrutil.ToPtr[int8](1)},
    63  				sourceExt: &SourceExt{schainDirty: true},
    64  			},
    65  			mutator: func(t *testing.T, reqWrap *RequestWrapper) {},
    66  		},
    67  		{
    68  			name: "General",
    69  			reqWrap: &RequestWrapper{
    70  				impWrappers: []*ImpWrapper{
    71  					{
    72  						impExt: &ImpExt{prebid: &ExtImpPrebid{Options: &Options{EchoVideoAttrs: true}}, prebidDirty: true, tid: "fun"},
    73  					},
    74  					{
    75  						impExt: &ImpExt{tid: "star"},
    76  					},
    77  				},
    78  				userExt:   &UserExt{consentDirty: true},
    79  				deviceExt: &DeviceExt{extDirty: true},
    80  				requestExt: &RequestExt{
    81  					prebid: &ExtRequestPrebid{Integration: "derivative"},
    82  				},
    83  				appExt:    &AppExt{prebidDirty: true},
    84  				regExt:    &RegExt{usPrivacy: "foo"},
    85  				siteExt:   &SiteExt{amp: ptrutil.ToPtr[int8](1)},
    86  				sourceExt: &SourceExt{schainDirty: true},
    87  			},
    88  			reqWrapCopy: &RequestWrapper{
    89  				impWrappers: []*ImpWrapper{
    90  					{
    91  						impExt: &ImpExt{prebid: &ExtImpPrebid{Options: &Options{EchoVideoAttrs: true}}, prebidDirty: true, tid: "fun"},
    92  					},
    93  					{
    94  						impExt: &ImpExt{tid: "star"},
    95  					},
    96  				},
    97  				userExt:   &UserExt{consentDirty: true},
    98  				deviceExt: &DeviceExt{extDirty: true},
    99  				requestExt: &RequestExt{
   100  					prebid: &ExtRequestPrebid{Integration: "derivative"},
   101  				},
   102  				appExt:    &AppExt{prebidDirty: true},
   103  				regExt:    &RegExt{usPrivacy: "foo"},
   104  				siteExt:   &SiteExt{amp: ptrutil.ToPtr[int8](1)},
   105  				sourceExt: &SourceExt{schainDirty: true},
   106  			},
   107  			mutator: func(t *testing.T, reqWrap *RequestWrapper) {
   108  				reqWrap.impWrappers[1].impExt.prebidDirty = true
   109  				reqWrap.impWrappers[0] = nil
   110  				reqWrap.impWrappers = append(reqWrap.impWrappers, &ImpWrapper{impExt: &ImpExt{tid: "star"}})
   111  				reqWrap.impWrappers = nil
   112  				reqWrap.userExt = nil
   113  				reqWrap.deviceExt = nil
   114  				reqWrap.requestExt = nil
   115  				reqWrap.appExt = nil
   116  				reqWrap.regExt = nil
   117  				reqWrap.siteExt = nil
   118  				reqWrap.sourceExt = nil
   119  			},
   120  		},
   121  	}
   122  
   123  	for _, test := range testCases {
   124  		t.Run(test.name, func(t *testing.T) {
   125  			clone := test.reqWrap.Clone()
   126  			test.mutator(t, test.reqWrap)
   127  			assert.Equal(t, test.reqWrapCopy, clone)
   128  		})
   129  	}
   130  }
   131  
   132  func TestUserExt(t *testing.T) {
   133  	userExt := &UserExt{}
   134  
   135  	userExt.unmarshal(nil)
   136  	assert.Equal(t, false, userExt.Dirty(), "New UserExt should not be dirty.")
   137  	assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent")
   138  	assert.Nil(t, userExt.GetEid(), "Empty UserExt should have nil eid")
   139  	assert.Nil(t, userExt.GetPrebid(), "Empty UserExt should have nil prebid")
   140  	assert.Nil(t, userExt.GetConsentedProvidersSettingsIn(), "Empty UserExt should have nil consentedProvidersSettings")
   141  
   142  	newConsent := "NewConsent"
   143  	userExt.SetConsent(&newConsent)
   144  	assert.Equal(t, "NewConsent", *userExt.GetConsent(), "UserExt consent is incorrect")
   145  
   146  	eid := openrtb2.EID{Source: "source", UIDs: []openrtb2.UID{{ID: "id"}}}
   147  	newEid := []openrtb2.EID{eid}
   148  	userExt.SetEid(&newEid)
   149  	assert.Equal(t, []openrtb2.EID{eid}, *userExt.GetEid(), "UserExt eid is incorrect")
   150  
   151  	buyerIDs := map[string]string{"buyer": "id"}
   152  	newPrebid := ExtUserPrebid{BuyerUIDs: buyerIDs}
   153  	userExt.SetPrebid(&newPrebid)
   154  	assert.Equal(t, ExtUserPrebid{BuyerUIDs: buyerIDs}, *userExt.GetPrebid(), "UserExt prebid is incorrect")
   155  
   156  	consentedProvidersSettings := &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"}
   157  	userExt.SetConsentedProvidersSettingsIn(consentedProvidersSettings)
   158  	assert.Equal(t, &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"}, userExt.GetConsentedProvidersSettingsIn(), "UserExt consentedProvidersSettings is incorrect")
   159  	assert.Equal(t, true, userExt.Dirty(), "UserExt should be dirty after field updates")
   160  	cpsIn := userExt.GetConsentedProvidersSettingsIn()
   161  	assert.Equal(t, "1~X.X.X", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect")
   162  
   163  	consentedProvidersString := "1~1.35.41.101"
   164  	cpsIn.ConsentedProvidersString = consentedProvidersString
   165  	userExt.SetConsentedProvidersSettingsIn(cpsIn)
   166  	cpsIn = userExt.GetConsentedProvidersSettingsIn()
   167  	assert.Equal(t, "1~1.35.41.101", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect")
   168  
   169  	cpsOut := &ConsentedProvidersSettingsOut{}
   170  	//cpsOut.ConsentedProvidersList = make([]int, 0, 1)
   171  	cpsOut.ConsentedProvidersList = append(cpsOut.ConsentedProvidersList, ParseConsentedProvidersString(consentedProvidersString)...)
   172  	assert.Len(t, cpsOut.ConsentedProvidersList, 4, "UserExt consentedProvidersList is incorrect")
   173  	userExt.SetConsentedProvidersSettingsOut(cpsOut)
   174  
   175  	updatedUserExt, err := userExt.marshal()
   176  	assert.Nil(t, err, "Marshalling UserExt after updating should not cause an error")
   177  
   178  	expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"consented_providers_settings":{"consented_providers":[1,35,41,101]},"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"},"eids":[{"source":"source","uids":[{"id":"id"}]}]}`)
   179  	assert.JSONEq(t, string(expectedUserExt), string(updatedUserExt), "Marshalled UserExt is incorrect")
   180  
   181  	assert.Equal(t, false, userExt.Dirty(), "UserExt should not be dirty after marshalling")
   182  }
   183  
   184  func TestRebuildImp(t *testing.T) {
   185  	var (
   186  		prebid     = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)}
   187  		prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)
   188  	)
   189  
   190  	testCases := []struct {
   191  		description       string
   192  		request           openrtb2.BidRequest
   193  		requestImpWrapper []*ImpWrapper
   194  		expectedRequest   openrtb2.BidRequest
   195  		expectedError     string
   196  	}{
   197  		{
   198  			description:       "Empty - Never Accessed",
   199  			request:           openrtb2.BidRequest{},
   200  			requestImpWrapper: nil,
   201  			expectedRequest:   openrtb2.BidRequest{},
   202  		},
   203  		{
   204  			description:       "One - Never Accessed",
   205  			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
   206  			requestImpWrapper: nil,
   207  			expectedRequest:   openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
   208  		},
   209  		{
   210  			description:       "One - Accessed - Dirty",
   211  			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
   212  			requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}},
   213  			expectedRequest:   openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}},
   214  		},
   215  		{
   216  			description:       "One - Accessed - Error",
   217  			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
   218  			requestImpWrapper: []*ImpWrapper{{Imp: nil, impExt: &ImpExt{}}},
   219  			expectedError:     "ImpWrapper RebuildImp called on a nil Imp",
   220  		},
   221  		{
   222  			description:       "Many - Accessed - Dirty / Not Dirty",
   223  			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}}},
   224  			requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{}}, {Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}},
   225  			expectedRequest:   openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2", Ext: prebidJson}}},
   226  		},
   227  	}
   228  
   229  	for _, test := range testCases {
   230  		// create required filed in the test loop to keep test declaration easier to read
   231  		for _, w := range test.requestImpWrapper {
   232  			w.impExt.ext = make(map[string]json.RawMessage)
   233  		}
   234  
   235  		w := RequestWrapper{BidRequest: &test.request, impWrappers: test.requestImpWrapper, impWrappersAccessed: test.requestImpWrapper != nil}
   236  		err := w.RebuildRequest()
   237  
   238  		if test.expectedError != "" {
   239  			assert.EqualError(t, err, test.expectedError, test.description)
   240  		} else {
   241  			assert.NoError(t, err, test.description)
   242  			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
   243  		}
   244  	}
   245  }
   246  
   247  func TestRebuildUserExt(t *testing.T) {
   248  	strA := "a"
   249  	strB := "b"
   250  
   251  	type testCase struct {
   252  		description           string
   253  		request               openrtb2.BidRequest
   254  		requestUserExtWrapper UserExt
   255  		expectedRequest       openrtb2.BidRequest
   256  	}
   257  	testGroups := []struct {
   258  		groupDesc string
   259  		tests     []testCase
   260  	}{
   261  		{
   262  			groupDesc: "Consent string tests",
   263  			tests: []testCase{
   264  				// Nil req.User
   265  				{
   266  					description:           "Nil req.User - UserExt Not Dirty",
   267  					request:               openrtb2.BidRequest{},
   268  					requestUserExtWrapper: UserExt{},
   269  					expectedRequest:       openrtb2.BidRequest{},
   270  				},
   271  				{
   272  					description:           "Nil req.User - Dirty UserExt",
   273  					request:               openrtb2.BidRequest{},
   274  					requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
   275  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   276  				},
   277  				{
   278  					description:           "Nil req.User - Dirty UserExt but consent is nil - No Change",
   279  					request:               openrtb2.BidRequest{},
   280  					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
   281  					expectedRequest:       openrtb2.BidRequest{},
   282  				},
   283  				// Nil req.User.Ext
   284  				{
   285  					description:           "Nil req.User.Ext - Not Dirty - No Change",
   286  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   287  					requestUserExtWrapper: UserExt{},
   288  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   289  				},
   290  				{
   291  					description:           "Nil req.User.Ext - Dirty with valid consent string - Expect consent string to be added",
   292  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   293  					requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
   294  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
   295  				},
   296  				{
   297  					description:           "Nil req.User.Ext - Dirty UserExt with nil consent string- No Change",
   298  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   299  					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
   300  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   301  				},
   302  				// Not-nil req.User.Ext
   303  				{
   304  					description:           "Populated - Not Dirty - No Change",
   305  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   306  					requestUserExtWrapper: UserExt{},
   307  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   308  				},
   309  				{
   310  					description:           "Populated - Dirty - Consent string overriden",
   311  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   312  					requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
   313  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
   314  				},
   315  				{
   316  					description:           "Populated - Dirty but consent string is equal to the one already set - No Change",
   317  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   318  					requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
   319  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   320  				},
   321  				{
   322  					description:           "Populated - Dirty UserExt with nil consent string - Cleared",
   323  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
   324  					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
   325  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   326  				},
   327  			},
   328  		},
   329  		{
   330  			groupDesc: "Consented provider settings string form",
   331  			tests: []testCase{
   332  				// Nil req.User
   333  				{
   334  					description:           "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change",
   335  					request:               openrtb2.BidRequest{},
   336  					requestUserExtWrapper: UserExt{},
   337  					expectedRequest:       openrtb2.BidRequest{},
   338  				},
   339  				{
   340  					description:           "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change",
   341  					request:               openrtb2.BidRequest{},
   342  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
   343  					expectedRequest:       openrtb2.BidRequest{},
   344  				},
   345  				{
   346  					description:           "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
   347  					request:               openrtb2.BidRequest{},
   348  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true},
   349  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}},
   350  				},
   351  				// Nil req.User.Ext
   352  				{
   353  					description:           "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change",
   354  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   355  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true},
   356  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   357  				},
   358  				{
   359  					description:           "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change",
   360  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   361  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
   362  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   363  				},
   364  				{
   365  					description:           "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
   366  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   367  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true},
   368  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}},
   369  				},
   370  				// Not-nil req.User.Ext
   371  				{
   372  					description:           "Populated req.User.Ext - Not Dirty UserExt - No Change",
   373  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
   374  					requestUserExtWrapper: UserExt{},
   375  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
   376  				},
   377  				{
   378  					description:           "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext",
   379  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
   380  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true},
   381  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   382  				},
   383  				{
   384  					description:           "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext",
   385  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"1~X.X.X.X":{"consented_providers":"1~X.X.X.X"}}`)}},
   386  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
   387  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   388  				},
   389  				{
   390  					description:           "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are overriden",
   391  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
   392  					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~1.35.41.101"}, consentedProvidersSettingsInDirty: true},
   393  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"}}`)}},
   394  				},
   395  			},
   396  		},
   397  		{
   398  			groupDesc: "Consented provider settings array",
   399  			tests: []testCase{
   400  				// Nil req.User
   401  				{
   402  					description:           "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change",
   403  					request:               openrtb2.BidRequest{},
   404  					requestUserExtWrapper: UserExt{},
   405  					expectedRequest:       openrtb2.BidRequest{},
   406  				},
   407  				{
   408  					description:           "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change",
   409  					request:               openrtb2.BidRequest{},
   410  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
   411  					expectedRequest:       openrtb2.BidRequest{},
   412  				},
   413  				{
   414  					description:           "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
   415  					request:               openrtb2.BidRequest{},
   416  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true},
   417  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   418  				},
   419  				// Nil req.User.Ext
   420  				{
   421  					description:           "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change",
   422  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   423  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true},
   424  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   425  				},
   426  				{
   427  					description:           "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change",
   428  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   429  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
   430  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   431  				},
   432  				{
   433  					description:           "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
   434  					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
   435  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true},
   436  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   437  				},
   438  				// Not-nil req.User.Ext
   439  				{
   440  					description:           "Populated req.User.Ext - Not Dirty UserExt - No Change",
   441  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   442  					requestUserExtWrapper: UserExt{},
   443  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   444  				},
   445  				{
   446  					description:           "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext",
   447  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   448  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true},
   449  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   450  				},
   451  				{
   452  					description:           "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext",
   453  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   454  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
   455  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
   456  				},
   457  				{
   458  					description:           "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettingsOut - consented_providers list elements are overriden",
   459  					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
   460  					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{35, 36, 240}}, consentedProvidersSettingsOutDirty: true},
   461  					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[35,36,240]}}`)}},
   462  				},
   463  			},
   464  		},
   465  	}
   466  
   467  	for _, group := range testGroups {
   468  		for _, test := range group.tests {
   469  			// create required filed in the test loop to keep test declaration easier to read
   470  			test.requestUserExtWrapper.ext = make(map[string]json.RawMessage)
   471  
   472  			w := RequestWrapper{BidRequest: &test.request, userExt: &test.requestUserExtWrapper}
   473  			w.RebuildRequest()
   474  			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
   475  		}
   476  	}
   477  }
   478  
   479  func TestUserExtUnmarshal(t *testing.T) {
   480  	type testInput struct {
   481  		userExt *UserExt
   482  		extJson json.RawMessage
   483  	}
   484  	testCases := []struct {
   485  		desc        string
   486  		in          testInput
   487  		expectError bool
   488  	}{
   489  		{
   490  			desc: "UserExt.ext is not nil, don't expect error",
   491  			in: testInput{
   492  				userExt: &UserExt{
   493  					ext: map[string]json.RawMessage{
   494  						"eids": json.RawMessage(`[{"source":"value"}]`),
   495  					},
   496  				},
   497  				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
   498  			},
   499  			expectError: false,
   500  		},
   501  		{
   502  			desc: "UserExt.ext is dirty, don't expect error",
   503  			in: testInput{
   504  				userExt: &UserExt{extDirty: true},
   505  				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
   506  			},
   507  			expectError: false,
   508  		},
   509  		// Eids
   510  		{
   511  			desc: "Has eids and it is valid JSON",
   512  			in: testInput{
   513  				userExt: &UserExt{},
   514  				extJson: json.RawMessage(`{"eids":[{"source":"value"}]}`),
   515  			},
   516  			expectError: false,
   517  		},
   518  		{
   519  			desc: "Has malformed eids expect error",
   520  			in: testInput{
   521  				userExt: &UserExt{},
   522  				extJson: json.RawMessage(`{"eids":123}`),
   523  			},
   524  			expectError: true,
   525  		},
   526  		// prebid
   527  		{
   528  			desc: "Has prebid and it is valid JSON",
   529  			in: testInput{
   530  				userExt: &UserExt{},
   531  				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
   532  			},
   533  			expectError: false,
   534  		},
   535  		{
   536  			desc: "Has malformed prebid expect error",
   537  			in: testInput{
   538  				userExt: &UserExt{},
   539  				extJson: json.RawMessage(`{"prebid":{"buyeruids":123}}`),
   540  			},
   541  			expectError: true,
   542  		},
   543  		// ConsentedProvidersSettings
   544  		{
   545  			desc: "Has ConsentedProvidersSettings and it is valid JSON",
   546  			in: testInput{
   547  				userExt: &UserExt{},
   548  				extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`),
   549  			},
   550  			expectError: false,
   551  		},
   552  		{
   553  			desc: "Has malformed ConsentedProvidersSettings expect error",
   554  			in: testInput{
   555  				userExt: &UserExt{},
   556  				extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":123}}`),
   557  			},
   558  			expectError: true,
   559  		},
   560  		// consented_providers_settings
   561  		{
   562  			desc: "Has consented_providers_settings and it is valid JSON",
   563  			in: testInput{
   564  				userExt: &UserExt{},
   565  				extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[2,25]}}`),
   566  			},
   567  			expectError: false,
   568  		},
   569  		{
   570  			desc: "Has malformed consented_providers_settings expect error",
   571  			in: testInput{
   572  				userExt: &UserExt{},
   573  				extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":123}}`),
   574  			},
   575  			expectError: true,
   576  		},
   577  	}
   578  	for _, tc := range testCases {
   579  		err := tc.in.userExt.unmarshal(tc.in.extJson)
   580  
   581  		if tc.expectError {
   582  			assert.Error(t, err, tc.desc)
   583  		} else {
   584  			assert.NoError(t, err, tc.desc)
   585  		}
   586  	}
   587  }
   588  
   589  func TestCloneUserExt(t *testing.T) {
   590  	testCases := []struct {
   591  		name        string
   592  		userExt     *UserExt
   593  		userExtCopy *UserExt                             // manual copy of above ext object to verify against
   594  		mutator     func(t *testing.T, userExt *UserExt) // function to modify the Ext object
   595  	}{
   596  		{
   597  			name:        "Nil", // Verify the nil case
   598  			userExt:     nil,
   599  			userExtCopy: nil,
   600  			mutator:     func(t *testing.T, user *UserExt) {},
   601  		},
   602  		{
   603  			name: "NoMutate",
   604  			userExt: &UserExt{
   605  				ext:          map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
   606  				consent:      ptrutil.ToPtr("Myconsent"),
   607  				consentDirty: true,
   608  				prebid: &ExtUserPrebid{
   609  					BuyerUIDs: map[string]string{"A": "X", "B": "Y"},
   610  				},
   611  				prebidDirty: true,
   612  				eids:        &[]openrtb2.EID{},
   613  			},
   614  			userExtCopy: &UserExt{
   615  				ext:          map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
   616  				consent:      ptrutil.ToPtr("Myconsent"),
   617  				consentDirty: true,
   618  				prebid: &ExtUserPrebid{
   619  					BuyerUIDs: map[string]string{"A": "X", "B": "Y"},
   620  				},
   621  				prebidDirty: true,
   622  				eids:        &[]openrtb2.EID{},
   623  			},
   624  			mutator: func(t *testing.T, user *UserExt) {},
   625  		},
   626  		{
   627  			name: "General",
   628  			userExt: &UserExt{
   629  				ext:          map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
   630  				consent:      ptrutil.ToPtr("Myconsent"),
   631  				consentDirty: true,
   632  				prebid: &ExtUserPrebid{
   633  					BuyerUIDs: map[string]string{"A": "X", "B": "Y"},
   634  				},
   635  				prebidDirty: true,
   636  				eids:        &[]openrtb2.EID{},
   637  			},
   638  			userExtCopy: &UserExt{
   639  				ext:          map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
   640  				consent:      ptrutil.ToPtr("Myconsent"),
   641  				consentDirty: true,
   642  				prebid: &ExtUserPrebid{
   643  					BuyerUIDs: map[string]string{"A": "X", "B": "Y"},
   644  				},
   645  				prebidDirty: true,
   646  				eids:        &[]openrtb2.EID{},
   647  			},
   648  			mutator: func(t *testing.T, user *UserExt) {
   649  				user.ext["A"] = json.RawMessage(`G`)
   650  				user.ext["C"] = json.RawMessage(`L`)
   651  				user.extDirty = true
   652  				user.consent = nil
   653  				user.consentDirty = false
   654  				user.prebid.BuyerUIDs["A"] = "C"
   655  				user.prebid.BuyerUIDs["C"] = "A"
   656  				user.prebid = nil
   657  			},
   658  		},
   659  		{
   660  			name: "EIDs",
   661  			userExt: &UserExt{
   662  				eids: &[]openrtb2.EID{
   663  					{
   664  						Source: "Sauce",
   665  						UIDs: []openrtb2.UID{
   666  							{ID: "A", AType: 5, Ext: json.RawMessage(`{}`)},
   667  							{ID: "B", AType: 1, Ext: json.RawMessage(`{"extra": "stuff"}`)},
   668  						},
   669  					},
   670  					{
   671  						Source: "Moon",
   672  						UIDs: []openrtb2.UID{
   673  							{ID: "G", AType: 3, Ext: json.RawMessage(`{}`)},
   674  							{ID: "D", AType: 1},
   675  						},
   676  					},
   677  				},
   678  			},
   679  			userExtCopy: &UserExt{
   680  				eids: &[]openrtb2.EID{
   681  					{
   682  						Source: "Sauce",
   683  						UIDs: []openrtb2.UID{
   684  							{ID: "A", AType: 5, Ext: json.RawMessage(`{}`)},
   685  							{ID: "B", AType: 1, Ext: json.RawMessage(`{"extra": "stuff"}`)},
   686  						},
   687  					},
   688  					{
   689  						Source: "Moon",
   690  						UIDs: []openrtb2.UID{
   691  							{ID: "G", AType: 3, Ext: json.RawMessage(`{}`)},
   692  							{ID: "D", AType: 1},
   693  						},
   694  					},
   695  				},
   696  			},
   697  			mutator: func(t *testing.T, userExt *UserExt) {
   698  				eids := *userExt.eids
   699  				eids[0].UIDs[1].ID = "G2"
   700  				eids[1].UIDs[0].AType = 0
   701  				eids[0].UIDs = append(eids[0].UIDs, openrtb2.UID{ID: "Z", AType: 2})
   702  				eids = append(eids, openrtb2.EID{Source: "Blank"})
   703  				userExt.eids = nil
   704  			},
   705  		},
   706  		{
   707  			name: "ConsentedProviders",
   708  			userExt: &UserExt{
   709  				consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{
   710  					ConsentedProvidersString: "A,B,C",
   711  				},
   712  				consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{
   713  					ConsentedProvidersList: []int{1, 2, 3, 4},
   714  				},
   715  			},
   716  			userExtCopy: &UserExt{
   717  				consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{
   718  					ConsentedProvidersString: "A,B,C",
   719  				},
   720  				consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{
   721  					ConsentedProvidersList: []int{1, 2, 3, 4},
   722  				},
   723  			},
   724  			mutator: func(t *testing.T, userExt *UserExt) {
   725  				userExt.consentedProvidersSettingsIn.ConsentedProvidersString = "B,C,D"
   726  				userExt.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{
   727  					ConsentedProvidersString: "G,H,I",
   728  				}
   729  				userExt.consentedProvidersSettingsOut.ConsentedProvidersList[1] = 5
   730  				userExt.consentedProvidersSettingsOut.ConsentedProvidersList = append(userExt.consentedProvidersSettingsOut.ConsentedProvidersList, 7)
   731  				userExt.consentedProvidersSettingsOut = nil
   732  			},
   733  		},
   734  	}
   735  
   736  	for _, test := range testCases {
   737  		t.Run(test.name, func(t *testing.T) {
   738  			clone := test.userExt.Clone()
   739  			test.mutator(t, test.userExt)
   740  			assert.Equal(t, test.userExtCopy, clone)
   741  		})
   742  	}
   743  }
   744  
   745  func TestRebuildDeviceExt(t *testing.T) {
   746  	prebidContent1 := ExtDevicePrebid{Interstitial: &ExtDeviceInt{MinWidthPerc: 1}}
   747  	prebidContent2 := ExtDevicePrebid{Interstitial: &ExtDeviceInt{MinWidthPerc: 2}}
   748  
   749  	testCases := []struct {
   750  		description             string
   751  		request                 openrtb2.BidRequest
   752  		requestDeviceExtWrapper DeviceExt
   753  		expectedRequest         openrtb2.BidRequest
   754  	}{
   755  		{
   756  			description:             "Nil - Not Dirty",
   757  			request:                 openrtb2.BidRequest{},
   758  			requestDeviceExtWrapper: DeviceExt{},
   759  			expectedRequest:         openrtb2.BidRequest{},
   760  		},
   761  		{
   762  			description:             "Nil - Dirty",
   763  			request:                 openrtb2.BidRequest{},
   764  			requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true},
   765  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   766  		},
   767  		{
   768  			description:             "Nil - Dirty - No Change",
   769  			request:                 openrtb2.BidRequest{},
   770  			requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true},
   771  			expectedRequest:         openrtb2.BidRequest{},
   772  		},
   773  		{
   774  			description:             "Empty - Not Dirty",
   775  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{}},
   776  			requestDeviceExtWrapper: DeviceExt{},
   777  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{}},
   778  		},
   779  		{
   780  			description:             "Empty - Dirty",
   781  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{}},
   782  			requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true},
   783  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   784  		},
   785  		{
   786  			description:             "Empty - Dirty - No Change",
   787  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{}},
   788  			requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true},
   789  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{}},
   790  		},
   791  		{
   792  			description:             "Populated - Not Dirty",
   793  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   794  			requestDeviceExtWrapper: DeviceExt{},
   795  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   796  		},
   797  		{
   798  			description:             "Populated - Dirty",
   799  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   800  			requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent2, prebidDirty: true},
   801  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":2,"minheightperc":0}}}`)}},
   802  		},
   803  		{
   804  			description:             "Populated - Dirty - No Change",
   805  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   806  			requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true},
   807  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   808  		},
   809  		{
   810  			description:             "Populated - Dirty - Cleared",
   811  			request:                 openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}},
   812  			requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true},
   813  			expectedRequest:         openrtb2.BidRequest{Device: &openrtb2.Device{}},
   814  		},
   815  	}
   816  
   817  	for _, test := range testCases {
   818  		// create required filed in the test loop to keep test declaration easier to read
   819  		test.requestDeviceExtWrapper.ext = make(map[string]json.RawMessage)
   820  
   821  		w := RequestWrapper{BidRequest: &test.request, deviceExt: &test.requestDeviceExtWrapper}
   822  		w.RebuildRequest()
   823  		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
   824  	}
   825  }
   826  
   827  func TestRebuildRequestExt(t *testing.T) {
   828  	prebidContent1 := ExtRequestPrebid{Integration: "1"}
   829  	prebidContent2 := ExtRequestPrebid{Integration: "2"}
   830  
   831  	testCases := []struct {
   832  		description              string
   833  		request                  openrtb2.BidRequest
   834  		requestRequestExtWrapper RequestExt
   835  		expectedRequest          openrtb2.BidRequest
   836  	}{
   837  		{
   838  			description:              "Empty - Not Dirty",
   839  			request:                  openrtb2.BidRequest{},
   840  			requestRequestExtWrapper: RequestExt{},
   841  			expectedRequest:          openrtb2.BidRequest{},
   842  		},
   843  		{
   844  			description:              "Empty - Dirty",
   845  			request:                  openrtb2.BidRequest{},
   846  			requestRequestExtWrapper: RequestExt{prebid: &prebidContent1, prebidDirty: true},
   847  			expectedRequest:          openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   848  		},
   849  		{
   850  			description:              "Empty - Dirty - No Change",
   851  			request:                  openrtb2.BidRequest{},
   852  			requestRequestExtWrapper: RequestExt{prebid: nil, prebidDirty: true},
   853  			expectedRequest:          openrtb2.BidRequest{},
   854  		},
   855  		{
   856  			description:              "Populated - Not Dirty",
   857  			request:                  openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   858  			requestRequestExtWrapper: RequestExt{},
   859  			expectedRequest:          openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   860  		},
   861  		{
   862  			description:              "Populated - Dirty",
   863  			request:                  openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   864  			requestRequestExtWrapper: RequestExt{prebid: &prebidContent2, prebidDirty: true},
   865  			expectedRequest:          openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"2"}}`)},
   866  		},
   867  		{
   868  			description:              "Populated - Dirty - No Change",
   869  			request:                  openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   870  			requestRequestExtWrapper: RequestExt{prebid: &prebidContent1, prebidDirty: true},
   871  			expectedRequest:          openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   872  		},
   873  		{
   874  			description:              "Populated - Dirty - Cleared",
   875  			request:                  openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"integration":"1"}}`)},
   876  			requestRequestExtWrapper: RequestExt{prebid: nil, prebidDirty: true},
   877  			expectedRequest:          openrtb2.BidRequest{},
   878  		},
   879  	}
   880  
   881  	for _, test := range testCases {
   882  		// create required filed in the test loop to keep test declaration easier to read
   883  		test.requestRequestExtWrapper.ext = make(map[string]json.RawMessage)
   884  
   885  		w := RequestWrapper{BidRequest: &test.request, requestExt: &test.requestRequestExtWrapper}
   886  		w.RebuildRequest()
   887  		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
   888  	}
   889  }
   890  
   891  func TestCloneRequestExt(t *testing.T) {
   892  	testCases := []struct {
   893  		name       string
   894  		reqExt     *RequestExt
   895  		reqExtCopy *RequestExt                            // manual copy of above ext object to verify against
   896  		mutator    func(t *testing.T, reqExt *RequestExt) // function to modify the Ext object
   897  	}{
   898  		{
   899  			name:       "Nil", // Verify the nil case
   900  			reqExt:     nil,
   901  			reqExtCopy: nil,
   902  			mutator:    func(t *testing.T, reqExt *RequestExt) {},
   903  		},
   904  		{
   905  			name: "NoMutate", // Verify the nil case
   906  			reqExt: &RequestExt{
   907  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
   908  				extDirty: true,
   909  				prebid: &ExtRequestPrebid{
   910  					BidderParams: json.RawMessage(`{}`),
   911  				},
   912  			},
   913  			reqExtCopy: &RequestExt{
   914  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
   915  				extDirty: true,
   916  				prebid: &ExtRequestPrebid{
   917  					BidderParams: json.RawMessage(`{}`),
   918  				},
   919  			},
   920  			mutator: func(t *testing.T, reqExt *RequestExt) {},
   921  		},
   922  		{
   923  			name: "General", // Verify the nil case
   924  			reqExt: &RequestExt{
   925  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
   926  				extDirty: true,
   927  				prebid: &ExtRequestPrebid{
   928  					BidderParams: json.RawMessage(`{}`),
   929  				},
   930  			},
   931  			reqExtCopy: &RequestExt{
   932  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
   933  				extDirty: true,
   934  				prebid: &ExtRequestPrebid{
   935  					BidderParams: json.RawMessage(`{}`),
   936  				},
   937  			},
   938  			mutator: func(t *testing.T, reqExt *RequestExt) {
   939  				reqExt.ext["A"] = json.RawMessage(`"string"`)
   940  				reqExt.ext["C"] = json.RawMessage(`{}`)
   941  				reqExt.extDirty = false
   942  				reqExt.prebid.Channel = &ExtRequestPrebidChannel{Name: "Bob"}
   943  				reqExt.prebid.BidderParams = nil
   944  				reqExt.prebid = nil
   945  			},
   946  		},
   947  		{
   948  			name: "SChain", // Verify the nil case
   949  			reqExt: &RequestExt{
   950  				schain: &openrtb2.SupplyChain{
   951  					Complete: 1,
   952  					Ver:      "1.1",
   953  					Nodes: []openrtb2.SupplyChainNode{
   954  						{ASI: "Is a", RID: "off", HP: ptrutil.ToPtr[int8](1)},
   955  						{ASI: "other", RID: "drift", HP: ptrutil.ToPtr[int8](0)},
   956  					},
   957  				},
   958  			},
   959  			reqExtCopy: &RequestExt{
   960  				schain: &openrtb2.SupplyChain{
   961  					Complete: 1,
   962  					Ver:      "1.1",
   963  					Nodes: []openrtb2.SupplyChainNode{
   964  						{ASI: "Is a", RID: "off", HP: ptrutil.ToPtr[int8](1)},
   965  						{ASI: "other", RID: "drift", HP: ptrutil.ToPtr[int8](0)},
   966  					},
   967  				},
   968  			},
   969  			mutator: func(t *testing.T, reqExt *RequestExt) {
   970  				reqExt.schain.Complete = 0
   971  				reqExt.schain.Ver = "1.2"
   972  				reqExt.schain.Nodes[0].ASI = "some"
   973  				reqExt.schain.Nodes[1].HP = nil
   974  				reqExt.schain.Nodes = append(reqExt.schain.Nodes, openrtb2.SupplyChainNode{ASI: "added"})
   975  				reqExt.schain = nil
   976  			},
   977  		},
   978  	}
   979  
   980  	for _, test := range testCases {
   981  		t.Run(test.name, func(t *testing.T) {
   982  			clone := test.reqExt.Clone()
   983  			test.mutator(t, test.reqExt)
   984  			assert.Equal(t, test.reqExtCopy, clone)
   985  		})
   986  	}
   987  
   988  }
   989  
   990  func TestCloneDeviceExt(t *testing.T) {
   991  	testCases := []struct {
   992  		name       string
   993  		devExt     *DeviceExt
   994  		devExtCopy *DeviceExt                            // manual copy of above ext object to verify against
   995  		mutator    func(t *testing.T, devExt *DeviceExt) // function to modify the Ext object
   996  	}{
   997  		{
   998  			name:       "Nil", // Verify the nil case
   999  			devExt:     nil,
  1000  			devExtCopy: nil,
  1001  			mutator:    func(t *testing.T, devExt *DeviceExt) {},
  1002  		},
  1003  		{
  1004  			name: "NoMutate",
  1005  			devExt: &DeviceExt{
  1006  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
  1007  				extDirty: true,
  1008  				prebid: &ExtDevicePrebid{
  1009  					Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0},
  1010  				},
  1011  			},
  1012  			devExtCopy: &DeviceExt{
  1013  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
  1014  				extDirty: true,
  1015  				prebid: &ExtDevicePrebid{
  1016  					Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0},
  1017  				},
  1018  			},
  1019  			mutator: func(t *testing.T, devExt *DeviceExt) {},
  1020  		},
  1021  		{
  1022  			name: "General",
  1023  			devExt: &DeviceExt{
  1024  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
  1025  				extDirty: true,
  1026  				prebid: &ExtDevicePrebid{
  1027  					Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0},
  1028  				},
  1029  			},
  1030  			devExtCopy: &DeviceExt{
  1031  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)},
  1032  				extDirty: true,
  1033  				prebid: &ExtDevicePrebid{
  1034  					Interstitial: &ExtDeviceInt{MinWidthPerc: 65, MinHeightPerc: 75},
  1035  				},
  1036  			},
  1037  			mutator: func(t *testing.T, devExt *DeviceExt) {
  1038  				devExt.ext["A"] = json.RawMessage(`"string"`)
  1039  				devExt.ext["C"] = json.RawMessage(`{}`)
  1040  				devExt.extDirty = false
  1041  				devExt.prebid.Interstitial.MinHeightPerc = 55
  1042  				devExt.prebid.Interstitial = &ExtDeviceInt{MinWidthPerc: 80}
  1043  				devExt.prebid = nil
  1044  			},
  1045  		},
  1046  	}
  1047  
  1048  	for _, test := range testCases {
  1049  		t.Run(test.name, func(t *testing.T) {
  1050  			clone := test.devExt.Clone()
  1051  			test.mutator(t, test.devExt)
  1052  			assert.Equal(t, test.devExtCopy, clone)
  1053  		})
  1054  	}
  1055  }
  1056  
  1057  func TestRebuildAppExt(t *testing.T) {
  1058  	prebidContent1 := ExtAppPrebid{Source: "1"}
  1059  	prebidContent2 := ExtAppPrebid{Source: "2"}
  1060  
  1061  	testCases := []struct {
  1062  		description          string
  1063  		request              openrtb2.BidRequest
  1064  		requestAppExtWrapper AppExt
  1065  		expectedRequest      openrtb2.BidRequest
  1066  	}{
  1067  		{
  1068  			description:          "Nil - Not Dirty",
  1069  			request:              openrtb2.BidRequest{},
  1070  			requestAppExtWrapper: AppExt{},
  1071  			expectedRequest:      openrtb2.BidRequest{},
  1072  		},
  1073  		{
  1074  			description:          "Nil - Dirty",
  1075  			request:              openrtb2.BidRequest{},
  1076  			requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true},
  1077  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1078  		},
  1079  		{
  1080  			description:          "Nil - Dirty - No Change",
  1081  			request:              openrtb2.BidRequest{},
  1082  			requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true},
  1083  			expectedRequest:      openrtb2.BidRequest{},
  1084  		},
  1085  		{
  1086  			description:          "Empty - Not Dirty",
  1087  			request:              openrtb2.BidRequest{App: &openrtb2.App{}},
  1088  			requestAppExtWrapper: AppExt{},
  1089  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{}},
  1090  		},
  1091  		{
  1092  			description:          "Empty - Dirty",
  1093  			request:              openrtb2.BidRequest{App: &openrtb2.App{}},
  1094  			requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true},
  1095  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1096  		},
  1097  		{
  1098  			description:          "Empty - Dirty - No Change",
  1099  			request:              openrtb2.BidRequest{App: &openrtb2.App{}},
  1100  			requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true},
  1101  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{}},
  1102  		},
  1103  		{
  1104  			description:          "Populated - Not Dirty",
  1105  			request:              openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1106  			requestAppExtWrapper: AppExt{},
  1107  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1108  		},
  1109  		{
  1110  			description:          "Populated - Dirty",
  1111  			request:              openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1112  			requestAppExtWrapper: AppExt{prebid: &prebidContent2, prebidDirty: true},
  1113  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"2"}}`)}},
  1114  		},
  1115  		{
  1116  			description:          "Populated - Dirty - No Change",
  1117  			request:              openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1118  			requestAppExtWrapper: AppExt{prebid: &prebidContent1, prebidDirty: true},
  1119  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1120  		},
  1121  		{
  1122  			description:          "Populated - Dirty - Cleared",
  1123  			request:              openrtb2.BidRequest{App: &openrtb2.App{Ext: json.RawMessage(`{"prebid":{"source":"1"}}`)}},
  1124  			requestAppExtWrapper: AppExt{prebid: nil, prebidDirty: true},
  1125  			expectedRequest:      openrtb2.BidRequest{App: &openrtb2.App{}},
  1126  		},
  1127  	}
  1128  
  1129  	for _, test := range testCases {
  1130  		// create required filed in the test loop to keep test declaration easier to read
  1131  		test.requestAppExtWrapper.ext = make(map[string]json.RawMessage)
  1132  
  1133  		w := RequestWrapper{BidRequest: &test.request, appExt: &test.requestAppExtWrapper}
  1134  		w.RebuildRequest()
  1135  		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
  1136  	}
  1137  }
  1138  
  1139  func TestCloneAppExt(t *testing.T) {
  1140  	testCases := []struct {
  1141  		name       string
  1142  		appExt     *AppExt
  1143  		appExtCopy *AppExt                            // manual copy of above ext object to verify against
  1144  		mutator    func(t *testing.T, appExt *AppExt) // function to modify the Ext object
  1145  	}{
  1146  		{
  1147  			name:       "Nil", // Verify the nil case
  1148  			appExt:     nil,
  1149  			appExtCopy: nil,
  1150  			mutator:    func(t *testing.T, appExt *AppExt) {},
  1151  		},
  1152  		{
  1153  			name: "NoMutate",
  1154  			appExt: &AppExt{
  1155  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1156  				extDirty: true,
  1157  				prebid: &ExtAppPrebid{
  1158  					Source:  "Sauce",
  1159  					Version: "2.2",
  1160  				},
  1161  			},
  1162  			appExtCopy: &AppExt{
  1163  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1164  				extDirty: true,
  1165  				prebid: &ExtAppPrebid{
  1166  					Source:  "Sauce",
  1167  					Version: "2.2",
  1168  				},
  1169  			},
  1170  			mutator: func(t *testing.T, appExt *AppExt) {},
  1171  		},
  1172  		{
  1173  			name: "General",
  1174  			appExt: &AppExt{
  1175  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1176  				extDirty: true,
  1177  				prebid: &ExtAppPrebid{
  1178  					Source:  "Sauce",
  1179  					Version: "2.2",
  1180  				},
  1181  			},
  1182  			appExtCopy: &AppExt{
  1183  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1184  				extDirty: true,
  1185  				prebid: &ExtAppPrebid{
  1186  					Source:  "Sauce",
  1187  					Version: "2.2",
  1188  				},
  1189  			},
  1190  			mutator: func(t *testing.T, appExt *AppExt) {
  1191  				appExt.ext["A"] = json.RawMessage(`"string"`)
  1192  				appExt.ext["C"] = json.RawMessage(`{}`)
  1193  				appExt.extDirty = false
  1194  				appExt.prebid.Source = "foobar"
  1195  				appExt.prebid = nil
  1196  			},
  1197  		},
  1198  	}
  1199  
  1200  	for _, test := range testCases {
  1201  		t.Run(test.name, func(t *testing.T) {
  1202  			clone := test.appExt.Clone()
  1203  			test.mutator(t, test.appExt)
  1204  			assert.Equal(t, test.appExtCopy, clone)
  1205  		})
  1206  	}
  1207  }
  1208  
  1209  func TestCloneRegExt(t *testing.T) {
  1210  	testCases := []struct {
  1211  		name       string
  1212  		regExt     *RegExt
  1213  		regExtCopy *RegExt                            // manual copy of above ext object to verify against
  1214  		mutator    func(t *testing.T, regExt *RegExt) // function to modify the Ext object
  1215  	}{
  1216  		{
  1217  			name:       "Nil", // Verify the nil case
  1218  			regExt:     nil,
  1219  			regExtCopy: nil,
  1220  			mutator:    func(t *testing.T, appExt *RegExt) {},
  1221  		},
  1222  		{
  1223  			name: "NoMutate",
  1224  			regExt: &RegExt{
  1225  				ext:            map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1226  				extDirty:       true,
  1227  				gdpr:           ptrutil.ToPtr[int8](1),
  1228  				usPrivacy:      "priv",
  1229  				usPrivacyDirty: true,
  1230  			},
  1231  			regExtCopy: &RegExt{
  1232  				ext:            map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1233  				extDirty:       true,
  1234  				gdpr:           ptrutil.ToPtr[int8](1),
  1235  				usPrivacy:      "priv",
  1236  				usPrivacyDirty: true,
  1237  			},
  1238  			mutator: func(t *testing.T, appExt *RegExt) {},
  1239  		},
  1240  		{
  1241  			name: "General",
  1242  			regExt: &RegExt{
  1243  				ext:            map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1244  				extDirty:       true,
  1245  				gdpr:           ptrutil.ToPtr[int8](1),
  1246  				usPrivacy:      "priv",
  1247  				usPrivacyDirty: true,
  1248  			},
  1249  			regExtCopy: &RegExt{
  1250  				ext:            map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1251  				extDirty:       true,
  1252  				gdpr:           ptrutil.ToPtr[int8](1),
  1253  				usPrivacy:      "priv",
  1254  				usPrivacyDirty: true,
  1255  			},
  1256  			mutator: func(t *testing.T, appExt *RegExt) {
  1257  				appExt.ext["A"] = json.RawMessage(`"string"`)
  1258  				appExt.ext["C"] = json.RawMessage(`{}`)
  1259  				appExt.extDirty = false
  1260  				appExt.gdpr = nil
  1261  				appExt.gdprDirty = true
  1262  				appExt.usPrivacy = "Other"
  1263  			},
  1264  		},
  1265  	}
  1266  
  1267  	for _, test := range testCases {
  1268  		t.Run(test.name, func(t *testing.T) {
  1269  			clone := test.regExt.Clone()
  1270  			test.mutator(t, test.regExt)
  1271  			assert.Equal(t, test.regExtCopy, clone)
  1272  		})
  1273  	}
  1274  }
  1275  
  1276  func TestRebuildSiteExt(t *testing.T) {
  1277  	int1 := int8(1)
  1278  	int2 := int8(2)
  1279  
  1280  	testCases := []struct {
  1281  		description           string
  1282  		request               openrtb2.BidRequest
  1283  		requestSiteExtWrapper SiteExt
  1284  		expectedRequest       openrtb2.BidRequest
  1285  	}{
  1286  		{
  1287  			description:           "Nil - Not Dirty",
  1288  			request:               openrtb2.BidRequest{},
  1289  			requestSiteExtWrapper: SiteExt{},
  1290  			expectedRequest:       openrtb2.BidRequest{},
  1291  		},
  1292  		{
  1293  			description:           "Nil - Dirty",
  1294  			request:               openrtb2.BidRequest{},
  1295  			requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true},
  1296  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1297  		},
  1298  		{
  1299  			description:           "Nil - Dirty - No Change",
  1300  			request:               openrtb2.BidRequest{},
  1301  			requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true},
  1302  			expectedRequest:       openrtb2.BidRequest{},
  1303  		},
  1304  		{
  1305  			description:           "Empty - Not Dirty",
  1306  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1307  			requestSiteExtWrapper: SiteExt{},
  1308  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1309  		},
  1310  		{
  1311  			description:           "Empty - Dirty",
  1312  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1313  			requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true},
  1314  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1315  		},
  1316  		{
  1317  			description:           "Empty - Dirty - No Change",
  1318  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1319  			requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true},
  1320  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1321  		},
  1322  		{
  1323  			description:           "Populated - Not Dirty",
  1324  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1325  			requestSiteExtWrapper: SiteExt{},
  1326  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1327  		},
  1328  		{
  1329  			description:           "Populated - Dirty",
  1330  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1331  			requestSiteExtWrapper: SiteExt{amp: &int2, ampDirty: true},
  1332  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":2}`)}},
  1333  		},
  1334  		{
  1335  			description:           "Populated - Dirty - No Change",
  1336  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1337  			requestSiteExtWrapper: SiteExt{amp: &int1, ampDirty: true},
  1338  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1339  		},
  1340  		{
  1341  			description:           "Populated - Dirty - Cleared",
  1342  			request:               openrtb2.BidRequest{Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}},
  1343  			requestSiteExtWrapper: SiteExt{amp: nil, ampDirty: true},
  1344  			expectedRequest:       openrtb2.BidRequest{Site: &openrtb2.Site{}},
  1345  		},
  1346  	}
  1347  
  1348  	for _, test := range testCases {
  1349  		// create required filed in the test loop to keep test declaration easier to read
  1350  		test.requestSiteExtWrapper.ext = make(map[string]json.RawMessage)
  1351  
  1352  		w := RequestWrapper{BidRequest: &test.request, siteExt: &test.requestSiteExtWrapper}
  1353  		w.RebuildRequest()
  1354  		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
  1355  	}
  1356  }
  1357  
  1358  func TestCloneSiteExt(t *testing.T) {
  1359  	testCases := []struct {
  1360  		name        string
  1361  		siteExt     *SiteExt
  1362  		siteExtCopy *SiteExt                             // manual copy of above ext object to verify against
  1363  		mutator     func(t *testing.T, siteExt *SiteExt) // function to modify the Ext object
  1364  	}{
  1365  		{
  1366  			name:        "Nil", // Verify the nil case
  1367  			siteExt:     nil,
  1368  			siteExtCopy: nil,
  1369  			mutator:     func(t *testing.T, siteExt *SiteExt) {},
  1370  		},
  1371  		{
  1372  			name: "NoMutate",
  1373  			siteExt: &SiteExt{
  1374  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1375  				extDirty: true,
  1376  				amp:      ptrutil.ToPtr[int8](1),
  1377  			},
  1378  			siteExtCopy: &SiteExt{
  1379  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1380  				extDirty: true,
  1381  				amp:      ptrutil.ToPtr[int8](1),
  1382  			},
  1383  			mutator: func(t *testing.T, siteExt *SiteExt) {},
  1384  		},
  1385  		{
  1386  			name: "General",
  1387  			siteExt: &SiteExt{
  1388  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1389  				extDirty: true,
  1390  				amp:      ptrutil.ToPtr[int8](1),
  1391  			},
  1392  			siteExtCopy: &SiteExt{
  1393  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1394  				extDirty: true,
  1395  				amp:      ptrutil.ToPtr[int8](1),
  1396  			},
  1397  			mutator: func(t *testing.T, siteExt *SiteExt) {
  1398  				siteExt.ext["A"] = json.RawMessage(`"string"`)
  1399  				siteExt.ext["C"] = json.RawMessage(`{}`)
  1400  				siteExt.extDirty = false
  1401  				siteExt.amp = nil
  1402  				siteExt.ampDirty = true
  1403  			},
  1404  		},
  1405  	}
  1406  
  1407  	for _, test := range testCases {
  1408  		t.Run(test.name, func(t *testing.T) {
  1409  			clone := test.siteExt.Clone()
  1410  			test.mutator(t, test.siteExt)
  1411  			assert.Equal(t, test.siteExtCopy, clone)
  1412  		})
  1413  	}
  1414  }
  1415  
  1416  func TestRebuildSourceExt(t *testing.T) {
  1417  	schainContent1 := openrtb2.SupplyChain{Ver: "1"}
  1418  	schainContent2 := openrtb2.SupplyChain{Ver: "2"}
  1419  
  1420  	testCases := []struct {
  1421  		description             string
  1422  		request                 openrtb2.BidRequest
  1423  		requestSourceExtWrapper SourceExt
  1424  		expectedRequest         openrtb2.BidRequest
  1425  	}{
  1426  		{
  1427  			description:             "Nil - Not Dirty",
  1428  			request:                 openrtb2.BidRequest{},
  1429  			requestSourceExtWrapper: SourceExt{},
  1430  			expectedRequest:         openrtb2.BidRequest{},
  1431  		},
  1432  		{
  1433  			description:             "Nil - Dirty",
  1434  			request:                 openrtb2.BidRequest{},
  1435  			requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true},
  1436  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1437  		},
  1438  		{
  1439  			description:             "Nil - Dirty - No Change",
  1440  			request:                 openrtb2.BidRequest{},
  1441  			requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true},
  1442  			expectedRequest:         openrtb2.BidRequest{},
  1443  		},
  1444  		{
  1445  			description:             "Empty - Not Dirty",
  1446  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1447  			requestSourceExtWrapper: SourceExt{},
  1448  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1449  		},
  1450  		{
  1451  			description:             "Empty - Dirty",
  1452  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1453  			requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true},
  1454  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1455  		},
  1456  		{
  1457  			description:             "Empty - Dirty - No Change",
  1458  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1459  			requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true},
  1460  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1461  		},
  1462  		{
  1463  			description:             "Populated - Not Dirty",
  1464  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1465  			requestSourceExtWrapper: SourceExt{},
  1466  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1467  		},
  1468  		{
  1469  			description:             "Populated - Dirty",
  1470  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1471  			requestSourceExtWrapper: SourceExt{schain: &schainContent2, schainDirty: true},
  1472  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"2"}}`)}},
  1473  		},
  1474  		{
  1475  			description:             "Populated - Dirty - No Change",
  1476  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1477  			requestSourceExtWrapper: SourceExt{schain: &schainContent1, schainDirty: true},
  1478  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1479  		},
  1480  		{
  1481  			description:             "Populated - Dirty - Cleared",
  1482  			request:                 openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":null,"ver":"1"}}`)}},
  1483  			requestSourceExtWrapper: SourceExt{schain: nil, schainDirty: true},
  1484  			expectedRequest:         openrtb2.BidRequest{Source: &openrtb2.Source{}},
  1485  		},
  1486  	}
  1487  
  1488  	for _, test := range testCases {
  1489  		// create required filed in the test loop to keep test declaration easier to read
  1490  		test.requestSourceExtWrapper.ext = make(map[string]json.RawMessage)
  1491  
  1492  		w := RequestWrapper{BidRequest: &test.request, sourceExt: &test.requestSourceExtWrapper}
  1493  		w.RebuildRequest()
  1494  		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
  1495  	}
  1496  }
  1497  
  1498  func TestCloneSourceExt(t *testing.T) {
  1499  	testCases := []struct {
  1500  		name          string
  1501  		sourceExt     *SourceExt
  1502  		sourceExtCopy *SourceExt                               // manual copy of above ext object to verify against
  1503  		mutator       func(t *testing.T, sourceExt *SourceExt) // function to modify the Ext object
  1504  	}{
  1505  		{
  1506  			name:          "Nil", // Verify the nil case
  1507  			sourceExt:     nil,
  1508  			sourceExtCopy: nil,
  1509  			mutator:       func(t *testing.T, sourceExt *SourceExt) {},
  1510  		},
  1511  		{
  1512  			name: "NoMutate",
  1513  			sourceExt: &SourceExt{
  1514  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1515  				extDirty: true,
  1516  				schain: &openrtb2.SupplyChain{
  1517  					Complete: 1,
  1518  					Ver:      "1.1",
  1519  					Nodes: []openrtb2.SupplyChainNode{
  1520  						{ASI: "Is a", RID: "off", HP: ptrutil.ToPtr[int8](1)},
  1521  						{ASI: "other", RID: "drift", HP: ptrutil.ToPtr[int8](0)},
  1522  					},
  1523  				},
  1524  			},
  1525  			sourceExtCopy: &SourceExt{
  1526  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1527  				extDirty: true,
  1528  				schain: &openrtb2.SupplyChain{
  1529  					Complete: 1,
  1530  					Ver:      "1.1",
  1531  					Nodes: []openrtb2.SupplyChainNode{
  1532  						{ASI: "Is a", RID: "off", HP: ptrutil.ToPtr[int8](1)},
  1533  						{ASI: "other", RID: "drift", HP: ptrutil.ToPtr[int8](0)},
  1534  					},
  1535  				},
  1536  			},
  1537  			mutator: func(t *testing.T, sourceExt *SourceExt) {
  1538  				sourceExt.ext["A"] = json.RawMessage(`"string"`)
  1539  				sourceExt.ext["C"] = json.RawMessage(`{}`)
  1540  				sourceExt.extDirty = false
  1541  				sourceExt.schain.Complete = 0
  1542  				sourceExt.schain.Ver = "1.2"
  1543  				sourceExt.schain.Nodes[0].ASI = "some"
  1544  				sourceExt.schain.Nodes[1].HP = nil
  1545  				sourceExt.schain.Nodes = append(sourceExt.schain.Nodes, openrtb2.SupplyChainNode{ASI: "added"})
  1546  				sourceExt.schain = nil
  1547  
  1548  			},
  1549  		},
  1550  	}
  1551  
  1552  	for _, test := range testCases {
  1553  		t.Run(test.name, func(t *testing.T) {
  1554  			clone := test.sourceExt.Clone()
  1555  			test.mutator(t, test.sourceExt)
  1556  			assert.Equal(t, test.sourceExtCopy, clone)
  1557  		})
  1558  	}
  1559  }
  1560  
  1561  func TestImpWrapperRebuildImp(t *testing.T) {
  1562  	var (
  1563  		isRewardedInventoryOne int8 = 1
  1564  		isRewardedInventoryTwo int8 = 2
  1565  	)
  1566  
  1567  	testCases := []struct {
  1568  		description   string
  1569  		imp           openrtb2.Imp
  1570  		impExtWrapper ImpExt
  1571  		expectedImp   openrtb2.Imp
  1572  	}{
  1573  		{
  1574  			description:   "Empty - Not Dirty",
  1575  			imp:           openrtb2.Imp{},
  1576  			impExtWrapper: ImpExt{},
  1577  			expectedImp:   openrtb2.Imp{},
  1578  		},
  1579  		{
  1580  			description:   "Empty - Dirty",
  1581  			imp:           openrtb2.Imp{},
  1582  			impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne}, prebidDirty: true},
  1583  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1584  		},
  1585  		{
  1586  			description:   "Empty - Dirty - No Change",
  1587  			imp:           openrtb2.Imp{},
  1588  			impExtWrapper: ImpExt{prebid: nil, prebidDirty: true},
  1589  			expectedImp:   openrtb2.Imp{},
  1590  		},
  1591  		{
  1592  			description:   "Populated - Not Dirty",
  1593  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1594  			impExtWrapper: ImpExt{},
  1595  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1596  		},
  1597  		{
  1598  			description:   "Populated - Dirty",
  1599  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1600  			impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryTwo}, prebidDirty: true},
  1601  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":2}}`)},
  1602  		},
  1603  		{
  1604  			description:   "Populated - Dirty - No Change",
  1605  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1606  			impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne}, prebidDirty: true},
  1607  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1608  		},
  1609  		{
  1610  			description:   "Populated - Dirty - Cleared",
  1611  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1612  			impExtWrapper: ImpExt{prebid: nil, prebidDirty: true},
  1613  			expectedImp:   openrtb2.Imp{},
  1614  		},
  1615  		{
  1616  			description:   "Populated - Dirty - Empty Prebid Object",
  1617  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)},
  1618  			impExtWrapper: ImpExt{prebid: &ExtImpPrebid{IsRewardedInventory: nil}, prebidDirty: true},
  1619  			expectedImp:   openrtb2.Imp{},
  1620  		},
  1621  		{
  1622  			description:   "Populated Tid - Dirty",
  1623  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"tid": "some-tid"}`)},
  1624  			impExtWrapper: ImpExt{tidDirty: true, tid: "12345"},
  1625  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"tid":"12345"}`)},
  1626  		},
  1627  		{
  1628  			description:   "Populated Tid - Dirty - No Change",
  1629  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"tid": "some-tid"}`)},
  1630  			impExtWrapper: ImpExt{tid: "some-tid", tidDirty: true},
  1631  			expectedImp:   openrtb2.Imp{Ext: json.RawMessage(`{"tid":"some-tid"}`)},
  1632  		},
  1633  		{
  1634  			description:   "Populated Tid - Dirty - Cleared",
  1635  			imp:           openrtb2.Imp{Ext: json.RawMessage(`{"tid":"some-tid"}`)},
  1636  			impExtWrapper: ImpExt{tid: "", tidDirty: true},
  1637  			expectedImp:   openrtb2.Imp{},
  1638  		},
  1639  	}
  1640  
  1641  	for _, test := range testCases {
  1642  		// create required filed in the test loop to keep test declaration easier to read
  1643  		test.impExtWrapper.ext = make(map[string]json.RawMessage)
  1644  
  1645  		w := &ImpWrapper{Imp: &test.imp, impExt: &test.impExtWrapper}
  1646  		w.RebuildImp()
  1647  		assert.Equal(t, test.expectedImp, *w.Imp, test.description)
  1648  	}
  1649  }
  1650  
  1651  func TestImpWrapperGetImpExt(t *testing.T) {
  1652  	var isRewardedInventoryOne int8 = 1
  1653  
  1654  	testCases := []struct {
  1655  		description    string
  1656  		givenWrapper   ImpWrapper
  1657  		expectedImpExt ImpExt
  1658  		expectedError  string
  1659  	}{
  1660  		{
  1661  			description:    "Empty",
  1662  			givenWrapper:   ImpWrapper{},
  1663  			expectedImpExt: ImpExt{ext: make(map[string]json.RawMessage)},
  1664  		},
  1665  		{
  1666  			description:  "Populated - Ext",
  1667  			givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1},"other":42,"tid":"test-tid","gpid":"test-gpid","data":{"adserver":{"name":"ads","adslot":"adslot123"},"pbadslot":"pbadslot123"}}`)}},
  1668  			expectedImpExt: ImpExt{
  1669  				ext: map[string]json.RawMessage{
  1670  					"prebid": json.RawMessage(`{"is_rewarded_inventory":1}`),
  1671  					"other":  json.RawMessage(`42`),
  1672  					"tid":    json.RawMessage(`"test-tid"`),
  1673  					"gpid":   json.RawMessage(`"test-gpid"`),
  1674  					"data":   json.RawMessage(`{"adserver":{"name":"ads","adslot":"adslot123"},"pbadslot":"pbadslot123"}`),
  1675  				},
  1676  				tid:  "test-tid",
  1677  				gpId: "test-gpid",
  1678  				data: &ExtImpData{
  1679  					AdServer: &ExtImpDataAdServer{
  1680  						Name:   "ads",
  1681  						AdSlot: "adslot123",
  1682  					},
  1683  					PbAdslot: "pbadslot123",
  1684  				},
  1685  				prebid: &ExtImpPrebid{IsRewardedInventory: &isRewardedInventoryOne},
  1686  			},
  1687  		},
  1688  		{
  1689  			description: "Already Retrieved - Dirty - Not Unmarshalled Again",
  1690  			givenWrapper: ImpWrapper{
  1691  				Imp:    &openrtb2.Imp{Ext: json.RawMessage(`{"will":"be ignored"}`)},
  1692  				impExt: &ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}}},
  1693  			expectedImpExt: ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}},
  1694  		},
  1695  		{
  1696  			description:   "Error - Ext",
  1697  			givenWrapper:  ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}},
  1698  			expectedError: "invalid character 'm' looking for beginning of value",
  1699  		},
  1700  		{
  1701  			description:   "Error - Ext - Prebid",
  1702  			givenWrapper:  ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}},
  1703  			expectedError: "invalid character 'm' looking for beginning of value",
  1704  		},
  1705  	}
  1706  
  1707  	for _, test := range testCases {
  1708  		impExt, err := test.givenWrapper.GetImpExt()
  1709  		if test.expectedError != "" {
  1710  			assert.EqualError(t, err, test.expectedError, test.description)
  1711  		} else {
  1712  			assert.NoError(t, err, test.description)
  1713  			assert.Equal(t, test.expectedImpExt, *impExt, test.description)
  1714  		}
  1715  	}
  1716  }
  1717  
  1718  func TestImpExtTid(t *testing.T) {
  1719  	impExt := &ImpExt{}
  1720  
  1721  	impExt.unmarshal(nil)
  1722  	assert.Equal(t, false, impExt.Dirty(), "New impext should not be dirty.")
  1723  	assert.Empty(t, impExt.GetTid(), "Empty ImpExt should have  empty tid")
  1724  
  1725  	newTid := "tid"
  1726  	impExt.SetTid(newTid)
  1727  	assert.Equal(t, "tid", impExt.GetTid(), "ImpExt tid is incorrect")
  1728  	assert.Equal(t, true, impExt.Dirty(), "New impext should be dirty.")
  1729  }
  1730  
  1731  func TestCloneImpWrapper(t *testing.T) {
  1732  	testCases := []struct {
  1733  		name           string
  1734  		impWrapper     *ImpWrapper
  1735  		impWrapperCopy *ImpWrapper                                // manual copy of above ext object to verify against
  1736  		mutator        func(t *testing.T, impWrapper *ImpWrapper) // function to modify the Ext object
  1737  	}{
  1738  		{
  1739  			name:           "Nil", // Verify the nil case
  1740  			impWrapper:     nil,
  1741  			impWrapperCopy: nil,
  1742  			mutator:        func(t *testing.T, impWrapper *ImpWrapper) {},
  1743  		},
  1744  		{
  1745  			name: "NoMutate",
  1746  			impWrapper: &ImpWrapper{
  1747  				impExt: &ImpExt{
  1748  					tid: "occupied",
  1749  				},
  1750  			},
  1751  			impWrapperCopy: &ImpWrapper{
  1752  				impExt: &ImpExt{
  1753  					tid: "occupied",
  1754  				},
  1755  			},
  1756  			mutator: func(t *testing.T, impWrapper *ImpWrapper) {},
  1757  		},
  1758  		{
  1759  			name: "General",
  1760  			impWrapper: &ImpWrapper{
  1761  				impExt: &ImpExt{
  1762  					tid: "occupied",
  1763  				},
  1764  			},
  1765  			impWrapperCopy: &ImpWrapper{
  1766  				impExt: &ImpExt{
  1767  					tid: "occupied",
  1768  				},
  1769  			},
  1770  			mutator: func(t *testing.T, impWrapper *ImpWrapper) {
  1771  				impWrapper.impExt.extDirty = true
  1772  				impWrapper.impExt.tid = "Something"
  1773  				impWrapper.impExt = nil
  1774  			},
  1775  		},
  1776  	}
  1777  
  1778  	for _, test := range testCases {
  1779  		t.Run(test.name, func(t *testing.T) {
  1780  			clone := test.impWrapper.Clone()
  1781  			test.mutator(t, test.impWrapper)
  1782  			assert.Equal(t, test.impWrapperCopy, clone)
  1783  		})
  1784  	}
  1785  }
  1786  
  1787  func TestCloneImpExt(t *testing.T) {
  1788  	testCases := []struct {
  1789  		name       string
  1790  		impExt     *ImpExt
  1791  		impExtCopy *ImpExt                            // manual copy of above ext object to verify against
  1792  		mutator    func(t *testing.T, impExt *ImpExt) // function to modify the Ext object
  1793  	}{
  1794  		{
  1795  			name:       "Nil", // Verify the nil case
  1796  			impExt:     nil,
  1797  			impExtCopy: nil,
  1798  			mutator:    func(t *testing.T, impExt *ImpExt) {},
  1799  		},
  1800  		{
  1801  			name: "NoMutate",
  1802  			impExt: &ImpExt{
  1803  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1804  				extDirty: true,
  1805  				tid:      "TID",
  1806  			},
  1807  			impExtCopy: &ImpExt{
  1808  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1809  				extDirty: true,
  1810  				tid:      "TID",
  1811  			},
  1812  			mutator: func(t *testing.T, impExt *ImpExt) {},
  1813  		},
  1814  		{
  1815  			name: "General",
  1816  			impExt: &ImpExt{
  1817  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1818  				extDirty: true,
  1819  				tid:      "TID",
  1820  			},
  1821  			impExtCopy: &ImpExt{
  1822  				ext:      map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)},
  1823  				extDirty: true,
  1824  				tid:      "TID",
  1825  			},
  1826  			mutator: func(t *testing.T, impExt *ImpExt) {
  1827  				impExt.ext["A"] = json.RawMessage(`"string"`)
  1828  				impExt.ext["C"] = json.RawMessage(`{}`)
  1829  				impExt.extDirty = false
  1830  				impExt.tid = "other"
  1831  				impExt.tidDirty = true
  1832  			},
  1833  		},
  1834  		{
  1835  			name: "Prebid",
  1836  			impExt: &ImpExt{
  1837  				prebid: &ExtImpPrebid{
  1838  					StoredRequest:         &ExtStoredRequest{ID: "abc123"},
  1839  					StoredAuctionResponse: &ExtStoredAuctionResponse{ID: "123abc"},
  1840  					StoredBidResponse: []ExtStoredBidResponse{
  1841  						{ID: "foo", Bidder: "bar", ReplaceImpId: ptrutil.ToPtr(true)},
  1842  						{ID: "def", Bidder: "xyz", ReplaceImpId: ptrutil.ToPtr(false)},
  1843  					},
  1844  					IsRewardedInventory: ptrutil.ToPtr[int8](1),
  1845  					Bidder: map[string]json.RawMessage{
  1846  						"abc": json.RawMessage(`{}`),
  1847  						"def": json.RawMessage(`{"alpha":"beta"}`),
  1848  					},
  1849  					Options:     &Options{EchoVideoAttrs: true},
  1850  					Passthrough: json.RawMessage(`{"foo":"bar"}`),
  1851  					Floors: &ExtImpPrebidFloors{
  1852  						FloorRule:      "Rule 16",
  1853  						FloorRuleValue: 16.17,
  1854  						FloorValue:     6.7,
  1855  					},
  1856  				},
  1857  			},
  1858  			impExtCopy: &ImpExt{
  1859  				prebid: &ExtImpPrebid{
  1860  					StoredRequest:         &ExtStoredRequest{ID: "abc123"},
  1861  					StoredAuctionResponse: &ExtStoredAuctionResponse{ID: "123abc"},
  1862  					StoredBidResponse: []ExtStoredBidResponse{
  1863  						{ID: "foo", Bidder: "bar", ReplaceImpId: ptrutil.ToPtr(true)},
  1864  						{ID: "def", Bidder: "xyz", ReplaceImpId: ptrutil.ToPtr(false)},
  1865  					},
  1866  					IsRewardedInventory: ptrutil.ToPtr[int8](1),
  1867  					Bidder: map[string]json.RawMessage{
  1868  						"abc": json.RawMessage(`{}`),
  1869  						"def": json.RawMessage(`{"alpha":"beta"}`),
  1870  					},
  1871  					Options:     &Options{EchoVideoAttrs: true},
  1872  					Passthrough: json.RawMessage(`{"foo":"bar"}`),
  1873  					Floors: &ExtImpPrebidFloors{
  1874  						FloorRule:      "Rule 16",
  1875  						FloorRuleValue: 16.17,
  1876  						FloorValue:     6.7,
  1877  					},
  1878  				},
  1879  			},
  1880  			mutator: func(t *testing.T, impExt *ImpExt) {
  1881  				impExt.prebid.StoredRequest.ID = "seventy"
  1882  				impExt.prebid.StoredRequest = nil
  1883  				impExt.prebid.StoredAuctionResponse.ID = "xyz"
  1884  				impExt.prebid.StoredAuctionResponse = nil
  1885  				impExt.prebid.StoredBidResponse[0].ID = "alpha"
  1886  				impExt.prebid.StoredBidResponse[1].ReplaceImpId = nil
  1887  				impExt.prebid.StoredBidResponse[0] = ExtStoredBidResponse{ID: "o", Bidder: "k", ReplaceImpId: ptrutil.ToPtr(false)}
  1888  				impExt.prebid.StoredBidResponse = append(impExt.prebid.StoredBidResponse, ExtStoredBidResponse{ID: "jay", Bidder: "walk"})
  1889  				impExt.prebid.IsRewardedInventory = nil
  1890  				impExt.prebid.Bidder["def"] = json.RawMessage(``)
  1891  				delete(impExt.prebid.Bidder, "abc")
  1892  				impExt.prebid.Bidder["xyz"] = json.RawMessage(`{"jar":5}`)
  1893  				impExt.prebid.Options.EchoVideoAttrs = false
  1894  				impExt.prebid.Options = nil
  1895  				impExt.prebid.Passthrough = json.RawMessage(`{}`)
  1896  				impExt.prebid.Floors.FloorRule = "Friday"
  1897  				impExt.prebid.Floors.FloorMinCur = "EUR"
  1898  				impExt.prebid.Floors = nil
  1899  			},
  1900  		},
  1901  	}
  1902  
  1903  	for _, test := range testCases {
  1904  		t.Run(test.name, func(t *testing.T) {
  1905  			clone := test.impExt.Clone()
  1906  			test.mutator(t, test.impExt)
  1907  			assert.Equal(t, test.impExtCopy, clone)
  1908  		})
  1909  	}
  1910  }