github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 20 "github.com/prebid/openrtb/v19/openrtb3" 21 "github.com/prebid/prebid-server/adapters" 22 "github.com/prebid/prebid-server/analytics" 23 analyticsConf "github.com/prebid/prebid-server/analytics/config" 24 "github.com/prebid/prebid-server/config" 25 "github.com/prebid/prebid-server/currency" 26 "github.com/prebid/prebid-server/errortypes" 27 "github.com/prebid/prebid-server/exchange" 28 "github.com/prebid/prebid-server/experiment/adscert" 29 "github.com/prebid/prebid-server/gdpr" 30 "github.com/prebid/prebid-server/hooks" 31 "github.com/prebid/prebid-server/hooks/hookexecution" 32 "github.com/prebid/prebid-server/hooks/hookstage" 33 "github.com/prebid/prebid-server/macros" 34 "github.com/prebid/prebid-server/metrics" 35 metricsConfig "github.com/prebid/prebid-server/metrics/config" 36 "github.com/prebid/prebid-server/openrtb_ext" 37 pbc "github.com/prebid/prebid-server/prebid_cache_client" 38 "github.com/prebid/prebid-server/stored_requests" 39 "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" 40 "github.com/prebid/prebid-server/util/iputil" 41 "github.com/prebid/prebid-server/util/uuidutil" 42 jsonpatch "gopkg.in/evanphx/json-patch.v4" 43 ) 44 45 // In this file we define: 46 // - Auxiliary types 47 // - Unit test interface implementations such as mocks 48 // - Other auxiliary functions that don't make assertions and don't take t *testing.T as parameter 49 // 50 // All of the above are useful for this package's unit test framework. 51 52 // ---------------------- 53 // test auxiliary types 54 // ---------------------- 55 const maxSize = 1024 * 256 56 57 const ( 58 AMP_ENDPOINT = iota 59 OPENRTB_ENDPOINT 60 VIDEO_ENDPOINT 61 ) 62 63 type testCase struct { 64 // Common 65 endpointType int 66 Description string `json:"description"` 67 Config *testConfigValues `json:"config"` 68 BidRequest json.RawMessage `json:"mockBidRequest"` 69 ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` 70 ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` 71 ExpectedErrorMessage string `json:"expectedErrorMessage"` 72 Query string `json:"query"` 73 planBuilder hooks.ExecutionPlanBuilder 74 75 // "/openrtb2/auction" endpoint JSON test info 76 ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` 77 78 // "/openrtb2/amp" endpoint JSON test info 79 StoredRequest map[string]json.RawMessage `json:"mockAmpStoredRequest"` 80 StoredResponse map[string]json.RawMessage `json:"mockAmpStoredResponse"` 81 ExpectedAmpResponse json.RawMessage `json:"expectedAmpResponse"` 82 } 83 84 type testConfigValues struct { 85 AccountRequired bool `json:"accountRequired"` 86 AliasJSON string `json:"aliases"` 87 BlacklistedAccounts []string `json:"blacklistedAccts"` 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 := json.Marshal(&s.data) 930 if err != nil { 931 http.Error(w, err.Error(), http.StatusInternalServerError) 932 return 933 } 934 w.Write(currencyServerJsonResponse) 935 return 936 } 937 938 // mockBidderHandler carries mock bidder server information that will be read from JSON test files 939 // and defines a handle function of a a mock bidder service. 940 type mockBidderHandler struct { 941 BidderName string `json:"bidderName"` 942 Currency string `json:"currency"` 943 Price float64 `json:"price"` 944 DealID string `json:"dealid"` 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 := json.Unmarshal(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 := json.Unmarshal(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 := json.Marshal(&serverResponseObject) 992 if err != nil { 993 http.Error(w, err.Error(), http.StatusInternalServerError) 994 return 995 } 996 w.Write(serverJsonResponse) 997 return 998 } 999 1000 // mockAdapter is a mock impression-splitting adapter 1001 type mockAdapter struct { 1002 mockServerURL string 1003 Server config.Server 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 := json.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 := json.Unmarshal(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 rv.Bids = append(rv.Bids, b) 1070 } 1071 } 1072 } 1073 } 1074 return rv, nil 1075 } 1076 1077 // --------------------------------------------------------- 1078 // Auxiliary functions that don't make assertions and don't 1079 // take t *testing.T as parameter 1080 // --------------------------------------------------------- 1081 func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.BidderName) config.BidderInfos { 1082 biddersInfos := make(config.BidderInfos) 1083 for _, name := range biddersNames { 1084 isDisabled := false 1085 for _, disabledAdapter := range disabledAdapters { 1086 if string(name) == disabledAdapter { 1087 isDisabled = true 1088 break 1089 } 1090 } 1091 biddersInfos[string(name)] = newBidderInfo(isDisabled) 1092 } 1093 return biddersInfos 1094 } 1095 1096 func newBidderInfo(isDisabled bool) config.BidderInfo { 1097 return config.BidderInfo{ 1098 Disabled: isDisabled, 1099 } 1100 } 1101 1102 func parseTestData(fileData []byte, testFile string) (testCase, error) { 1103 1104 parsedTestData := testCase{} 1105 var err, errEm error 1106 1107 // Get testCase values 1108 parsedTestData.BidRequest, _, _, err = jsonparser.Get(fileData, "mockBidRequest") 1109 if err != nil { 1110 return parsedTestData, fmt.Errorf("Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", testFile, err) 1111 } 1112 1113 // Get testCaseConfig values 1114 parsedTestData.Config = &testConfigValues{} 1115 var jsonTestConfig json.RawMessage 1116 1117 jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config") 1118 if err == nil { 1119 if err = json.Unmarshal(jsonTestConfig, parsedTestData.Config); err != nil { 1120 return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err) 1121 } 1122 } 1123 1124 // Get the return code we expect PBS to throw back given test's bidRequest and config 1125 parsedReturnCode, err := jsonparser.GetInt(fileData, "expectedReturnCode") 1126 if err != nil { 1127 return parsedTestData, fmt.Errorf("Error jsonparsing root.code from file %s. Desc: %v.", testFile, err) 1128 } 1129 1130 // Get both bid response and error message, if any 1131 parsedTestData.ExpectedBidResponse, _, _, err = jsonparser.Get(fileData, "expectedBidResponse") 1132 parsedTestData.ExpectedErrorMessage, errEm = jsonparser.GetString(fileData, "expectedErrorMessage") 1133 1134 if err == nil && errEm == nil { 1135 return parsedTestData, fmt.Errorf("Test case %s can't have both a valid expectedBidResponse and a valid expectedErrorMessage, fields are mutually exclusive", testFile) 1136 } else if err != nil && errEm != nil { 1137 return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile) 1138 } 1139 1140 parsedTestData.ExpectedReturnCode = int(parsedReturnCode) 1141 1142 return parsedTestData, nil 1143 } 1144 1145 func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool { 1146 var blacklistedAppMap map[string]bool 1147 1148 if len(tc.BlacklistedApps) > 0 { 1149 blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps)) 1150 for _, app := range tc.BlacklistedApps { 1151 blacklistedAppMap[app] = true 1152 } 1153 } 1154 return blacklistedAppMap 1155 } 1156 1157 func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { 1158 var blacklistedAccountMap map[string]bool 1159 1160 if len(tc.BlacklistedAccounts) > 0 { 1161 blacklistedAccountMap = make(map[string]bool, len(tc.BlacklistedAccounts)) 1162 for _, account := range tc.BlacklistedAccounts { 1163 blacklistedAccountMap[account] = true 1164 } 1165 } 1166 return blacklistedAccountMap 1167 } 1168 1169 // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call 1170 type exchangeTestWrapper struct { 1171 ex exchange.Exchange 1172 actualValidatedBidReq *openrtb2.BidRequest 1173 } 1174 1175 func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { 1176 1177 // rebuild/resync the request in the request wrapper. 1178 if err := r.BidRequestWrapper.RebuildRequest(); err != nil { 1179 return nil, err 1180 } 1181 1182 // Save the validated bidRequest that we are about to feed HoldAuction 1183 te.actualValidatedBidReq = r.BidRequestWrapper.BidRequest 1184 1185 // Call HoldAuction() implementation as written in the exchange package 1186 return te.ex.HoldAuction(ctx, r, debugLog) 1187 } 1188 1189 // buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server 1190 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) { 1191 if len(testCfg.MockBidders) == 0 { 1192 testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00}) 1193 } 1194 for _, mockBidder := range testCfg.MockBidders { 1195 bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid)) 1196 bidderAdapter := mockAdapter{mockServerURL: bidServer.URL} 1197 bidderName := openrtb_ext.BidderName(mockBidder.BidderName) 1198 1199 adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") 1200 mockBidServersArray = append(mockBidServersArray, bidServer) 1201 } 1202 1203 mockCurrencyConverter := currency.NewRateConverter(mockCurrencyRatesServer.Client(), mockCurrencyRatesServer.URL, time.Second) 1204 mockCurrencyConverter.Run() 1205 1206 gdprPermsBuilder := fakePermissionsBuilder{ 1207 permissions: &fakePermissions{}, 1208 }.Builder 1209 1210 testExchange := exchange.NewExchange(adapterMap, 1211 &wellBehavedCache{}, 1212 cfg, 1213 nil, 1214 met, 1215 bidderInfos, 1216 gdprPermsBuilder, 1217 mockCurrencyConverter, 1218 mockFetcher, 1219 &adscert.NilSigner{}, 1220 macros.NewStringIndexBasedReplacer(), 1221 ) 1222 1223 testExchange = &exchangeTestWrapper{ 1224 ex: testExchange, 1225 } 1226 1227 return testExchange, mockBidServersArray 1228 } 1229 1230 // buildTestEndpoint instantiates an openrtb2 Auction endpoint designed to test endpoints/openrtb2/auction.go 1231 func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, *exchangeTestWrapper, []*httptest.Server, *httptest.Server, error) { 1232 if test.Config == nil { 1233 test.Config = &testConfigValues{} 1234 } 1235 1236 var paramValidator openrtb_ext.BidderParamValidator 1237 if test.Config.RealParamsValidator { 1238 var err error 1239 paramValidator, err = openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") 1240 if err != nil { 1241 return nil, nil, nil, nil, err 1242 } 1243 } else { 1244 paramValidator = mockBidderParamValidator{} 1245 } 1246 1247 bidderInfos := getBidderInfos(test.Config.DisabledAdapters, openrtb_ext.CoreBidderNames()) 1248 bidderMap := exchange.GetActiveBidders(bidderInfos) 1249 disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) 1250 met := &metricsConfig.NilMetricsEngine{} 1251 mockFetcher := empty_fetcher.EmptyFetcher{} 1252 1253 // Adapter map with mock adapters needed to run JSON test cases 1254 adapterMap := make(map[openrtb_ext.BidderName]exchange.AdaptedBidder, 0) 1255 mockBidServersArray := make([]*httptest.Server, 0, 3) 1256 1257 // Mock prebid Server's currency converter, instantiate and start 1258 mockCurrencyConversionService := mockCurrencyRatesClient{ 1259 currencyInfo{ 1260 Conversions: test.Config.CurrencyRates, 1261 }, 1262 } 1263 mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle)) 1264 1265 testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) 1266 1267 var storedRequestFetcher stored_requests.Fetcher 1268 if len(test.StoredRequest) > 0 { 1269 storedRequestFetcher = &mockAmpStoredReqFetcher{test.StoredRequest} 1270 } else { 1271 storedRequestFetcher = &mockStoredReqFetcher{} 1272 } 1273 1274 var storedResponseFetcher stored_requests.Fetcher 1275 if len(test.StoredResponse) > 0 { 1276 storedResponseFetcher = &mockAmpStoredResponseFetcher{test.StoredResponse} 1277 } else { 1278 storedResponseFetcher = empty_fetcher.EmptyFetcher{} 1279 } 1280 1281 var accountFetcher stored_requests.AccountFetcher 1282 accountFetcher = &mockAccountFetcher{ 1283 data: map[string]json.RawMessage{ 1284 "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), 1285 }, 1286 } 1287 1288 planBuilder := test.planBuilder 1289 if planBuilder == nil { 1290 planBuilder = hooks.EmptyPlanBuilder{} 1291 } 1292 1293 var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) 1294 1295 switch test.endpointType { 1296 case AMP_ENDPOINT: 1297 endpointBuilder = NewAmpEndpoint 1298 default: //case OPENRTB_ENDPOINT: 1299 endpointBuilder = NewEndpoint 1300 } 1301 1302 endpoint, err := endpointBuilder( 1303 fakeUUIDGenerator{}, 1304 testExchange, 1305 paramValidator, 1306 storedRequestFetcher, 1307 accountFetcher, 1308 cfg, 1309 met, 1310 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 1311 disabledBidders, 1312 []byte(test.Config.AliasJSON), 1313 bidderMap, 1314 storedResponseFetcher, 1315 planBuilder, 1316 nil, 1317 ) 1318 1319 return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err 1320 } 1321 1322 type mockBidderParamValidator struct{} 1323 1324 func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error { 1325 return nil 1326 } 1327 func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" } 1328 1329 type mockAccountFetcher struct { 1330 data map[string]json.RawMessage 1331 } 1332 1333 func (af *mockAccountFetcher) FetchAccount(ctx context.Context, defaultAccountJSON json.RawMessage, accountID string) (json.RawMessage, []error) { 1334 if account, ok := af.data[accountID]; ok { 1335 return account, nil 1336 } 1337 return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} 1338 } 1339 1340 type mockAmpStoredReqFetcher struct { 1341 data map[string]json.RawMessage 1342 } 1343 1344 func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 1345 return cf.data, nil, nil 1346 } 1347 1348 func (cf *mockAmpStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 1349 return nil, nil 1350 } 1351 1352 type mockAmpStoredResponseFetcher struct { 1353 data map[string]json.RawMessage 1354 } 1355 1356 func (cf *mockAmpStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 1357 return nil, nil, nil 1358 } 1359 1360 func (cf *mockAmpStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 1361 for _, storedResponseID := range ids { 1362 if storedResponse, exists := cf.data[storedResponseID]; exists { 1363 // Found. Unescape string before returning 1364 response, err := strconv.Unquote(string(storedResponse)) 1365 if err != nil { 1366 return nil, append([]error{}, err) 1367 } 1368 cf.data[storedResponseID] = json.RawMessage(response) 1369 return cf.data, nil 1370 } 1371 } 1372 return nil, nil 1373 } 1374 1375 type wellBehavedCache struct{} 1376 1377 func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) { 1378 return "https", "www.pbcserver.com", "/pbcache/endpoint" 1379 } 1380 1381 func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) { 1382 ids := make([]string, len(values)) 1383 for i := 0; i < len(values); i++ { 1384 ids[i] = strconv.Itoa(i) 1385 } 1386 return ids, nil 1387 } 1388 1389 func readFile(t *testing.T, filename string) []byte { 1390 data, err := os.ReadFile(filename) 1391 if err != nil { 1392 t.Fatalf("Failed to read file %s: %v", filename, err) 1393 } 1394 return data 1395 } 1396 1397 type fakePermissionsBuilder struct { 1398 permissions gdpr.Permissions 1399 } 1400 1401 func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { 1402 return fpb.permissions 1403 } 1404 1405 type fakePermissions struct { 1406 } 1407 1408 func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) { 1409 return true, nil 1410 } 1411 1412 func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 1413 return true, nil 1414 } 1415 1416 func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { 1417 return gdpr.AuctionPermissions{ 1418 AllowBidRequest: true, 1419 }, nil 1420 } 1421 1422 type mockPlanBuilder struct { 1423 entrypointPlan hooks.Plan[hookstage.Entrypoint] 1424 rawAuctionPlan hooks.Plan[hookstage.RawAuctionRequest] 1425 processedAuctionPlan hooks.Plan[hookstage.ProcessedAuctionRequest] 1426 bidderRequestPlan hooks.Plan[hookstage.BidderRequest] 1427 rawBidderResponsePlan hooks.Plan[hookstage.RawBidderResponse] 1428 allProcessedBidResponsesPlan hooks.Plan[hookstage.AllProcessedBidResponses] 1429 auctionResponsePlan hooks.Plan[hookstage.AuctionResponse] 1430 } 1431 1432 func (m mockPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 1433 return m.entrypointPlan 1434 } 1435 1436 func (m mockPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 1437 return m.rawAuctionPlan 1438 } 1439 1440 func (m mockPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 1441 return m.processedAuctionPlan 1442 } 1443 1444 func (m mockPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 1445 return m.bidderRequestPlan 1446 } 1447 1448 func (m mockPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 1449 return m.rawBidderResponsePlan 1450 } 1451 1452 func (m mockPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 1453 return m.allProcessedBidResponsesPlan 1454 } 1455 1456 func (m mockPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 1457 return m.auctionResponsePlan 1458 } 1459 1460 func makePlan[H any](hook H) hooks.Plan[H] { 1461 return hooks.Plan[H]{ 1462 { 1463 Timeout: 5 * time.Millisecond, 1464 Hooks: []hooks.HookWrapper[H]{ 1465 { 1466 Module: "foobar", 1467 Code: "foo", 1468 Hook: hook, 1469 }, 1470 }, 1471 }, 1472 } 1473 } 1474 1475 type mockRejectionHook struct { 1476 nbr int 1477 err error 1478 } 1479 1480 func (m mockRejectionHook) HandleEntrypointHook( 1481 _ context.Context, 1482 _ hookstage.ModuleInvocationContext, 1483 _ hookstage.EntrypointPayload, 1484 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1485 return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, m.err 1486 } 1487 1488 func (m mockRejectionHook) HandleRawAuctionHook( 1489 _ context.Context, 1490 _ hookstage.ModuleInvocationContext, 1491 _ hookstage.RawAuctionRequestPayload, 1492 ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { 1493 return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err 1494 } 1495 1496 func (m mockRejectionHook) HandleProcessedAuctionHook( 1497 _ context.Context, 1498 _ hookstage.ModuleInvocationContext, 1499 _ hookstage.ProcessedAuctionRequestPayload, 1500 ) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { 1501 return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err 1502 } 1503 1504 func (m mockRejectionHook) HandleBidderRequestHook( 1505 _ context.Context, 1506 _ hookstage.ModuleInvocationContext, 1507 payload hookstage.BidderRequestPayload, 1508 ) (hookstage.HookResult[hookstage.BidderRequestPayload], error) { 1509 result := hookstage.HookResult[hookstage.BidderRequestPayload]{} 1510 if payload.Bidder == "appnexus" { 1511 result.Reject = true 1512 result.NbrCode = m.nbr 1513 } 1514 1515 return result, m.err 1516 } 1517 1518 func (m mockRejectionHook) HandleRawBidderResponseHook( 1519 _ context.Context, 1520 _ hookstage.ModuleInvocationContext, 1521 payload hookstage.RawBidderResponsePayload, 1522 ) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { 1523 result := hookstage.HookResult[hookstage.RawBidderResponsePayload]{} 1524 if payload.Bidder == "appnexus" { 1525 result.Reject = true 1526 result.NbrCode = m.nbr 1527 } 1528 1529 return result, nil 1530 } 1531 1532 var entryPointHookUpdateWithErrors = hooks.HookWrapper[hookstage.Entrypoint]{ 1533 Module: "foobar", 1534 Code: "foo", 1535 Hook: mockUpdateHook{ 1536 entrypointHandler: func( 1537 _ hookstage.ModuleInvocationContext, 1538 payload hookstage.EntrypointPayload, 1539 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1540 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1541 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1542 payload.Request.Header.Add("foo", "bar") 1543 return payload, nil 1544 }, hookstage.MutationUpdate, "header", "foo") 1545 1546 return hookstage.HookResult[hookstage.EntrypointPayload]{ 1547 ChangeSet: ch, 1548 Errors: []string{"error 1"}, 1549 }, nil 1550 }, 1551 }, 1552 } 1553 1554 var entryPointHookUpdateWithErrorsAndWarnings = hooks.HookWrapper[hookstage.Entrypoint]{ 1555 Module: "foobar", 1556 Code: "bar", 1557 Hook: mockUpdateHook{ 1558 entrypointHandler: func( 1559 _ hookstage.ModuleInvocationContext, 1560 payload hookstage.EntrypointPayload, 1561 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1562 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1563 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1564 params := payload.Request.URL.Query() 1565 params.Add("foo", "baz") 1566 payload.Request.URL.RawQuery = params.Encode() 1567 return payload, nil 1568 }, hookstage.MutationUpdate, "param", "foo") 1569 1570 return hookstage.HookResult[hookstage.EntrypointPayload]{ 1571 ChangeSet: ch, 1572 Errors: []string{"error 1"}, 1573 Warnings: []string{"warning 1"}, 1574 }, nil 1575 }, 1576 }, 1577 } 1578 1579 var entryPointHookUpdate = hooks.HookWrapper[hookstage.Entrypoint]{ 1580 Module: "foobar", 1581 Code: "baz", 1582 Hook: mockUpdateHook{ 1583 entrypointHandler: func( 1584 ctx hookstage.ModuleInvocationContext, 1585 payload hookstage.EntrypointPayload, 1586 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1587 result := hookstage.HookResult[hookstage.EntrypointPayload]{} 1588 if ctx.Endpoint != hookexecution.EndpointAuction { 1589 result.Warnings = []string{fmt.Sprintf("Endpoint %s is not supported by hook.", ctx.Endpoint)} 1590 return result, nil 1591 } 1592 1593 ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} 1594 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1595 body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":50}`)) 1596 if err == nil { 1597 payload.Body = body 1598 } 1599 return payload, err 1600 }, hookstage.MutationUpdate, "body", "tmax") 1601 ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { 1602 body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"regs": {"ext": {"gdpr": 1, "us_privacy": "1NYN"}}}`)) 1603 if err == nil { 1604 payload.Body = body 1605 } 1606 return payload, err 1607 }, hookstage.MutationAdd, "body", "regs", "ext", "us_privacy") 1608 result.ChangeSet = ch 1609 1610 return result, nil 1611 }, 1612 }, 1613 } 1614 1615 var rawAuctionHookNone = hooks.HookWrapper[hookstage.RawAuctionRequest]{ 1616 Module: "vendor.module", 1617 Code: "foobar", 1618 Hook: mockUpdateHook{}, 1619 } 1620 1621 type mockUpdateHook struct { 1622 entrypointHandler func( 1623 hookstage.ModuleInvocationContext, 1624 hookstage.EntrypointPayload, 1625 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) 1626 } 1627 1628 func (m mockUpdateHook) HandleEntrypointHook( 1629 _ context.Context, 1630 miCtx hookstage.ModuleInvocationContext, 1631 payload hookstage.EntrypointPayload, 1632 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { 1633 return m.entrypointHandler(miCtx, payload) 1634 } 1635 1636 func (m mockUpdateHook) HandleRawAuctionHook( 1637 _ context.Context, 1638 _ hookstage.ModuleInvocationContext, 1639 _ hookstage.RawAuctionRequestPayload, 1640 ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { 1641 return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil 1642 }