github.com/prebid/prebid-server/v2@v2.18.0/endpoints/openrtb2/test_utils.go (about) 1 package openrtb2 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net" 10 "net/http" 11 "net/http/httptest" 12 "os" 13 "strconv" 14 "testing" 15 "time" 16 17 "github.com/buger/jsonparser" 18 "github.com/julienschmidt/httprouter" 19 "github.com/prebid/openrtb/v20/openrtb2" 20 "github.com/prebid/openrtb/v20/openrtb3" 21 "github.com/prebid/prebid-server/v2/adapters" 22 "github.com/prebid/prebid-server/v2/analytics" 23 analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" 24 "github.com/prebid/prebid-server/v2/config" 25 "github.com/prebid/prebid-server/v2/currency" 26 "github.com/prebid/prebid-server/v2/errortypes" 27 "github.com/prebid/prebid-server/v2/exchange" 28 "github.com/prebid/prebid-server/v2/experiment/adscert" 29 "github.com/prebid/prebid-server/v2/gdpr" 30 "github.com/prebid/prebid-server/v2/hooks" 31 "github.com/prebid/prebid-server/v2/hooks/hookexecution" 32 "github.com/prebid/prebid-server/v2/hooks/hookstage" 33 "github.com/prebid/prebid-server/v2/macros" 34 "github.com/prebid/prebid-server/v2/metrics" 35 metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" 36 "github.com/prebid/prebid-server/v2/openrtb_ext" 37 pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" 38 "github.com/prebid/prebid-server/v2/stored_requests" 39 "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" 40 "github.com/prebid/prebid-server/v2/util/iputil" 41 "github.com/prebid/prebid-server/v2/util/jsonutil" 42 "github.com/prebid/prebid-server/v2/util/uuidutil" 43 jsonpatch "gopkg.in/evanphx/json-patch.v4" 44 ) 45 46 // In this file we define: 47 // - Auxiliary types 48 // - Unit test interface implementations such as mocks 49 // - Other auxiliary functions that don't make assertions and don't take t *testing.T as parameter 50 // 51 // All of the above are useful for this package's unit test framework. 52 53 // ---------------------- 54 // test auxiliary types 55 // ---------------------- 56 const maxSize = 1024 * 256 57 58 const ( 59 AMP_ENDPOINT = iota 60 OPENRTB_ENDPOINT 61 VIDEO_ENDPOINT 62 ) 63 64 type testCase struct { 65 // Common 66 endpointType int 67 Description string `json:"description"` 68 Config *testConfigValues `json:"config"` 69 BidRequest json.RawMessage `json:"mockBidRequest"` 70 ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` 71 ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` 72 ExpectedErrorMessage string `json:"expectedErrorMessage"` 73 Query string `json:"query"` 74 planBuilder hooks.ExecutionPlanBuilder 75 76 // "/openrtb2/auction" endpoint JSON test info 77 ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` 78 79 // "/openrtb2/amp" endpoint JSON test info 80 StoredRequest map[string]json.RawMessage `json:"mockAmpStoredRequest"` 81 StoredResponse map[string]json.RawMessage `json:"mockAmpStoredResponse"` 82 ExpectedAmpResponse json.RawMessage `json:"expectedAmpResponse"` 83 } 84 85 type testConfigValues struct { 86 AccountRequired bool `json:"accountRequired"` 87 AliasJSON string `json:"aliases"` 88 BlacklistedApps []string `json:"blacklistedApps"` 89 DisabledAdapters []string `json:"disabledAdapters"` 90 CurrencyRates map[string]map[string]float64 `json:"currencyRates"` 91 MockBidders []mockBidderHandler `json:"mockBidders"` 92 RealParamsValidator bool `json:"realParamsValidator"` 93 } 94 95 type brokenExchange struct{} 96 97 func (e *brokenExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 98 return nil, errors.New("Critical, unrecoverable error.") 99 } 100 101 // Stored Requests 102 var testStoredRequestData = map[string]json.RawMessage{ 103 // Valid JSON 104 "1": json.RawMessage(`{"id": "{{UUID}}"}`), 105 "2": json.RawMessage(`{ 106 "id": "{{uuid}}", 107 "tmax": 500, 108 "ext": { 109 "prebid": { 110 "targeting": { 111 "pricegranularity": "low" 112 } 113 } 114 } 115 }`), 116 // Invalid JSON because it comes with an extra closing curly brace '}' 117 "3": json.RawMessage(`{ 118 "tmax": 500, 119 "ext": { 120 "prebid": { 121 "targeting": { 122 "pricegranularity": "low" 123 } 124 } 125 }} 126 }`), 127 // Valid JSON 128 "4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`), 129 130 // Stored Request with Root Ext Passthrough 131 "5": json.RawMessage(`{ 132 "ext": { 133 "prebid": { 134 "passthrough": { 135 "root_ext_passthrough": 20 136 } 137 } 138 } 139 }`), 140 } 141 142 // Stored Imp Requests 143 var testStoredImpData = map[string]json.RawMessage{ 144 // Has valid JSON and matches schema 145 "1": json.RawMessage(`{ 146 "id": "adUnit1", 147 "ext": { 148 "appnexus": { 149 "placementId": "abc", 150 "position": "above", 151 "reserve": 0.35 152 }, 153 "rubicon": { 154 "accountId": "abc" 155 } 156 }, 157 "video":{ 158 "w":200, 159 "h":300 160 } 161 }`), 162 // Has valid JSON, matches schema but is missing video object 163 "2": json.RawMessage(`{ 164 "id": "adUnit1", 165 "ext": { 166 "appnexus": { 167 "placementId": "abc", 168 "position": "above", 169 "reserve": 0.35 170 }, 171 "rubicon": { 172 "accountId": "abc" 173 } 174 } 175 }`), 176 // Invalid JSON, is missing a coma after the rubicon's "accountId" field 177 "7": json.RawMessage(`{ 178 "id": "adUnit1", 179 "ext": { 180 "appnexus": { 181 "placementId": 12345678, 182 "position": "above", 183 "reserve": 0.35 184 }, 185 "rubicon": { 186 "accountId": 23456789 187 "siteId": 113932, 188 "zoneId": 535510 189 } 190 } 191 }`), 192 // Valid JSON. Missing video object 193 "9": json.RawMessage(`{ 194 "id": "adUnit1", 195 "ext": { 196 "appnexus": { 197 "placementId": 12345678, 198 "position": "above", 199 "reserve": 0.35 200 }, 201 "rubicon": { 202 "accountId": 23456789, 203 "siteId": 113932, 204 "zoneId": 535510 205 } 206 } 207 }`), 208 // Valid JSON. Missing video object 209 "10": json.RawMessage(`{ 210 "ext": { 211 "appnexus": { 212 "placementId": 12345678, 213 "position": "above", 214 "reserve": 0.35 215 } 216 } 217 }`), 218 // Stored Imp with Passthrough 219 "6": json.RawMessage(`{ 220 "id": "my-imp-id", 221 "ext": { 222 "prebid": { 223 "passthrough": { 224 "imp_passthrough": 30 225 } 226 } 227 } 228 }`), 229 } 230 231 // Incoming requests with stored request IDs 232 var testStoredRequests = []string{ 233 `{ 234 "id": "ThisID", 235 "imp": [ 236 { 237 "video":{ 238 "h":300, 239 "w":200 240 }, 241 "ext": { 242 "prebid": { 243 "storedrequest": { 244 "id": "1" 245 }, 246 "options": { 247 "echovideoattrs": true 248 } 249 } 250 } 251 } 252 ], 253 "ext": { 254 "prebid": { 255 "cache": { 256 "markup": 1 257 }, 258 "targeting": { 259 } 260 } 261 } 262 }`, 263 `{ 264 "id": "ThisID", 265 "imp": [ 266 { 267 "id": "adUnit2", 268 "ext": { 269 "prebid": { 270 "storedrequest": { 271 "id": "1" 272 }, 273 "options": { 274 "echovideoattrs": true 275 } 276 }, 277 "appnexus": { 278 "placementId": "def", 279 "trafficSourceCode": "mysite.com", 280 "reserve": null 281 }, 282 "rubicon": null 283 } 284 } 285 ], 286 "ext": { 287 "prebid": { 288 "cache": { 289 "markup": 1 290 }, 291 "targeting": { 292 } 293 } 294 } 295 }`, 296 `{ 297 "id": "ThisID", 298 "imp": [ 299 { 300 "ext": { 301 "prebid": { 302 "storedrequest": { 303 "id": "2" 304 }, 305 "options": { 306 "echovideoattrs": false 307 } 308 } 309 } 310 } 311 ], 312 "ext": { 313 "prebid": { 314 "storedrequest": { 315 "id": "2" 316 } 317 } 318 } 319 }`, 320 `{ 321 "id": "ThisID", 322 "imp": [ 323 { 324 "id": "some-static-imp", 325 "video":{ 326 "mimes":["video/mp4"] 327 }, 328 "ext": { 329 "appnexus": { 330 "placementId": "abc", 331 "position": "below" 332 } 333 } 334 }, 335 { 336 "ext": { 337 "prebid": { 338 "storedrequest": { 339 "id": "1" 340 } 341 } 342 } 343 } 344 ], 345 "ext": { 346 "prebid": { 347 "cache": { 348 "markup": 1 349 }, 350 "targeting": { 351 } 352 } 353 } 354 }`, 355 `{ 356 "id": "ThisID", 357 "imp": [ 358 { 359 "id": "my-imp-id", 360 "video":{ 361 "h":300, 362 "w":200 363 }, 364 "ext": { 365 "prebid": { 366 "storedrequest": { 367 "id": "6" 368 } 369 } 370 } 371 } 372 ], 373 "ext": { 374 "prebid": { 375 "storedrequest": { 376 "id": "5" 377 } 378 } 379 } 380 }`, 381 } 382 383 // The expected requests after stored request processing 384 var testFinalRequests = []string{ 385 `{ 386 "id": "ThisID", 387 "imp": [ 388 { 389 "video":{ 390 "h":300, 391 "w":200 392 }, 393 "ext":{ 394 "appnexus":{ 395 "placementId":"abc", 396 "position":"above", 397 "reserve":0.35 398 }, 399 "prebid":{ 400 "storedrequest":{ 401 "id":"1" 402 }, 403 "options":{ 404 "echovideoattrs":true 405 } 406 }, 407 "rubicon":{ 408 "accountId":"abc" 409 } 410 }, 411 "id":"adUnit1" 412 } 413 ], 414 "ext": { 415 "prebid": { 416 "cache": { 417 "markup": 1 418 }, 419 "targeting": { 420 } 421 } 422 } 423 }`, 424 `{ 425 "id": "ThisID", 426 "imp": [ 427 { 428 "video":{ 429 "w":200, 430 "h":300 431 }, 432 "ext":{ 433 "appnexus":{ 434 "placementId":"def", 435 "position":"above", 436 "trafficSourceCode":"mysite.com" 437 }, 438 "prebid":{ 439 "storedrequest":{ 440 "id":"1" 441 }, 442 "options":{ 443 "echovideoattrs":true 444 } 445 } 446 }, 447 "id":"adUnit2" 448 } 449 ], 450 "ext": { 451 "prebid": { 452 "cache": { 453 "markup": 1 454 }, 455 "targeting": { 456 } 457 } 458 } 459 }`, 460 `{ 461 "ext": { 462 "prebid": { 463 "storedrequest": { 464 "id": "2" 465 }, 466 "targeting": { 467 "pricegranularity": "low" 468 } 469 } 470 }, 471 "id": "ThisID", 472 "imp": [ 473 { 474 "ext": { 475 "appnexus": { 476 "placementId": "abc", 477 "position": "above", 478 "reserve": 0.35 479 }, 480 "prebid": { 481 "storedrequest": { 482 "id": "2" 483 }, 484 "options":{ 485 "echovideoattrs":false 486 } 487 }, 488 "rubicon": { 489 "accountId": "abc" 490 } 491 }, 492 "id": "adUnit1" 493 } 494 ], 495 "tmax": 500 496 }`, 497 `{ 498 "id": "ThisID", 499 "imp": [ 500 { 501 "id": "some-static-imp", 502 "video": { 503 "mimes": [ 504 "video/mp4" 505 ] 506 }, 507 "ext": { 508 "appnexus": { 509 "placementId": "abc", 510 "position": "below" 511 } 512 } 513 }, 514 { 515 "ext": { 516 "appnexus": { 517 "placementId": "abc", 518 "position": "above", 519 "reserve": 0.35 520 }, 521 "prebid": { 522 "storedrequest": { 523 "id": "1" 524 } 525 }, 526 "rubicon": { 527 "accountId": "abc" 528 } 529 }, 530 "id": "adUnit1", 531 "video":{ 532 "w":200, 533 "h":300 534 } 535 } 536 ], 537 "ext": { 538 "prebid": { 539 "cache": { 540 "markup": 1 541 }, 542 "targeting": { 543 } 544 } 545 } 546 }`, 547 `{ 548 "id": "ThisID", 549 "imp": [ 550 { 551 "ext":{ 552 "prebid":{ 553 "passthrough":{ 554 "imp_passthrough":30 555 }, 556 "storedrequest":{ 557 "id":"6" 558 } 559 } 560 }, 561 "id":"my-imp-id", 562 "video":{ 563 "h":300, 564 "w":200 565 } 566 } 567 ], 568 "ext":{ 569 "prebid":{ 570 "passthrough":{ 571 "root_ext_passthrough":20 572 }, 573 "storedrequest":{ 574 "id":"5" 575 } 576 } 577 } 578 }`, 579 } 580 581 var testStoredImpIds = []string{ 582 "adUnit1", "adUnit2", "adUnit1", "some-static-imp", "my-imp-id", 583 } 584 585 var testStoredImps = []string{ 586 `{ 587 "id": "adUnit1", 588 "ext": { 589 "appnexus": { 590 "placementId": "abc", 591 "position": "above", 592 "reserve": 0.35 593 }, 594 "rubicon": { 595 "accountId": "abc" 596 } 597 }, 598 "video":{ 599 "w":200, 600 "h":300 601 } 602 }`, 603 `{ 604 "id": "adUnit1", 605 "ext": { 606 "appnexus": { 607 "placementId": "abc", 608 "position": "above", 609 "reserve": 0.35 610 }, 611 "rubicon": { 612 "accountId": "abc" 613 } 614 }, 615 "video":{ 616 "w":200, 617 "h":300 618 } 619 }`, 620 `{ 621 "id": "adUnit1", 622 "ext": { 623 "appnexus": { 624 "placementId": "abc", 625 "position": "above", 626 "reserve": 0.35 627 }, 628 "rubicon": { 629 "accountId": "abc" 630 } 631 } 632 }`, 633 ``, 634 `{ 635 "id": "my-imp-id", 636 "ext": { 637 "prebid": { 638 "passthrough": { 639 "imp_passthrough": 30 640 } 641 } 642 } 643 }`, 644 } 645 646 var testBidRequests = []string{ 647 `{ 648 "id": "ThisID", 649 "app": { 650 "id": "123" 651 }, 652 "imp": [ 653 { 654 "video":{ 655 "h":300, 656 "w":200 657 }, 658 "ext": { 659 "prebid": { 660 "storedrequest": { 661 "id": "1" 662 }, 663 "options": { 664 "echovideoattrs": true 665 } 666 } 667 } 668 } 669 ], 670 "ext": { 671 "prebid": { 672 "cache": { 673 "markup": 1 674 }, 675 "targeting": { 676 } 677 } 678 } 679 }`, 680 `{ 681 "id": "ThisID", 682 "site": { 683 "page": "prebid.org" 684 }, 685 "imp": [ 686 { 687 "id": "adUnit2", 688 "ext": { 689 "prebid": { 690 "storedrequest": { 691 "id": "1" 692 }, 693 "options": { 694 "echovideoattrs": true 695 } 696 }, 697 "appnexus": { 698 "placementId": "def", 699 "trafficSourceCode": "mysite.com", 700 "reserve": null 701 }, 702 "rubicon": null 703 } 704 } 705 ], 706 "ext": { 707 "prebid": { 708 "storedrequest": { 709 "id": "1" 710 } 711 } 712 } 713 }`, 714 `{ 715 "id": "ThisID", 716 "app": { 717 "id": "123" 718 }, 719 "imp": [ 720 { 721 "ext": { 722 "prebid": { 723 "storedrequest": { 724 "id": "2" 725 }, 726 "options": { 727 "echovideoattrs": false 728 } 729 } 730 } 731 } 732 ], 733 "ext": { 734 "prebid": { 735 "storedrequest": { 736 "id": "2" 737 } 738 } 739 } 740 }`, 741 `{ 742 "id": "ThisID", 743 "site": { 744 "page": "prebid.org" 745 }, 746 "imp": [ 747 { 748 "ext": { 749 "prebid": { 750 "storedrequest": { 751 "id": "2" 752 }, 753 "options": { 754 "echovideoattrs": false 755 } 756 } 757 } 758 } 759 ], 760 "ext": { 761 "prebid": { 762 "storedrequest": { 763 "id": "2" 764 } 765 } 766 } 767 }`, 768 `{ 769 "id": "ThisID", 770 "app": { 771 "id": "123" 772 }, 773 "imp": [ 774 { 775 "ext": { 776 "prebid": { 777 "storedrequest": { 778 "id": "1" 779 }, 780 "options": { 781 "echovideoattrs": false 782 } 783 } 784 } 785 } 786 ], 787 "ext": { 788 "prebid": { 789 "storedrequest": { 790 "id": "1" 791 } 792 } 793 } 794 }`, 795 `{ 796 "id": "ThisID", 797 "imp": [{ 798 "id": "some-impression-id", 799 "banner": { 800 "format": [{ 801 "w": 600, 802 "h": 500 803 }, 804 { 805 "w": 300, 806 "h": 600 807 } 808 ] 809 }, 810 "ext": { 811 "appnexus": { 812 "placementId": 12883451 813 } 814 } 815 }], 816 "ext": { 817 "prebid": { 818 "debug": true, 819 "storedrequest": { 820 "id": "4" 821 } 822 } 823 }, 824 "site": { 825 "page": "https://example.com" 826 } 827 }`, 828 } 829 830 // --------------------------------------------------------- 831 // Some interfaces implemented with the purspose of testing 832 // --------------------------------------------------------- 833 834 // mockStoredReqFetcher implements the Fetcher interface 835 type mockStoredReqFetcher struct { 836 } 837 838 func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 839 return testStoredRequestData, testStoredImpData, nil 840 } 841 842 func (cf mockStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 843 return nil, nil 844 } 845 846 // mockExchange implements the Exchange interface 847 type mockExchange struct { 848 lastRequest *openrtb2.BidRequest 849 } 850 851 func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 852 r := auctionRequest.BidRequestWrapper 853 m.lastRequest = r.BidRequest 854 return &exchange.AuctionResponse{ 855 BidResponse: &openrtb2.BidResponse{ 856 SeatBid: []openrtb2.SeatBid{{ 857 Bid: []openrtb2.Bid{{ 858 AdM: "<script></script>", 859 }}, 860 }}, 861 }, 862 }, nil 863 } 864 865 // hardcodedResponseIPValidator implements the IPValidator interface. 866 type hardcodedResponseIPValidator struct { 867 response bool 868 } 869 870 func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { 871 return v.response 872 } 873 874 // fakeUUIDGenerator implements the UUIDGenerator interface 875 type fakeUUIDGenerator struct { 876 id string 877 err error 878 } 879 880 func (f fakeUUIDGenerator) Generate() (string, error) { 881 return f.id, f.err 882 } 883 884 // warningsCheckExchange is a well-behaved exchange which stores all incoming warnings. 885 // implements the Exchange interface 886 type warningsCheckExchange struct { 887 auctionRequest exchange.AuctionRequest 888 } 889 890 func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 891 e.auctionRequest = *r 892 return nil, nil 893 } 894 895 // nobidExchange is a well-behaved exchange which always bids "no bid". 896 // implements the Exchange interface 897 type nobidExchange struct { 898 gotRequest *openrtb2.BidRequest 899 } 900 901 func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 902 r := auctionRequest.BidRequestWrapper 903 e.gotRequest = r.BidRequest 904 905 return &exchange.AuctionResponse{ 906 BidResponse: &openrtb2.BidResponse{ 907 ID: r.BidRequest.ID, 908 BidID: "test bid id", 909 NBR: openrtb3.NoBidUnknownError.Ptr(), 910 }, 911 }, nil 912 } 913 914 // mockCurrencyRatesClient is a mock currency rate server and the rates it returns 915 // are set in the JSON test file 916 type mockCurrencyRatesClient struct { 917 data currencyInfo 918 } 919 920 type currencyInfo struct { 921 Conversions map[string]map[string]float64 `json:"conversions"` 922 DataAsOfRaw string `json:"dataAsOf"` 923 } 924 925 func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request) { 926 s.data.DataAsOfRaw = "2018-09-12" 927 928 // Marshal the response and http write it 929 currencyServerJsonResponse, err := jsonutil.Marshal(&s.data) 930 if err != nil { 931 http.Error(w, err.Error(), http.StatusInternalServerError) 932 return 933 } 934 w.Write(currencyServerJsonResponse) 935 } 936 937 // mockBidderHandler carries mock bidder server information that will be read from JSON test files 938 // and defines a handle function of a a mock bidder service. 939 type mockBidderHandler struct { 940 BidderName string `json:"bidderName"` 941 Currency string `json:"currency"` 942 Price float64 `json:"price"` 943 DealID string `json:"dealid"` 944 Seat string `json:"seat"` 945 } 946 947 func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { 948 // Read request Body 949 buf := new(bytes.Buffer) 950 buf.ReadFrom(req.Body) 951 952 // Unmarshal exit if error 953 var openrtb2Request openrtb2.BidRequest 954 if err := jsonutil.UnmarshalValid(buf.Bytes(), &openrtb2Request); err != nil { 955 http.Error(w, err.Error(), http.StatusBadRequest) 956 return 957 } 958 959 var openrtb2ImpExt map[string]json.RawMessage 960 if err := jsonutil.UnmarshalValid(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil { 961 http.Error(w, err.Error(), http.StatusBadRequest) 962 return 963 } 964 965 _, exists := openrtb2ImpExt["bidder"] 966 if !exists { 967 http.Error(w, "This request is not meant for this bidder", http.StatusBadRequest) 968 return 969 } 970 971 // Create bid service openrtb2.BidResponse with one bid according to JSON test file values 972 var serverResponseObject = openrtb2.BidResponse{ 973 ID: openrtb2Request.ID, 974 Cur: b.Currency, 975 SeatBid: []openrtb2.SeatBid{ 976 { 977 Bid: []openrtb2.Bid{ 978 { 979 ID: b.BidderName + "-bid", 980 ImpID: openrtb2Request.Imp[0].ID, 981 Price: b.Price, 982 DealID: b.DealID, 983 }, 984 }, 985 Seat: b.BidderName, 986 }, 987 }, 988 } 989 990 // Marshal the response and http write it 991 serverJsonResponse, err := jsonutil.Marshal(&serverResponseObject) 992 if err != nil { 993 http.Error(w, err.Error(), http.StatusInternalServerError) 994 return 995 } 996 w.Write(serverJsonResponse) 997 } 998 999 // mockAdapter is a mock impression-splitting adapter 1000 type mockAdapter struct { 1001 mockServerURL string 1002 Server config.Server 1003 seat string 1004 } 1005 1006 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 1007 adapter := &mockAdapter{ 1008 mockServerURL: config.Endpoint, 1009 Server: server, 1010 } 1011 return adapter, nil 1012 } 1013 1014 func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 1015 var requests []*adapters.RequestData 1016 var errors []error 1017 1018 requestCopy := *request 1019 for _, imp := range request.Imp { 1020 requestCopy.Imp = []openrtb2.Imp{imp} 1021 1022 requestJSON, err := jsonutil.Marshal(request) 1023 if err != nil { 1024 errors = append(errors, err) 1025 continue 1026 } 1027 1028 requestData := &adapters.RequestData{ 1029 Method: "POST", 1030 Uri: a.mockServerURL, 1031 Body: requestJSON, 1032 } 1033 requests = append(requests, requestData) 1034 } 1035 return requests, errors 1036 } 1037 1038 func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 1039 if responseData.StatusCode != http.StatusOK { 1040 switch responseData.StatusCode { 1041 case http.StatusNoContent: 1042 return nil, nil 1043 case http.StatusBadRequest: 1044 return nil, []error{&errortypes.BadInput{ 1045 Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", 1046 }} 1047 default: 1048 return nil, []error{&errortypes.BadServerResponse{ 1049 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), 1050 }} 1051 } 1052 } 1053 1054 var publisherResponse openrtb2.BidResponse 1055 if err := jsonutil.UnmarshalValid(responseData.Body, &publisherResponse); err != nil { 1056 return nil, []error{err} 1057 } 1058 1059 rv := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 1060 rv.Currency = publisherResponse.Cur 1061 for _, seatBid := range publisherResponse.SeatBid { 1062 for i, bid := range seatBid.Bid { 1063 for _, imp := range request.Imp { 1064 if imp.ID == bid.ImpID { 1065 b := &adapters.TypedBid{ 1066 Bid: &seatBid.Bid[i], 1067 BidType: openrtb_ext.BidTypeBanner, 1068 } 1069 if len(a.seat) > 0 { 1070 b.Seat = openrtb_ext.BidderName(a.seat) 1071 } 1072 rv.Bids = append(rv.Bids, b) 1073 } 1074 } 1075 } 1076 } 1077 return rv, nil 1078 } 1079 1080 // --------------------------------------------------------- 1081 // Auxiliary functions that don't make assertions and don't 1082 // take t *testing.T as parameter 1083 // --------------------------------------------------------- 1084 func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.BidderName) config.BidderInfos { 1085 biddersInfos := make(config.BidderInfos) 1086 for _, name := range biddersNames { 1087 isDisabled := false 1088 for _, disabledAdapter := range disabledAdapters { 1089 if string(name) == disabledAdapter { 1090 isDisabled = true 1091 break 1092 } 1093 } 1094 biddersInfos[string(name)] = newBidderInfo(isDisabled) 1095 } 1096 return biddersInfos 1097 } 1098 1099 func enableBidders(bidderInfos config.BidderInfos) { 1100 for name, bidderInfo := range bidderInfos { 1101 if bidderInfo.Disabled { 1102 bidderInfo.Disabled = false 1103 bidderInfos[name] = bidderInfo 1104 } 1105 } 1106 } 1107 1108 func disableBidders(disabledAdapters []string, bidderInfos config.BidderInfos) { 1109 for _, disabledAdapter := range disabledAdapters { 1110 if bidderInfo, ok := bidderInfos[disabledAdapter]; ok { 1111 bidderInfo.Disabled = true 1112 bidderInfos[disabledAdapter] = bidderInfo 1113 } 1114 } 1115 } 1116 1117 func newBidderInfo(isDisabled bool) config.BidderInfo { 1118 return config.BidderInfo{ 1119 Disabled: isDisabled, 1120 } 1121 } 1122 1123 func parseTestData(fileData []byte, testFile string) (testCase, error) { 1124 1125 parsedTestData := testCase{} 1126 var err, errEm error 1127 1128 // Get testCase values 1129 parsedTestData.BidRequest, _, _, err = jsonparser.Get(fileData, "mockBidRequest") 1130 if err != nil { 1131 return parsedTestData, fmt.Errorf("Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", testFile, err) 1132 } 1133 1134 // Get testCaseConfig values 1135 parsedTestData.Config = &testConfigValues{} 1136 var jsonTestConfig json.RawMessage 1137 1138 jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config") 1139 if err == nil { 1140 if err = jsonutil.UnmarshalValid(jsonTestConfig, parsedTestData.Config); err != nil { 1141 return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err) 1142 } 1143 } 1144 1145 // Get the return code we expect PBS to throw back given test's bidRequest and config 1146 parsedReturnCode, err := jsonparser.GetInt(fileData, "expectedReturnCode") 1147 if err != nil { 1148 return parsedTestData, fmt.Errorf("Error jsonparsing root.code from file %s. Desc: %v.", testFile, err) 1149 } 1150 1151 // Get both bid response and error message, if any 1152 parsedTestData.ExpectedBidResponse, _, _, err = jsonparser.Get(fileData, "expectedBidResponse") 1153 parsedTestData.ExpectedErrorMessage, errEm = jsonparser.GetString(fileData, "expectedErrorMessage") 1154 1155 if err == nil && errEm == nil { 1156 return parsedTestData, fmt.Errorf("Test case %s can't have both a valid expectedBidResponse and a valid expectedErrorMessage, fields are mutually exclusive", testFile) 1157 } else if err != nil && errEm != nil { 1158 return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile) 1159 } 1160 1161 parsedTestData.ExpectedReturnCode = int(parsedReturnCode) 1162 1163 return parsedTestData, nil 1164 } 1165 1166 func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool { 1167 var blacklistedAppMap map[string]bool 1168 1169 if len(tc.BlacklistedApps) > 0 { 1170 blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps)) 1171 for _, app := range tc.BlacklistedApps { 1172 blacklistedAppMap[app] = true 1173 } 1174 } 1175 return blacklistedAppMap 1176 } 1177 1178 // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call 1179 type exchangeTestWrapper struct { 1180 ex exchange.Exchange 1181 actualValidatedBidReq *openrtb2.BidRequest 1182 } 1183 1184 func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 1185 1186 // rebuild/resync the request in the request wrapper. 1187 if err := r.BidRequestWrapper.RebuildRequest(); err != nil { 1188 return nil, err 1189 } 1190 1191 // Save the validated bidRequest that we are about to feed HoldAuction 1192 te.actualValidatedBidReq = r.BidRequestWrapper.BidRequest 1193 1194 // Call HoldAuction() implementation as written in the exchange package 1195 return te.ex.HoldAuction(ctx, r, debugLog) 1196 } 1197 1198 // buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server 1199 func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) { 1200 if len(testCfg.MockBidders) == 0 { 1201 testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00}) 1202 } 1203 for _, mockBidder := range testCfg.MockBidders { 1204 bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid)) 1205 bidderAdapter := mockAdapter{mockServerURL: bidServer.URL, seat: mockBidder.Seat} 1206 bidderName := openrtb_ext.BidderName(mockBidder.BidderName) 1207 1208 adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") 1209 mockBidServersArray = append(mockBidServersArray, bidServer) 1210 } 1211 1212 mockCurrencyConverter := currency.NewRateConverter(mockCurrencyRatesServer.Client(), mockCurrencyRatesServer.URL, time.Second) 1213 mockCurrencyConverter.Run() 1214 1215 gdprPermsBuilder := fakePermissionsBuilder{ 1216 permissions: &fakePermissions{}, 1217 }.Builder 1218 1219 testExchange := exchange.NewExchange(adapterMap, 1220 &wellBehavedCache{}, 1221 cfg, 1222 nil, 1223 met, 1224 bidderInfos, 1225 gdprPermsBuilder, 1226 mockCurrencyConverter, 1227 mockFetcher, 1228 &adscert.NilSigner{}, 1229 macros.NewStringIndexBasedReplacer(), 1230 nil, 1231 ) 1232 1233 testExchange = &exchangeTestWrapper{ 1234 ex: testExchange, 1235 } 1236 1237 return testExchange, mockBidServersArray 1238 } 1239 1240 // buildTestEndpoint instantiates an openrtb2 Auction endpoint designed to test endpoints/openrtb2/auction.go 1241 func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, *exchangeTestWrapper, []*httptest.Server, *httptest.Server, error) { 1242 if test.Config == nil { 1243 test.Config = &testConfigValues{} 1244 } 1245 1246 var paramValidator openrtb_ext.BidderParamValidator 1247 if test.Config.RealParamsValidator { 1248 var err error 1249 paramValidator, err = openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") 1250 if err != nil { 1251 return nil, nil, nil, nil, err 1252 } 1253 } else { 1254 paramValidator = mockBidderParamValidator{} 1255 } 1256 1257 bidderInfos, _ := config.LoadBidderInfoFromDisk("../../static/bidder-info") 1258 enableBidders(bidderInfos) 1259 disableBidders(test.Config.DisabledAdapters, bidderInfos) 1260 bidderMap := exchange.GetActiveBidders(bidderInfos) 1261 disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) 1262 met := &metricsConfig.NilMetricsEngine{} 1263 mockFetcher := empty_fetcher.EmptyFetcher{} 1264 1265 // Adapter map with mock adapters needed to run JSON test cases 1266 adapterMap := make(map[openrtb_ext.BidderName]exchange.AdaptedBidder, 0) 1267 mockBidServersArray := make([]*httptest.Server, 0, 3) 1268 1269 // Mock prebid Server's currency converter, instantiate and start 1270 mockCurrencyConversionService := mockCurrencyRatesClient{ 1271 currencyInfo{ 1272 Conversions: test.Config.CurrencyRates, 1273 }, 1274 } 1275 mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle)) 1276 1277 testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) 1278 1279 var storedRequestFetcher stored_requests.Fetcher 1280 if len(test.StoredRequest) > 0 { 1281 storedRequestFetcher = &mockAmpStoredReqFetcher{test.StoredRequest} 1282 } else { 1283 storedRequestFetcher = &mockStoredReqFetcher{} 1284 } 1285 1286 var storedResponseFetcher stored_requests.Fetcher 1287 if len(test.StoredResponse) > 0 { 1288 storedResponseFetcher = &mockAmpStoredResponseFetcher{test.StoredResponse} 1289 } else { 1290 storedResponseFetcher = empty_fetcher.EmptyFetcher{} 1291 } 1292 1293 accountFetcher := &mockAccountFetcher{ 1294 data: map[string]json.RawMessage{ 1295 "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), 1296 "disabled_acct": json.RawMessage(`{"disabled":true}`), 1297 "alternate_bidder_code_acct": json.RawMessage(`{"disabled":false,"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}`), 1298 }, 1299 } 1300 1301 planBuilder := test.planBuilder 1302 if planBuilder == nil { 1303 planBuilder = hooks.EmptyPlanBuilder{} 1304 } 1305 1306 var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) 1307 1308 switch test.endpointType { 1309 case AMP_ENDPOINT: 1310 endpointBuilder = NewAmpEndpoint 1311 default: //case OPENRTB_ENDPOINT: 1312 endpointBuilder = NewEndpoint 1313 } 1314 1315 endpoint, err := endpointBuilder( 1316 fakeUUIDGenerator{}, 1317 testExchange, 1318 paramValidator, 1319 storedRequestFetcher, 1320 accountFetcher, 1321 cfg, 1322 met, 1323 analyticsBuild.New(&config.Analytics{}), 1324 disabledBidders, 1325 []byte(test.Config.AliasJSON), 1326 bidderMap, 1327 storedResponseFetcher, 1328 planBuilder, 1329 nil, 1330 ) 1331 1332 return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err 1333 } 1334 1335 type mockBidderParamValidator struct{} 1336 1337 func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error { 1338 return nil 1339 } 1340 func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" } 1341 1342 type mockAccountFetcher struct { 1343 data map[string]json.RawMessage 1344 } 1345 1346 func (af *mockAccountFetcher) FetchAccount(ctx context.Context, defaultAccountJSON json.RawMessage, accountID string) (json.RawMessage, []error) { 1347 if account, ok := af.data[accountID]; ok { 1348 return account, nil 1349 } 1350 return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} 1351 } 1352 1353 type mockAmpStoredReqFetcher struct { 1354 data map[string]json.RawMessage 1355 } 1356 1357 func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 1358 return cf.data, nil, nil 1359 } 1360 1361 func (cf *mockAmpStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 1362 return nil, nil 1363 } 1364 1365 type mockAmpStoredResponseFetcher struct { 1366 data map[string]json.RawMessage 1367 } 1368 1369 func (cf *mockAmpStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 1370 return nil, nil, nil 1371 } 1372 1373 func (cf *mockAmpStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 1374 for _, storedResponseID := range ids { 1375 if storedResponse, exists := cf.data[storedResponseID]; exists { 1376 // Found. Unescape string before returning 1377 response, err := strconv.Unquote(string(storedResponse)) 1378 if err != nil { 1379 return nil, append([]error{}, err) 1380 } 1381 cf.data[storedResponseID] = json.RawMessage(response) 1382 return cf.data, nil 1383 } 1384 } 1385 return nil, nil 1386 } 1387 1388 type wellBehavedCache struct{} 1389 1390 func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) { 1391 return "https", "www.pbcserver.com", "/pbcache/endpoint" 1392 } 1393 1394 func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) { 1395 ids := make([]string, len(values)) 1396 for i := 0; i < len(values); i++ { 1397 ids[i] = strconv.Itoa(i) 1398 } 1399 return ids, nil 1400 } 1401 1402 func readFile(t *testing.T, filename string) []byte { 1403 data, err := os.ReadFile(filename) 1404 if err != nil { 1405 t.Fatalf("Failed to read file %s: %v", filename, err) 1406 } 1407 return data 1408 } 1409 1410 type fakePermissionsBuilder struct { 1411 permissions gdpr.Permissions 1412 } 1413 1414 func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { 1415 return fpb.permissions 1416 } 1417 1418 type fakePermissions struct { 1419 } 1420 1421 func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) { 1422 return true, nil 1423 } 1424 1425 func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 1426 return true, nil 1427 } 1428 1429 func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { 1430 return gdpr.AuctionPermissions{ 1431 AllowBidRequest: true, 1432 }, nil 1433 } 1434 1435 type mockPlanBuilder struct { 1436 entrypointPlan hooks.Plan[hookstage.Entrypoint] 1437 rawAuctionPlan hooks.Plan[hookstage.RawAuctionRequest] 1438 processedAuctionPlan hooks.Plan[hookstage.ProcessedAuctionRequest] 1439 bidderRequestPlan hooks.Plan[hookstage.BidderRequest] 1440 rawBidderResponsePlan hooks.Plan[hookstage.RawBidderResponse] 1441 allProcessedBidResponsesPlan hooks.Plan[hookstage.AllProcessedBidResponses] 1442 auctionResponsePlan hooks.Plan[hookstage.AuctionResponse] 1443 } 1444 1445 func (m mockPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 1446 return m.entrypointPlan 1447 } 1448 1449 func (m mockPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 1450 return m.rawAuctionPlan 1451 } 1452 1453 func (m mockPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 1454 return m.processedAuctionPlan 1455 } 1456 1457 func (m mockPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 1458 return m.bidderRequestPlan 1459 } 1460 1461 func (m mockPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 1462 return m.rawBidderResponsePlan 1463 } 1464 1465 func (m mockPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 1466 return m.allProcessedBidResponsesPlan 1467 } 1468 1469 func (m mockPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 1470 return m.auctionResponsePlan 1471 } 1472 1473 func makePlan[H any](hook H) hooks.Plan[H] { 1474 return hooks.Plan[H]{ 1475 { 1476 Timeout: 5 * time.Millisecond, 1477 Hooks: []hooks.HookWrapper[H]{ 1478 { 1479 Module: "foobar", 1480 Code: "foo", 1481 Hook: hook, 1482 }, 1483 }, 1484 }, 1485 } 1486 } 1487 1488 type mockRejectionHook struct { 1489 nbr int 1490 err error 1491 } 1492 1493 func (m mockRejectionHook) HandleEntrypointHook( 1494 _ context.Context, 1495 _ hookstage.ModuleInvocationContext, 1496 _ hookstage.EntrypointPayload, 1497 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1498 return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, m.err 1499 } 1500 1501 func (m mockRejectionHook) HandleRawAuctionHook( 1502 _ context.Context, 1503 _ hookstage.ModuleInvocationContext, 1504 _ hookstage.RawAuctionRequestPayload, 1505 ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { 1506 return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err 1507 } 1508 1509 func (m mockRejectionHook) HandleProcessedAuctionHook( 1510 _ context.Context, 1511 _ hookstage.ModuleInvocationContext, 1512 _ hookstage.ProcessedAuctionRequestPayload, 1513 ) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { 1514 return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err 1515 } 1516 1517 func (m mockRejectionHook) HandleBidderRequestHook( 1518 _ context.Context, 1519 _ hookstage.ModuleInvocationContext, 1520 payload hookstage.BidderRequestPayload, 1521 ) (hookstage.HookResult[hookstage.BidderRequestPayload], error) { 1522 result := hookstage.HookResult[hookstage.BidderRequestPayload]{} 1523 if payload.Bidder == "appnexus" { 1524 result.Reject = true 1525 result.NbrCode = m.nbr 1526 } 1527 1528 return result, m.err 1529 } 1530 1531 func (m mockRejectionHook) HandleRawBidderResponseHook( 1532 _ context.Context, 1533 _ hookstage.ModuleInvocationContext, 1534 payload hookstage.RawBidderResponsePayload, 1535 ) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { 1536 result := hookstage.HookResult[hookstage.RawBidderResponsePayload]{} 1537 if payload.Bidder == "appnexus" { 1538 result.Reject = true 1539 result.NbrCode = m.nbr 1540 } 1541 1542 return result, nil 1543 } 1544 1545 var entryPointHookUpdateWithErrors = hooks.HookWrapper[hookstage.Entrypoint]{ 1546 Module: "foobar", 1547 Code: "foo", 1548 Hook: mockUpdateHook{ 1549 entrypointHandler: func( 1550 _ hookstage.ModuleInvocationContext, 1551 payload hookstage.EntrypointPayload, 1552 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1553 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1554 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1555 payload.Request.Header.Add("foo", "bar") 1556 return payload, nil 1557 }, hookstage.MutationUpdate, "header", "foo") 1558 1559 return hookstage.HookResult[hookstage.EntrypointPayload]{ 1560 ChangeSet: ch, 1561 Errors: []string{"error 1"}, 1562 }, nil 1563 }, 1564 }, 1565 } 1566 1567 var entryPointHookUpdateWithErrorsAndWarnings = hooks.HookWrapper[hookstage.Entrypoint]{ 1568 Module: "foobar", 1569 Code: "bar", 1570 Hook: mockUpdateHook{ 1571 entrypointHandler: func( 1572 _ hookstage.ModuleInvocationContext, 1573 payload hookstage.EntrypointPayload, 1574 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1575 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1576 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1577 params := payload.Request.URL.Query() 1578 params.Add("foo", "baz") 1579 payload.Request.URL.RawQuery = params.Encode() 1580 return payload, nil 1581 }, hookstage.MutationUpdate, "param", "foo") 1582 1583 return hookstage.HookResult[hookstage.EntrypointPayload]{ 1584 ChangeSet: ch, 1585 Errors: []string{"error 1"}, 1586 Warnings: []string{"warning 1"}, 1587 }, nil 1588 }, 1589 }, 1590 } 1591 1592 var entryPointHookUpdate = hooks.HookWrapper[hookstage.Entrypoint]{ 1593 Module: "foobar", 1594 Code: "baz", 1595 Hook: mockUpdateHook{ 1596 entrypointHandler: func( 1597 ctx hookstage.ModuleInvocationContext, 1598 payload hookstage.EntrypointPayload, 1599 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1600 result := hookstage.HookResult[hookstage.EntrypointPayload]{} 1601 if ctx.Endpoint != hookexecution.EndpointAuction { 1602 result.Warnings = []string{fmt.Sprintf("Endpoint %s is not supported by hook.", ctx.Endpoint)} 1603 return result, nil 1604 } 1605 1606 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1607 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1608 body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":600}`)) 1609 if err == nil { 1610 payload.Body = body 1611 } 1612 return payload, err 1613 }, hookstage.MutationUpdate, "body", "tmax") 1614 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1615 body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"regs": {"ext": {"gdpr": 1, "us_privacy": "1NYN"}}}`)) 1616 if err == nil { 1617 payload.Body = body 1618 } 1619 return payload, err 1620 }, hookstage.MutationAdd, "body", "regs", "ext", "us_privacy") 1621 result.ChangeSet = ch 1622 1623 return result, nil 1624 }, 1625 }, 1626 } 1627 1628 var rawAuctionHookNone = hooks.HookWrapper[hookstage.RawAuctionRequest]{ 1629 Module: "vendor.module", 1630 Code: "foobar", 1631 Hook: mockUpdateHook{}, 1632 } 1633 1634 type mockUpdateHook struct { 1635 entrypointHandler func( 1636 hookstage.ModuleInvocationContext, 1637 hookstage.EntrypointPayload, 1638 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) 1639 } 1640 1641 func (m mockUpdateHook) HandleEntrypointHook( 1642 _ context.Context, 1643 miCtx hookstage.ModuleInvocationContext, 1644 payload hookstage.EntrypointPayload, 1645 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1646 return m.entrypointHandler(miCtx, payload) 1647 } 1648 1649 func (m mockUpdateHook) HandleRawAuctionHook( 1650 _ context.Context, 1651 _ hookstage.ModuleInvocationContext, 1652 _ hookstage.RawAuctionRequestPayload, 1653 ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { 1654 return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil 1655 }