github.com/prebid/prebid-server@v0.275.0/amp/parse_test.go (about) 1 package amp 2 3 import ( 4 "net/http" 5 "testing" 6 7 "github.com/prebid/openrtb/v19/openrtb2" 8 "github.com/prebid/prebid-server/errortypes" 9 "github.com/prebid/prebid-server/privacy" 10 "github.com/prebid/prebid-server/privacy/ccpa" 11 "github.com/prebid/prebid-server/privacy/gdpr" 12 "github.com/stretchr/testify/assert" 13 ) 14 15 func TestParseParams(t *testing.T) { 16 var expectedTimeout uint64 = 42 17 18 testCases := []struct { 19 description string 20 query string 21 expectedParams Params 22 expectedError string 23 }{ 24 { 25 description: "Empty", 26 query: "", 27 expectedError: "AMP requests require an AMP tag_id", 28 }, 29 { 30 description: "All Fields", 31 // targeting data is encoded string that looks like this: {"gam-key1":"val1","gam-key2":"val2"} 32 query: "tag_id=anyTagID&account=anyAccount&curl=anyCurl&consent_string=anyConsent&debug=1&__amp_source_origin=anyOrigin" + 33 "&slot=anySlot&timeout=42&h=1&w=2&oh=3&ow=4&ms=10x11,12x13&targeting=%7B%22gam-key1%22%3A%22val1%22%2C%22gam-key2%22%3A%22val2%22%7D&trace=basic", 34 expectedParams: Params{ 35 Account: "anyAccount", 36 CanonicalURL: "anyCurl", 37 Consent: "anyConsent", 38 Debug: true, 39 Origin: "anyOrigin", 40 Slot: "anySlot", 41 StoredRequestID: "anyTagID", 42 Timeout: &expectedTimeout, 43 Size: Size{ 44 Height: 1, 45 OverrideHeight: 3, 46 OverrideWidth: 4, 47 Width: 2, 48 Multisize: []openrtb2.Format{ 49 {W: 10, H: 11}, {W: 12, H: 13}, 50 }, 51 }, 52 Targeting: `{"gam-key1":"val1","gam-key2":"val2"}`, 53 Trace: "basic", 54 }, 55 }, 56 { 57 description: "Integer Values Ignored If Invalid", 58 query: "tag_id=anyTagID&h=invalid&w=invalid&oh=invalid&ow=invalid&ms=invalid", 59 expectedParams: Params{StoredRequestID: "anyTagID"}, 60 }, 61 { 62 description: "consent_string Preferred Over gdpr_consent", 63 query: "tag_id=anyTagID&consent_string=consent1&gdpr_consent=consent2", 64 expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent1"}, 65 }, 66 { 67 description: "consent_string Preferred Over gdpr_consent - Order Doesn't Matter", 68 query: "tag_id=anyTagID&gdpr_consent=consent2&consent_string=consent1", 69 expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent1"}, 70 }, 71 { 72 description: "Just gdpr_consent", 73 query: "tag_id=anyTagID&gdpr_consent=consent2", 74 expectedParams: Params{StoredRequestID: "anyTagID", Consent: "consent2"}, 75 }, 76 { 77 description: "Debug 0", 78 query: "tag_id=anyTagID&debug=0", 79 expectedParams: Params{StoredRequestID: "anyTagID", Debug: false}, 80 }, 81 { 82 description: "Debug Ignored If Invalid", 83 query: "tag_id=anyTagID&debug=invalid", 84 expectedParams: Params{StoredRequestID: "anyTagID", Debug: false}, 85 }, 86 } 87 88 for _, test := range testCases { 89 httpRequest, err := http.NewRequest("GET", "http://any.url/anypage?"+test.query, nil) 90 assert.NoError(t, err, test.description+":request") 91 92 params, err := ParseParams(httpRequest) 93 assert.Equal(t, test.expectedParams, params, test.description+":params") 94 if test.expectedError == "" { 95 assert.NoError(t, err, test.description+":err") 96 } else { 97 assert.EqualError(t, err, test.expectedError) 98 } 99 } 100 } 101 102 func TestParseIntPtr(t *testing.T) { 103 var boolZero uint64 = 0 104 var boolOne uint64 = 1 105 106 type testResults struct { 107 intPtr *uint64 108 err bool 109 } 110 111 testCases := []struct { 112 desc string 113 input string 114 expected testResults 115 }{ 116 { 117 desc: "Input is an empty string: expect nil pointer and error", 118 input: "", 119 expected: testResults{ 120 intPtr: nil, 121 err: true, 122 }, 123 }, 124 { 125 desc: "Input is negative number: expect a nil pointer and error", 126 input: "-1", 127 expected: testResults{ 128 intPtr: nil, 129 err: true, 130 }, 131 }, 132 { 133 desc: "Input is a string depicting a zero value: expect a reference pointing to zero value, no error", 134 input: "0", 135 expected: testResults{ 136 intPtr: &boolZero, 137 err: false, 138 }, 139 }, 140 { 141 desc: "Input is a string depicting a value of 1: expect a reference pointing to the value of 1 and no error", 142 input: "1", 143 expected: testResults{ 144 intPtr: &boolOne, 145 err: false, 146 }, 147 }, 148 } 149 for _, tc := range testCases { 150 resultingIntPtr, resultingErr := parseIntPtr(tc.input) 151 152 assert.Equal(t, tc.expected.intPtr, resultingIntPtr, tc.desc) 153 if tc.expected.err { 154 assert.Error(t, resultingErr, tc.desc) 155 } else { 156 assert.NoError(t, resultingErr, tc.desc) 157 } 158 } 159 } 160 161 func TestParseBoolPtr(t *testing.T) { 162 boolTrue := true 163 boolFalse := false 164 165 type testResults struct { 166 boolPtr *bool 167 err bool 168 } 169 170 testCases := []struct { 171 desc string 172 input string 173 expected testResults 174 }{ 175 { 176 desc: "Input is an empty string: expect nil pointer and error", 177 input: "", 178 expected: testResults{ 179 boolPtr: nil, 180 err: true, 181 }, 182 }, 183 { 184 desc: "Input is neither true nor false: expect a nil pointer and error", 185 input: "other", 186 expected: testResults{ 187 boolPtr: nil, 188 err: true, 189 }, 190 }, 191 { 192 desc: "Input is the word 'false', expect a reference pointing to false value", 193 input: "false", 194 expected: testResults{ 195 boolPtr: &boolFalse, 196 err: false, 197 }, 198 }, 199 { 200 desc: "Input is the word 'true', expect a reference pointing to true value", 201 input: "true", 202 expected: testResults{ 203 boolPtr: &boolTrue, 204 err: false, 205 }, 206 }, 207 } 208 for _, tc := range testCases { 209 resultingBoolPtr, resultingErr := parseBoolPtr(tc.input) 210 211 assert.Equal(t, tc.expected.boolPtr, resultingBoolPtr, tc.desc) 212 if tc.expected.err { 213 assert.Error(t, resultingErr, tc.desc) 214 } else { 215 assert.NoError(t, resultingErr, tc.desc) 216 } 217 } 218 } 219 220 // TestPrivacyReader asserts the ReadPolicy scenarios 221 func TestPrivacyReader(t *testing.T) { 222 int8Zero := int8(0) 223 int8One := int8(1) 224 boolTrue := true 225 boolFalse := false 226 227 type testInput struct { 228 ampParams Params 229 } 230 type expectedResults struct { 231 policyWriter privacy.PolicyWriter 232 warning error 233 } 234 type testCase struct { 235 desc string 236 in testInput 237 expected expectedResults 238 } 239 240 testGroups := []struct { 241 groupDesc string 242 tests []testCase 243 }{ 244 { 245 groupDesc: "No consent string", 246 tests: []testCase{ 247 { 248 desc: "Params comes with an empty consent string, expect nil policy writer. No warning returned", 249 expected: expectedResults{policyWriter: privacy.NilPolicyWriter{}, warning: nil}, 250 }, 251 }, 252 }, 253 { 254 groupDesc: "TCF1", 255 tests: []testCase{ 256 { 257 desc: "Consent type TCF1: expect nil policy writer. Warning is returned", 258 in: testInput{ 259 ampParams: Params{Consent: "VALID_TCF1_CONSENT", ConsentType: ConsentTCF1}, 260 }, 261 expected: expectedResults{ 262 policyWriter: privacy.NilPolicyWriter{}, 263 warning: &errortypes.Warning{Message: "TCF1 consent is deprecated and no longer supported.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 264 }, 265 }, 266 }, 267 }, 268 { 269 groupDesc: "ConsentNone. In order to be backwards compatible, we'll guess what consent string this is", 270 tests: []testCase{ 271 { 272 desc: "No consent type was specified and invalid consent string provided: expect nil policy writer and a warning", 273 in: testInput{ 274 ampParams: Params{Consent: "NOT_CCPA_NOR_GDPR_TCF2"}, 275 }, 276 expected: expectedResults{ 277 policyWriter: privacy.NilPolicyWriter{}, 278 warning: &errortypes.Warning{Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 279 }, 280 }, 281 { 282 desc: "No consent type specified but query params come with a valid CCPA consent string: expect a CCPA consent writer and no error nor warning", 283 in: testInput{ 284 ampParams: Params{Consent: "1YYY"}, 285 }, 286 expected: expectedResults{ 287 policyWriter: ccpa.ConsentWriter{"1YYY"}, 288 warning: nil, 289 }, 290 }, 291 { 292 desc: "No consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", 293 in: testInput{ 294 ampParams: Params{Consent: "1YYY", GdprApplies: &boolTrue}, 295 }, 296 expected: expectedResults{ 297 policyWriter: ccpa.ConsentWriter{"1YYY"}, 298 warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 299 }, 300 }, 301 { 302 desc: "No consent type, valid GDPR consent string and gdpr_applies not set: expect a GDPR consent writer and no error nor warning", 303 in: testInput{ 304 ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, 305 }, 306 expected: expectedResults{ 307 policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, 308 warning: nil, 309 }, 310 }, 311 }, 312 }, 313 { 314 groupDesc: "Unrecognized consent type. In order to be backwards compatible, we'll guess what consent string type it is", 315 tests: []testCase{ 316 { 317 desc: "Unrecognized consent type was specified and invalid consent string provided: expect nil policy writer and a warning", 318 in: testInput{ 319 ampParams: Params{ConsentType: 101, Consent: "NOT_CCPA_NOR_GDPR_TCF2"}, 320 }, 321 expected: expectedResults{ 322 policyWriter: privacy.NilPolicyWriter{}, 323 warning: &errortypes.Warning{Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 324 }, 325 }, 326 { 327 desc: "Unrecognized consent type specified but query params come with a valid CCPA consent string: expect a CCPA consent writer and no error nor warning", 328 in: testInput{ 329 ampParams: Params{ConsentType: 101, Consent: "1YYY"}, 330 }, 331 expected: expectedResults{ 332 policyWriter: ccpa.ConsentWriter{"1YYY"}, 333 warning: nil, 334 }, 335 }, 336 { 337 desc: "Unrecognized consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", 338 in: testInput{ 339 ampParams: Params{ConsentType: 101, Consent: "1YYY", GdprApplies: &boolTrue}, 340 }, 341 expected: expectedResults{ 342 policyWriter: ccpa.ConsentWriter{"1YYY"}, 343 warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 344 }, 345 }, 346 { 347 desc: "Unrecognized consent type, valid TCF2 consent string and gdpr_applies not set: expect GDPR consent writer and no error nor warning", 348 in: testInput{ 349 ampParams: Params{ConsentType: 101, Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, 350 }, 351 expected: expectedResults{ 352 policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, 353 warning: nil, 354 }, 355 }, 356 }, 357 }, 358 { 359 groupDesc: "consent type TCF2. Return a valid GDPR consent writer in all scenarios.", 360 tests: []testCase{ 361 { 362 desc: "GDPR consent string is invalid, but consent type is TCF2: return a valid GDPR writer and warn about the GDPR string being invalid", 363 in: testInput{ 364 ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: nil}, 365 }, 366 expected: expectedResults{ 367 policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8One}, 368 warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 369 }, 370 }, 371 { 372 desc: "GDPR consent string is invalid, consent type is TCF2, gdpr_applies is set to true: return a valid GDPR writer and warn about the GDPR string being invalid", 373 in: testInput{ 374 ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, 375 }, 376 expected: expectedResults{ 377 policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8Zero}, 378 warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 379 }, 380 }, 381 { 382 desc: "Valid GDPR consent string, gdpr_applies is set to false, return a valid GDPR writer, no warning", 383 in: testInput{ 384 ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, 385 }, 386 expected: expectedResults{ 387 policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8Zero}, 388 warning: nil, 389 }, 390 }, 391 { 392 desc: "Valid GDPR consent string, gdpr_applies is set to true, return a valid GDPR writer and no warning", 393 in: testInput{ 394 ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolTrue}, 395 }, 396 expected: expectedResults{ 397 policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, 398 warning: nil, 399 }, 400 }, 401 { 402 desc: "Valid GDPR consent string, return a valid GDPR writer and no warning", 403 in: testInput{ 404 ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2}, 405 }, 406 expected: expectedResults{ 407 policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, 408 warning: nil, 409 }, 410 }, 411 }, 412 }, 413 { 414 groupDesc: "consent type CCPA. Return a valid CCPA consent writer in all scenarios.", 415 tests: []testCase{ 416 { 417 desc: "CCPA consent string is invalid: return a valid writer a warning about the string being invalid", 418 in: testInput{ 419 ampParams: Params{Consent: "XXXX", ConsentType: ConsentUSPrivacy}, 420 }, 421 expected: expectedResults{ 422 policyWriter: ccpa.ConsentWriter{"XXXX"}, 423 warning: &errortypes.Warning{Message: "Consent string 'XXXX' is not a valid CCPA consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 424 }, 425 }, 426 { 427 desc: "Valid CCPA consent string, gdpr_applies is set to true: return a valid GDPR writer and warn about the gdpr_applies value.", 428 in: testInput{ 429 ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy, GdprApplies: &boolTrue}, 430 }, 431 expected: expectedResults{ 432 policyWriter: ccpa.ConsentWriter{"1YYY"}, 433 warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, 434 }, 435 }, 436 { 437 desc: "Valid CCPA consent string, return a valid GDPR writer and no warning", 438 in: testInput{ 439 ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy}, 440 }, 441 expected: expectedResults{ 442 policyWriter: ccpa.ConsentWriter{"1YYY"}, 443 warning: nil, 444 }, 445 }, 446 }, 447 }, 448 } 449 for _, group := range testGroups { 450 for _, tc := range group.tests { 451 actualPolicyWriter, actualErr := ReadPolicy(tc.in.ampParams, true) 452 453 assert.Equal(t, tc.expected.policyWriter, actualPolicyWriter, tc.desc) 454 assert.Equal(t, tc.expected.warning, actualErr, tc.desc) 455 } 456 } 457 } 458 459 func TestBuildGdprTCF2ConsentWriter(t *testing.T) { 460 int8Zero := int8(0) 461 int8One := int8(1) 462 boolTrue := true 463 boolFalse := false 464 consentString := "CONSENT" 465 466 testCases := []struct { 467 desc string 468 inParams Params 469 expectedWriter gdpr.ConsentWriter 470 }{ 471 { 472 desc: "gdpr_applies not set", 473 inParams: Params{Consent: consentString}, 474 expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, 475 }, 476 { 477 desc: "gdpr_applies set to false", 478 inParams: Params{Consent: consentString, GdprApplies: &boolFalse}, 479 expectedWriter: gdpr.ConsentWriter{consentString, &int8Zero}, 480 }, 481 { 482 desc: "gdpr_applies set to true", 483 inParams: Params{Consent: consentString, GdprApplies: &boolTrue}, 484 expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, 485 }, 486 } 487 for _, tc := range testCases { 488 actualPolicyWriter := buildGdprTCF2ConsentWriter(tc.inParams) 489 assert.Equal(t, tc.expectedWriter, actualPolicyWriter, tc.desc) 490 } 491 } 492 493 func TestParseMultisize(t *testing.T) { 494 testCases := []struct { 495 description string 496 multisize string 497 expectedFormats []openrtb2.Format 498 }{ 499 { 500 description: "Empty", 501 multisize: "", 502 expectedFormats: nil, 503 }, 504 { 505 description: "One", 506 multisize: "1x2", 507 expectedFormats: []openrtb2.Format{{W: 1, H: 2}}, 508 }, 509 { 510 description: "Many", 511 multisize: "1x2,3x4", 512 expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 3, H: 4}}, 513 }, 514 { 515 // Existing Behavior: The " 3" token in the second size is parsed as 0. 516 description: "Many With Space - Quirky Result", 517 multisize: "1x2, 3x4", 518 expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 0, H: 4}}, 519 }, 520 { 521 description: "One - Zero Size - Ignored", 522 multisize: "0x0", 523 expectedFormats: nil, 524 }, 525 { 526 description: "Many - Zero Size - All Ignored", 527 multisize: "0x0,3x4", 528 expectedFormats: nil, 529 }, 530 { 531 description: "One - Extra Dimension - Ignored", 532 multisize: "1x2x3", 533 expectedFormats: nil, 534 }, 535 { 536 description: "Many - Extra Dimension - All Ignored", 537 multisize: "1x2x3,4x5", 538 expectedFormats: nil, 539 }, 540 { 541 description: "One - Invalid Values - Ignored", 542 multisize: "INVALIDxINVALID", 543 expectedFormats: nil, 544 }, 545 { 546 description: "Many - Invalid Values - All Ignored", 547 multisize: "1x2,INVALIDxINVALID", 548 expectedFormats: nil, 549 }, 550 { 551 description: "One - No Pair - Ignored", 552 multisize: "INVALID", 553 expectedFormats: nil, 554 }, 555 { 556 description: "Many - No Pair - All Ignored", 557 multisize: "1x2,INVALID", 558 expectedFormats: nil, 559 }, 560 } 561 562 for _, test := range testCases { 563 result := parseMultisize(test.multisize) 564 assert.ElementsMatch(t, test.expectedFormats, result, test.description) 565 } 566 } 567 568 func TestParseGdprApplies(t *testing.T) { 569 gdprAppliesFalse := false 570 gdprAppliesTrue := true 571 572 testCases := []struct { 573 desc string 574 inGdprApplies *bool 575 expectRegsExtGdpr int8 576 }{ 577 { 578 desc: "gdprApplies was not set and defaulted to nil, expect 0", 579 inGdprApplies: nil, 580 expectRegsExtGdpr: int8(0), 581 }, 582 { 583 desc: "gdprApplies isn't nil and is set to false, expect a value of 0", 584 inGdprApplies: &gdprAppliesFalse, 585 expectRegsExtGdpr: int8(0), 586 }, 587 { 588 desc: "gdprApplies isn't nil and is set to true, expect a value of 1", 589 inGdprApplies: &gdprAppliesTrue, 590 expectRegsExtGdpr: int8(1), 591 }, 592 } 593 for _, tc := range testCases { 594 assert.Equal(t, tc.expectRegsExtGdpr, parseGdprApplies(tc.inGdprApplies), tc.desc) 595 } 596 }