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