github.com/prebid/prebid-server/v2@v2.18.0/amp/parse_test.go (about) 1 package amp 2 3 import ( 4 "net/http" 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/privacy" 10 "github.com/prebid/prebid-server/v2/privacy/ccpa" 11 "github.com/prebid/prebid-server/v2/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{Consent: "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{ 295 Consent: "1YYY", 296 GdprApplies: &boolTrue, 297 }, 298 }, 299 expected: expectedResults{ 300 policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, 301 warning: &errortypes.Warning{ 302 Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", 303 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 304 }, 305 }, 306 }, 307 { 308 desc: "No consent type, valid GDPR consent string and gdpr_applies not set: expect a GDPR consent writer and no error nor warning", 309 in: testInput{ 310 ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, 311 }, 312 expected: expectedResults{ 313 policyWriter: gdpr.ConsentWriter{ 314 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 315 RegExtGDPR: &int8One, 316 }, 317 warning: nil, 318 }, 319 }, 320 }, 321 }, 322 { 323 groupDesc: "Unrecognized consent type. In order to be backwards compatible, we'll guess what consent string type it is", 324 tests: []testCase{ 325 { 326 desc: "Unrecognized consent type was specified and invalid consent string provided: expect nil policy writer and a warning", 327 in: testInput{ 328 ampParams: Params{ 329 ConsentType: 101, 330 Consent: "NOT_CCPA_NOR_GDPR_TCF2", 331 }, 332 }, 333 expected: expectedResults{ 334 policyWriter: privacy.NilPolicyWriter{}, 335 warning: &errortypes.Warning{ 336 Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", 337 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 338 }, 339 }, 340 }, 341 { 342 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", 343 in: testInput{ 344 ampParams: Params{ 345 ConsentType: 101, 346 Consent: "1YYY", 347 }, 348 }, 349 expected: expectedResults{ 350 policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, 351 warning: nil, 352 }, 353 }, 354 { 355 desc: "Unrecognized consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", 356 in: testInput{ 357 ampParams: Params{ 358 ConsentType: 101, 359 Consent: "1YYY", 360 GdprApplies: &boolTrue, 361 }, 362 }, 363 expected: expectedResults{ 364 policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, 365 warning: &errortypes.Warning{ 366 Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", 367 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 368 }, 369 }, 370 }, 371 { 372 desc: "Unrecognized consent type, valid TCF2 consent string and gdpr_applies not set: expect GDPR consent writer and no error nor warning", 373 in: testInput{ 374 ampParams: Params{ 375 ConsentType: 101, 376 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 377 }, 378 }, 379 expected: expectedResults{ 380 policyWriter: gdpr.ConsentWriter{ 381 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 382 RegExtGDPR: &int8One, 383 }, 384 warning: nil, 385 }, 386 }, 387 }, 388 }, 389 { 390 groupDesc: "consent type TCF2. Return a valid GDPR consent writer in all scenarios.", 391 tests: []testCase{ 392 { 393 desc: "GDPR consent string is invalid, but consent type is TCF2: return a valid GDPR writer and warn about the GDPR string being invalid", 394 in: testInput{ 395 ampParams: Params{ 396 Consent: "INVALID_GDPR", 397 ConsentType: ConsentTCF2, 398 GdprApplies: nil, 399 }, 400 }, 401 expected: expectedResults{ 402 policyWriter: gdpr.ConsentWriter{ 403 Consent: "INVALID_GDPR", 404 RegExtGDPR: &int8One, 405 }, 406 warning: &errortypes.Warning{ 407 Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", 408 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 409 }, 410 }, 411 }, 412 { 413 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", 414 in: testInput{ 415 ampParams: Params{ 416 Consent: "INVALID_GDPR", 417 ConsentType: ConsentTCF2, 418 GdprApplies: &boolFalse, 419 }, 420 }, 421 expected: expectedResults{ 422 policyWriter: gdpr.ConsentWriter{ 423 Consent: "INVALID_GDPR", 424 RegExtGDPR: &int8Zero, 425 }, 426 warning: &errortypes.Warning{ 427 Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", 428 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 429 }, 430 }, 431 }, 432 { 433 desc: "Valid GDPR consent string, gdpr_applies is set to false, return a valid GDPR writer, no warning", 434 in: testInput{ 435 ampParams: Params{ 436 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 437 ConsentType: ConsentTCF2, 438 GdprApplies: &boolFalse, 439 }, 440 }, 441 expected: expectedResults{ 442 policyWriter: gdpr.ConsentWriter{ 443 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 444 RegExtGDPR: &int8Zero, 445 }, 446 warning: nil, 447 }, 448 }, 449 { 450 desc: "Valid GDPR consent string, gdpr_applies is set to true, return a valid GDPR writer and no warning", 451 in: testInput{ 452 ampParams: Params{ 453 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 454 ConsentType: ConsentTCF2, 455 GdprApplies: &boolTrue, 456 }, 457 }, 458 expected: expectedResults{ 459 policyWriter: gdpr.ConsentWriter{ 460 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 461 RegExtGDPR: &int8One, 462 }, 463 warning: nil, 464 }, 465 }, 466 { 467 desc: "Valid GDPR consent string, return a valid GDPR writer and no warning", 468 in: testInput{ 469 ampParams: Params{ 470 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 471 ConsentType: ConsentTCF2, 472 }, 473 }, 474 expected: expectedResults{ 475 policyWriter: gdpr.ConsentWriter{ 476 Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", 477 RegExtGDPR: &int8One, 478 }, 479 warning: nil, 480 }, 481 }, 482 }, 483 }, 484 { 485 groupDesc: "consent type CCPA. Return a valid CCPA consent writer in all scenarios.", 486 tests: []testCase{ 487 { 488 desc: "CCPA consent string is invalid: return a valid writer a warning about the string being invalid", 489 in: testInput{ 490 ampParams: Params{ 491 Consent: "XXXX", 492 ConsentType: ConsentUSPrivacy, 493 }, 494 }, 495 expected: expectedResults{ 496 policyWriter: ccpa.ConsentWriter{Consent: "XXXX"}, 497 warning: &errortypes.Warning{ 498 Message: "Consent string 'XXXX' is not a valid CCPA consent string.", 499 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 500 }, 501 }, 502 }, 503 { 504 desc: "Valid CCPA consent string, gdpr_applies is set to true: return a valid GDPR writer and warn about the gdpr_applies value.", 505 in: testInput{ 506 ampParams: Params{ 507 Consent: "1YYY", 508 ConsentType: ConsentUSPrivacy, 509 GdprApplies: &boolTrue, 510 }, 511 }, 512 expected: expectedResults{ 513 policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, 514 warning: &errortypes.Warning{ 515 Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", 516 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 517 }, 518 }, 519 }, 520 { 521 desc: "Valid CCPA consent string, return a valid GDPR writer and no warning", 522 in: testInput{ 523 ampParams: Params{ 524 Consent: "1YYY", 525 ConsentType: ConsentUSPrivacy, 526 }, 527 }, 528 expected: expectedResults{ 529 policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, 530 warning: nil, 531 }, 532 }, 533 }, 534 }, 535 } 536 for _, group := range testGroups { 537 for _, tc := range group.tests { 538 actualPolicyWriter, actualErr := ReadPolicy(tc.in.ampParams, true) 539 540 assert.Equal(t, tc.expected.policyWriter, actualPolicyWriter, tc.desc) 541 assert.Equal(t, tc.expected.warning, actualErr, tc.desc) 542 } 543 } 544 } 545 546 func TestBuildGdprTCF2ConsentWriter(t *testing.T) { 547 int8Zero := int8(0) 548 int8One := int8(1) 549 boolTrue := true 550 boolFalse := false 551 consentString := "CONSENT" 552 553 testCases := []struct { 554 desc string 555 inParams Params 556 expectedWriter gdpr.ConsentWriter 557 }{ 558 { 559 desc: "gdpr_applies not set", 560 inParams: Params{Consent: consentString}, 561 expectedWriter: gdpr.ConsentWriter{ 562 Consent: consentString, 563 RegExtGDPR: &int8One, 564 }, 565 }, 566 { 567 desc: "gdpr_applies set to false", 568 inParams: Params{ 569 Consent: consentString, 570 GdprApplies: &boolFalse, 571 }, 572 expectedWriter: gdpr.ConsentWriter{ 573 Consent: consentString, 574 RegExtGDPR: &int8Zero, 575 }, 576 }, 577 { 578 desc: "gdpr_applies set to true", 579 inParams: Params{ 580 Consent: consentString, 581 GdprApplies: &boolTrue, 582 }, 583 expectedWriter: gdpr.ConsentWriter{ 584 Consent: consentString, 585 RegExtGDPR: &int8One, 586 }, 587 }, 588 } 589 for _, tc := range testCases { 590 actualPolicyWriter := buildGdprTCF2ConsentWriter(tc.inParams) 591 assert.Equal(t, tc.expectedWriter, actualPolicyWriter, tc.desc) 592 } 593 } 594 595 func TestParseMultisize(t *testing.T) { 596 testCases := []struct { 597 description string 598 multisize string 599 expectedFormats []openrtb2.Format 600 }{ 601 { 602 description: "Empty", 603 multisize: "", 604 expectedFormats: nil, 605 }, 606 { 607 description: "One", 608 multisize: "1x2", 609 expectedFormats: []openrtb2.Format{{W: 1, H: 2}}, 610 }, 611 { 612 description: "Many", 613 multisize: "1x2,3x4", 614 expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 3, H: 4}}, 615 }, 616 { 617 // Existing Behavior: The " 3" token in the second size is parsed as 0. 618 description: "Many With Space - Quirky Result", 619 multisize: "1x2, 3x4", 620 expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 0, H: 4}}, 621 }, 622 { 623 description: "One - Zero Size - Ignored", 624 multisize: "0x0", 625 expectedFormats: nil, 626 }, 627 { 628 description: "Many - Zero Size - All Ignored", 629 multisize: "0x0,3x4", 630 expectedFormats: nil, 631 }, 632 { 633 description: "One - Extra Dimension - Ignored", 634 multisize: "1x2x3", 635 expectedFormats: nil, 636 }, 637 { 638 description: "Many - Extra Dimension - All Ignored", 639 multisize: "1x2x3,4x5", 640 expectedFormats: nil, 641 }, 642 { 643 description: "One - Invalid Values - Ignored", 644 multisize: "INVALIDxINVALID", 645 expectedFormats: nil, 646 }, 647 { 648 description: "Many - Invalid Values - All Ignored", 649 multisize: "1x2,INVALIDxINVALID", 650 expectedFormats: nil, 651 }, 652 { 653 description: "One - No Pair - Ignored", 654 multisize: "INVALID", 655 expectedFormats: nil, 656 }, 657 { 658 description: "Many - No Pair - All Ignored", 659 multisize: "1x2,INVALID", 660 expectedFormats: nil, 661 }, 662 } 663 664 for _, test := range testCases { 665 result := parseMultisize(test.multisize) 666 assert.ElementsMatch(t, test.expectedFormats, result, test.description) 667 } 668 } 669 670 func TestParseGdprApplies(t *testing.T) { 671 gdprAppliesFalse := false 672 gdprAppliesTrue := true 673 674 testCases := []struct { 675 desc string 676 inGdprApplies *bool 677 expectRegsExtGdpr int8 678 }{ 679 { 680 desc: "gdprApplies was not set and defaulted to nil, expect 0", 681 inGdprApplies: nil, 682 expectRegsExtGdpr: int8(0), 683 }, 684 { 685 desc: "gdprApplies isn't nil and is set to false, expect a value of 0", 686 inGdprApplies: &gdprAppliesFalse, 687 expectRegsExtGdpr: int8(0), 688 }, 689 { 690 desc: "gdprApplies isn't nil and is set to true, expect a value of 1", 691 inGdprApplies: &gdprAppliesTrue, 692 expectRegsExtGdpr: int8(1), 693 }, 694 } 695 for _, tc := range testCases { 696 assert.Equal(t, tc.expectRegsExtGdpr, parseGdprApplies(tc.inGdprApplies), tc.desc) 697 } 698 }