github.com/prebid/prebid-server@v0.275.0/hooks/hookexecution/executor_test.go (about) 1 package hookexecution 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "net/url" 8 "testing" 9 "time" 10 11 "github.com/prebid/openrtb/v19/openrtb2" 12 "github.com/prebid/prebid-server/adapters" 13 "github.com/prebid/prebid-server/config" 14 "github.com/prebid/prebid-server/exchange/entities" 15 "github.com/prebid/prebid-server/hooks" 16 "github.com/prebid/prebid-server/hooks/hookanalytics" 17 "github.com/prebid/prebid-server/hooks/hookstage" 18 "github.com/prebid/prebid-server/metrics" 19 metricsConfig "github.com/prebid/prebid-server/metrics/config" 20 "github.com/prebid/prebid-server/openrtb_ext" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/mock" 23 ) 24 25 func TestEmptyHookExecutor(t *testing.T) { 26 executor := EmptyHookExecutor{} 27 executor.SetAccount(&config.Account{}) 28 29 body := []byte(`{"foo": "bar"}`) 30 reader := bytes.NewReader(body) 31 req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) 32 assert.NoError(t, err, "Failed to create http request.") 33 34 bidderRequest := &openrtb2.BidRequest{ID: "some-id"} 35 expectedBidderRequest := &openrtb2.BidRequest{ID: "some-id"} 36 37 entrypointBody, entrypointRejectErr := executor.ExecuteEntrypointStage(req, body) 38 rawAuctionBody, rawAuctionRejectErr := executor.ExecuteRawAuctionStage(body) 39 processedAuctionRejectErr := executor.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) 40 bidderRequestRejectErr := executor.ExecuteBidderRequestStage(bidderRequest, "bidder-name") 41 executor.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) 42 43 outcomes := executor.GetOutcomes() 44 assert.Equal(t, EmptyHookExecutor{}, executor, "EmptyHookExecutor shouldn't be changed.") 45 assert.Empty(t, outcomes, "EmptyHookExecutor shouldn't return stage outcomes.") 46 47 assert.Nil(t, entrypointRejectErr, "EmptyHookExecutor shouldn't return reject error at entrypoint stage.") 48 assert.Equal(t, body, entrypointBody, "EmptyHookExecutor shouldn't change body at entrypoint stage.") 49 50 assert.Nil(t, rawAuctionRejectErr, "EmptyHookExecutor shouldn't return reject error at raw-auction stage.") 51 assert.Equal(t, body, rawAuctionBody, "EmptyHookExecutor shouldn't change body at raw-auction stage.") 52 53 assert.Nil(t, processedAuctionRejectErr, "EmptyHookExecutor shouldn't return reject error at processed-auction stage.") 54 assert.Nil(t, bidderRequestRejectErr, "EmptyHookExecutor shouldn't return reject error at bidder-request stage.") 55 assert.Equal(t, expectedBidderRequest, bidderRequest, "EmptyHookExecutor shouldn't change payload at bidder-request stage.") 56 } 57 58 func TestExecuteEntrypointStage(t *testing.T) { 59 const body string = `{"name": "John", "last_name": "Doe"}` 60 const urlString string = "https://prebid.com/openrtb2/auction" 61 62 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 63 64 testCases := []struct { 65 description string 66 givenBody string 67 givenUrl string 68 givenPlanBuilder hooks.ExecutionPlanBuilder 69 expectedBody string 70 expectedHeader http.Header 71 expectedQuery url.Values 72 expectedReject *RejectError 73 expectedModuleContexts *moduleContexts 74 expectedStageOutcomes []StageOutcome 75 }{ 76 { 77 description: "Payload not changed if hook execution plan empty", 78 givenBody: body, 79 givenUrl: urlString, 80 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 81 expectedBody: body, 82 expectedHeader: http.Header{}, 83 expectedQuery: url.Values{}, 84 expectedReject: nil, 85 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 86 expectedStageOutcomes: []StageOutcome{}, 87 }, 88 { 89 description: "Payload changed if hooks return mutations", 90 givenBody: body, 91 givenUrl: urlString, 92 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 93 expectedBody: `{"last_name": "Doe", "foo": "bar"}`, 94 expectedHeader: http.Header{"Foo": []string{"bar"}}, 95 expectedQuery: url.Values{"foo": []string{"baz"}}, 96 expectedReject: nil, 97 expectedModuleContexts: foobarModuleCtx, 98 expectedStageOutcomes: []StageOutcome{ 99 { 100 Entity: entityHttpRequest, 101 Stage: hooks.StageEntrypoint.String(), 102 Groups: []GroupOutcome{ 103 { 104 InvocationResults: []HookOutcome{ 105 { 106 AnalyticsTags: hookanalytics.Analytics{}, 107 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 108 Status: StatusSuccess, 109 Action: ActionUpdate, 110 Message: "", 111 DebugMessages: []string{fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate)}, 112 Errors: nil, 113 Warnings: nil, 114 }, 115 { 116 AnalyticsTags: hookanalytics.Analytics{}, 117 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foobaz"}, 118 Status: StatusExecutionFailure, 119 Action: ActionUpdate, 120 Message: "", 121 DebugMessages: nil, 122 Errors: nil, 123 Warnings: []string{"failed to apply hook mutation: key not found"}, 124 }, 125 { 126 AnalyticsTags: hookanalytics.Analytics{}, 127 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 128 Status: StatusSuccess, 129 Action: ActionUpdate, 130 Message: "", 131 DebugMessages: []string{fmt.Sprintf("Hook mutation successfully applied, affected key: param.foo, mutation type: %s", hookstage.MutationUpdate)}, 132 Errors: nil, 133 Warnings: nil, 134 }, 135 }, 136 }, 137 { 138 InvocationResults: []HookOutcome{ 139 { 140 AnalyticsTags: hookanalytics.Analytics{}, 141 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 142 Status: StatusSuccess, 143 Action: ActionUpdate, 144 Message: "", 145 DebugMessages: []string{ 146 fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), 147 fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), 148 }, 149 Errors: nil, 150 Warnings: nil, 151 }, 152 { 153 AnalyticsTags: hookanalytics.Analytics{}, 154 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 155 Status: StatusFailure, 156 Action: "", 157 Message: "", 158 DebugMessages: nil, 159 Errors: []string{"hook execution failed: attribute not found"}, 160 Warnings: nil, 161 }, 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 { 169 description: "Stage execution can be rejected - and later hooks rejected", 170 givenBody: body, 171 givenUrl: urlString, 172 givenPlanBuilder: TestRejectPlanBuilder{}, 173 expectedBody: body, 174 expectedHeader: http.Header{"Foo": []string{"bar"}}, 175 expectedQuery: url.Values{}, 176 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "bar"}, hooks.StageEntrypoint.String()}, 177 expectedModuleContexts: foobarModuleCtx, 178 expectedStageOutcomes: []StageOutcome{ 179 { 180 Entity: entityHttpRequest, 181 Stage: hooks.StageEntrypoint.String(), 182 Groups: []GroupOutcome{ 183 { 184 InvocationResults: []HookOutcome{ 185 { 186 AnalyticsTags: hookanalytics.Analytics{}, 187 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 188 Status: StatusSuccess, 189 Action: ActionUpdate, 190 Message: "", 191 DebugMessages: []string{ 192 fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate), 193 }, 194 Errors: nil, 195 Warnings: nil, 196 }, 197 { 198 AnalyticsTags: hookanalytics.Analytics{}, 199 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 200 Status: StatusExecutionFailure, 201 Action: "", 202 Message: "", 203 DebugMessages: nil, 204 Errors: []string{"unexpected error"}, 205 Warnings: nil, 206 }, 207 }, 208 }, 209 { 210 InvocationResults: []HookOutcome{ 211 { 212 AnalyticsTags: hookanalytics.Analytics{}, 213 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 214 Status: StatusSuccess, 215 Action: ActionReject, 216 Message: "", 217 DebugMessages: nil, 218 Errors: []string{ 219 `Module foobar (hook: bar) rejected request with code 0 at entrypoint stage`, 220 }, 221 Warnings: nil, 222 }, 223 }, 224 }, 225 }, 226 }, 227 }, 228 }, 229 { 230 description: "Request can be changed when a hook times out", 231 givenBody: body, 232 givenUrl: urlString, 233 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 234 expectedBody: `{"foo":"bar", "last_name":"Doe"}`, 235 expectedHeader: http.Header{"Foo": []string{"bar"}}, 236 expectedQuery: url.Values{}, 237 expectedReject: nil, 238 expectedModuleContexts: foobarModuleCtx, 239 expectedStageOutcomes: []StageOutcome{ 240 { 241 Entity: entityHttpRequest, 242 Stage: hooks.StageEntrypoint.String(), 243 Groups: []GroupOutcome{ 244 { 245 InvocationResults: []HookOutcome{ 246 { 247 AnalyticsTags: hookanalytics.Analytics{}, 248 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 249 Status: StatusSuccess, 250 Action: ActionUpdate, 251 Message: "", 252 DebugMessages: []string{ 253 fmt.Sprintf("Hook mutation successfully applied, affected key: header.foo, mutation type: %s", hookstage.MutationUpdate), 254 }, 255 Errors: nil, 256 Warnings: nil, 257 }, 258 { 259 AnalyticsTags: hookanalytics.Analytics{}, 260 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 261 Status: StatusTimeout, 262 Action: "", 263 Message: "", 264 DebugMessages: nil, 265 Errors: []string{"Hook execution timeout"}, 266 Warnings: nil, 267 }, 268 }, 269 }, 270 { 271 InvocationResults: []HookOutcome{ 272 { 273 AnalyticsTags: hookanalytics.Analytics{}, 274 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 275 Status: StatusSuccess, 276 Action: ActionUpdate, 277 Message: "", 278 DebugMessages: []string{ 279 fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), 280 fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), 281 }, 282 Errors: nil, 283 Warnings: nil, 284 }, 285 }, 286 }, 287 }, 288 }, 289 }, 290 }, 291 { 292 description: "Modules contexts are preserved and correct", 293 givenBody: body, 294 givenUrl: urlString, 295 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 296 expectedBody: body, 297 expectedHeader: http.Header{}, 298 expectedQuery: url.Values{}, 299 expectedReject: nil, 300 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 301 "module-1": {"entrypoint-ctx-1": "some-ctx-1", "entrypoint-ctx-3": "some-ctx-3"}, 302 "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, 303 }}, 304 expectedStageOutcomes: []StageOutcome{ 305 { 306 Entity: entityHttpRequest, 307 Stage: hooks.StageEntrypoint.String(), 308 Groups: []GroupOutcome{ 309 { 310 InvocationResults: []HookOutcome{ 311 { 312 AnalyticsTags: hookanalytics.Analytics{}, 313 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 314 Status: StatusSuccess, 315 Action: ActionNone, 316 Message: "", 317 DebugMessages: nil, 318 Errors: nil, 319 Warnings: nil, 320 }, 321 }, 322 }, 323 { 324 InvocationResults: []HookOutcome{ 325 { 326 AnalyticsTags: hookanalytics.Analytics{}, 327 HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, 328 Status: StatusSuccess, 329 Action: ActionNone, 330 Message: "", 331 DebugMessages: nil, 332 Errors: nil, 333 Warnings: nil, 334 }, 335 { 336 AnalyticsTags: hookanalytics.Analytics{}, 337 HookID: HookID{ModuleCode: "module-1", HookImplCode: "baz"}, 338 Status: StatusSuccess, 339 Action: ActionNone, 340 Message: "", 341 DebugMessages: nil, 342 Errors: nil, 343 Warnings: nil, 344 }, 345 }, 346 }, 347 }, 348 }, 349 }, 350 }, 351 } 352 353 for _, test := range testCases { 354 t.Run(test.description, func(t *testing.T) { 355 body := []byte(test.givenBody) 356 reader := bytes.NewReader(body) 357 req, err := http.NewRequest(http.MethodPost, test.givenUrl, reader) 358 assert.NoError(t, err) 359 360 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 361 newBody, reject := exec.ExecuteEntrypointStage(req, body) 362 363 assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") 364 assert.JSONEq(t, test.expectedBody, string(newBody), "Incorrect request body.") 365 assert.Equal(t, test.expectedHeader, req.Header, "Incorrect request header.") 366 assert.Equal(t, test.expectedQuery, req.URL.Query(), "Incorrect request query.") 367 assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 368 369 stageOutcomes := exec.GetOutcomes() 370 if len(test.expectedStageOutcomes) == 0 { 371 assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") 372 } else { 373 assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) 374 } 375 }) 376 } 377 } 378 379 func TestMetricsAreGatheredDuringHookExecution(t *testing.T) { 380 reader := bytes.NewReader(nil) 381 req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) 382 assert.NoError(t, err) 383 384 metricEngine := &metrics.MetricsEngineMock{} 385 builder := TestAllHookResultsBuilder{} 386 exec := NewHookExecutor(TestAllHookResultsBuilder{}, "/openrtb2/auction", metricEngine) 387 moduleName := "module.x-1" 388 moduleLabels := metrics.ModuleLabels{ 389 Module: moduleReplacer.Replace(moduleName), 390 Stage: "entrypoint", 391 } 392 rTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 } 393 plan := builder.PlanForEntrypointStage("") 394 hooksCalledDuringStage := 0 395 for _, group := range plan { 396 for range group.Hooks { 397 hooksCalledDuringStage++ 398 } 399 } 400 metricEngine.On("RecordModuleCalled", moduleLabels, mock.MatchedBy(rTime)).Times(hooksCalledDuringStage) 401 metricEngine.On("RecordModuleSuccessUpdated", moduleLabels).Once() 402 metricEngine.On("RecordModuleSuccessRejected", moduleLabels).Once() 403 metricEngine.On("RecordModuleTimeout", moduleLabels).Once() 404 metricEngine.On("RecordModuleExecutionError", moduleLabels).Twice() 405 metricEngine.On("RecordModuleFailed", moduleLabels).Once() 406 metricEngine.On("RecordModuleSuccessNooped", moduleLabels).Once() 407 408 _, _ = exec.ExecuteEntrypointStage(req, nil) 409 410 // Assert that all module metrics funcs were called with the parameters we expected 411 metricEngine.AssertExpectations(t) 412 } 413 414 func TestExecuteRawAuctionStage(t *testing.T) { 415 const body string = `{"name": "John", "last_name": "Doe"}` 416 const bodyUpdated string = `{"last_name": "Doe", "foo": "bar"}` 417 const urlString string = "https://prebid.com/openrtb2/auction" 418 419 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 420 account := &config.Account{} 421 422 testCases := []struct { 423 description string 424 givenBody string 425 givenUrl string 426 givenPlanBuilder hooks.ExecutionPlanBuilder 427 givenAccount *config.Account 428 expectedBody string 429 expectedReject *RejectError 430 expectedModuleContexts *moduleContexts 431 expectedStageOutcomes []StageOutcome 432 }{ 433 { 434 description: "Payload not changed if hook execution plan empty", 435 givenBody: body, 436 givenUrl: urlString, 437 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 438 givenAccount: account, 439 expectedBody: body, 440 expectedReject: nil, 441 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 442 expectedStageOutcomes: []StageOutcome{}, 443 }, 444 { 445 description: "Payload changed if hooks return mutations", 446 givenBody: body, 447 givenUrl: urlString, 448 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 449 givenAccount: account, 450 expectedBody: bodyUpdated, 451 expectedReject: nil, 452 expectedModuleContexts: foobarModuleCtx, 453 expectedStageOutcomes: []StageOutcome{ 454 { 455 Entity: entityAuctionRequest, 456 Stage: hooks.StageRawAuctionRequest.String(), 457 Groups: []GroupOutcome{ 458 { 459 InvocationResults: []HookOutcome{ 460 { 461 AnalyticsTags: hookanalytics.Analytics{}, 462 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 463 Status: StatusSuccess, 464 Action: ActionUpdate, 465 Message: "", 466 DebugMessages: []string{ 467 fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), 468 fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), 469 }, 470 Errors: nil, 471 Warnings: nil, 472 }, 473 { 474 AnalyticsTags: hookanalytics.Analytics{}, 475 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 476 Status: StatusExecutionFailure, 477 Action: ActionUpdate, 478 Message: "", 479 DebugMessages: nil, 480 Errors: nil, 481 Warnings: []string{"failed to apply hook mutation: key not found"}, 482 }, 483 }, 484 }, 485 { 486 InvocationResults: []HookOutcome{ 487 { 488 AnalyticsTags: hookanalytics.Analytics{}, 489 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 490 Status: StatusFailure, 491 Action: "", 492 Message: "", 493 DebugMessages: nil, 494 Errors: []string{"hook execution failed: attribute not found"}, 495 Warnings: nil, 496 }, 497 }, 498 }, 499 }, 500 }, 501 }, 502 }, 503 { 504 description: "Stage execution can be rejected - and later hooks rejected", 505 givenBody: body, 506 givenUrl: urlString, 507 givenPlanBuilder: TestRejectPlanBuilder{}, 508 givenAccount: nil, 509 expectedBody: bodyUpdated, 510 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "bar"}, hooks.StageRawAuctionRequest.String()}, 511 expectedModuleContexts: foobarModuleCtx, 512 expectedStageOutcomes: []StageOutcome{ 513 { 514 Entity: entityAuctionRequest, 515 Stage: hooks.StageRawAuctionRequest.String(), 516 Groups: []GroupOutcome{ 517 { 518 InvocationResults: []HookOutcome{ 519 { 520 AnalyticsTags: hookanalytics.Analytics{}, 521 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 522 Status: StatusSuccess, 523 Action: ActionUpdate, 524 Message: "", 525 DebugMessages: []string{ 526 fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), 527 fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), 528 }, 529 Errors: nil, 530 Warnings: nil, 531 }, 532 { 533 AnalyticsTags: hookanalytics.Analytics{}, 534 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 535 Status: StatusExecutionFailure, 536 Action: "", 537 Message: "", 538 DebugMessages: nil, 539 Errors: []string{"unexpected error"}, 540 Warnings: nil, 541 }, 542 }, 543 }, 544 { 545 InvocationResults: []HookOutcome{ 546 { 547 AnalyticsTags: hookanalytics.Analytics{}, 548 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 549 Status: StatusSuccess, 550 Action: ActionReject, 551 Message: "", 552 DebugMessages: nil, 553 Errors: []string{ 554 `Module foobar (hook: bar) rejected request with code 0 at raw_auction_request stage`, 555 }, 556 Warnings: nil, 557 }, 558 }, 559 }, 560 }, 561 }, 562 }, 563 }, 564 { 565 description: "Request can be changed when a hook times out", 566 givenBody: body, 567 givenUrl: urlString, 568 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 569 givenAccount: account, 570 expectedBody: bodyUpdated, 571 expectedReject: nil, 572 expectedModuleContexts: foobarModuleCtx, 573 expectedStageOutcomes: []StageOutcome{ 574 { 575 Entity: entityAuctionRequest, 576 Stage: hooks.StageRawAuctionRequest.String(), 577 Groups: []GroupOutcome{ 578 { 579 InvocationResults: []HookOutcome{ 580 { 581 AnalyticsTags: hookanalytics.Analytics{}, 582 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 583 Status: StatusSuccess, 584 Action: ActionUpdate, 585 Message: "", 586 DebugMessages: []string{ 587 fmt.Sprintf("Hook mutation successfully applied, affected key: body.foo, mutation type: %s", hookstage.MutationUpdate), 588 fmt.Sprintf("Hook mutation successfully applied, affected key: body.name, mutation type: %s", hookstage.MutationDelete), 589 }, 590 Errors: nil, 591 Warnings: nil, 592 }, 593 }, 594 }, 595 { 596 InvocationResults: []HookOutcome{ 597 { 598 AnalyticsTags: hookanalytics.Analytics{}, 599 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 600 Status: StatusTimeout, 601 Action: "", 602 Message: "", 603 DebugMessages: nil, 604 Errors: []string{"Hook execution timeout"}, 605 Warnings: nil, 606 }, 607 }, 608 }, 609 }, 610 }, 611 }, 612 }, 613 { 614 description: "Modules contexts are preserved and correct", 615 givenBody: body, 616 givenUrl: urlString, 617 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 618 givenAccount: account, 619 expectedBody: body, 620 expectedReject: nil, 621 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 622 "module-1": {"raw-auction-ctx-1": "some-ctx-1", "raw-auction-ctx-3": "some-ctx-3"}, 623 "module-2": {"raw-auction-ctx-2": "some-ctx-2"}, 624 }}, 625 expectedStageOutcomes: []StageOutcome{ 626 { 627 Entity: entityAuctionRequest, 628 Stage: hooks.StageRawAuctionRequest.String(), 629 Groups: []GroupOutcome{ 630 { 631 InvocationResults: []HookOutcome{ 632 { 633 AnalyticsTags: hookanalytics.Analytics{}, 634 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 635 Status: StatusSuccess, 636 Action: ActionNone, 637 Message: "", 638 DebugMessages: nil, 639 Errors: nil, 640 Warnings: nil, 641 }, 642 { 643 AnalyticsTags: hookanalytics.Analytics{}, 644 HookID: HookID{ModuleCode: "module-2", HookImplCode: "baz"}, 645 Status: StatusSuccess, 646 Action: ActionNone, 647 Message: "", 648 DebugMessages: nil, 649 Errors: nil, 650 Warnings: nil, 651 }, 652 }, 653 }, 654 { 655 InvocationResults: []HookOutcome{ 656 { 657 AnalyticsTags: hookanalytics.Analytics{}, 658 HookID: HookID{ModuleCode: "module-1", HookImplCode: "bar"}, 659 Status: StatusSuccess, 660 Action: ActionNone, 661 Message: "", 662 DebugMessages: nil, 663 Errors: nil, 664 Warnings: nil, 665 }, 666 }, 667 }, 668 }, 669 }, 670 }, 671 }, 672 } 673 674 for _, test := range testCases { 675 t.Run(test.description, func(t *testing.T) { 676 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 677 exec.SetAccount(test.givenAccount) 678 679 newBody, reject := exec.ExecuteRawAuctionStage([]byte(test.givenBody)) 680 681 assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") 682 assert.JSONEq(t, test.expectedBody, string(newBody), "Incorrect request body.") 683 assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 684 685 stageOutcomes := exec.GetOutcomes() 686 if len(test.expectedStageOutcomes) == 0 { 687 assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") 688 } else { 689 assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) 690 } 691 }) 692 } 693 } 694 695 func TestExecuteProcessedAuctionStage(t *testing.T) { 696 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 697 account := &config.Account{} 698 req := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} 699 reqUpdated := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id", Yob: 2000, Consent: "true"}} 700 701 testCases := []struct { 702 description string 703 givenPlanBuilder hooks.ExecutionPlanBuilder 704 givenAccount *config.Account 705 givenRequest openrtb_ext.RequestWrapper 706 expectedRequest openrtb2.BidRequest 707 expectedErr error 708 expectedModuleContexts *moduleContexts 709 expectedStageOutcomes []StageOutcome 710 }{ 711 { 712 description: "Request not changed if hook execution plan empty", 713 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 714 givenAccount: account, 715 givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, 716 expectedRequest: req, 717 expectedErr: nil, 718 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 719 expectedStageOutcomes: []StageOutcome{}, 720 }, 721 { 722 description: "Request changed if hooks return mutations", 723 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 724 givenAccount: account, 725 givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, 726 expectedRequest: reqUpdated, 727 expectedErr: nil, 728 expectedModuleContexts: foobarModuleCtx, 729 expectedStageOutcomes: []StageOutcome{ 730 { 731 Entity: entityAuctionRequest, 732 Stage: hooks.StageProcessedAuctionRequest.String(), 733 Groups: []GroupOutcome{ 734 { 735 InvocationResults: []HookOutcome{ 736 { 737 AnalyticsTags: hookanalytics.Analytics{}, 738 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 739 Status: StatusSuccess, 740 Action: ActionUpdate, 741 Message: "", 742 DebugMessages: []string{ 743 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), 744 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), 745 }, 746 Errors: nil, 747 Warnings: nil, 748 }, 749 }, 750 }, 751 }, 752 }, 753 }, 754 }, 755 { 756 description: "Stage execution can be rejected - and later hooks rejected", 757 givenPlanBuilder: TestRejectPlanBuilder{}, 758 givenAccount: nil, 759 givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, 760 expectedRequest: req, 761 expectedErr: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageProcessedAuctionRequest.String()}, 762 expectedModuleContexts: foobarModuleCtx, 763 expectedStageOutcomes: []StageOutcome{ 764 { 765 Entity: entityAuctionRequest, 766 Stage: hooks.StageProcessedAuctionRequest.String(), 767 Groups: []GroupOutcome{ 768 { 769 InvocationResults: []HookOutcome{ 770 { 771 AnalyticsTags: hookanalytics.Analytics{}, 772 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 773 Status: StatusSuccess, 774 Action: ActionReject, 775 Message: "", 776 DebugMessages: nil, 777 Errors: []string{ 778 `Module foobar (hook: foo) rejected request with code 0 at processed_auction_request stage`, 779 }, 780 Warnings: nil, 781 }, 782 }, 783 }, 784 }, 785 }, 786 }, 787 }, 788 { 789 description: "Request can be changed when a hook times out", 790 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 791 givenAccount: account, 792 givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, 793 expectedRequest: reqUpdated, 794 expectedErr: nil, 795 expectedModuleContexts: foobarModuleCtx, 796 expectedStageOutcomes: []StageOutcome{ 797 { 798 Entity: entityAuctionRequest, 799 Stage: hooks.StageProcessedAuctionRequest.String(), 800 Groups: []GroupOutcome{ 801 { 802 InvocationResults: []HookOutcome{ 803 { 804 AnalyticsTags: hookanalytics.Analytics{}, 805 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 806 Status: StatusTimeout, 807 Action: "", 808 Message: "", 809 DebugMessages: nil, 810 Errors: []string{"Hook execution timeout"}, 811 Warnings: nil, 812 }, 813 }, 814 }, 815 { 816 InvocationResults: []HookOutcome{ 817 { 818 AnalyticsTags: hookanalytics.Analytics{}, 819 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 820 Status: StatusSuccess, 821 Action: ActionUpdate, 822 Message: "", 823 DebugMessages: []string{ 824 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), 825 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), 826 }, 827 Errors: nil, 828 Warnings: nil, 829 }, 830 }, 831 }, 832 }, 833 }, 834 }, 835 }, 836 { 837 description: "Modules contexts are preserved and correct", 838 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 839 givenAccount: account, 840 givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, 841 expectedRequest: req, 842 expectedErr: nil, 843 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 844 "module-1": {"processed-auction-ctx-1": "some-ctx-1", "processed-auction-ctx-3": "some-ctx-3"}, 845 "module-2": {"processed-auction-ctx-2": "some-ctx-2"}, 846 }}, 847 expectedStageOutcomes: []StageOutcome{ 848 { 849 Entity: entityAuctionRequest, 850 Stage: hooks.StageProcessedAuctionRequest.String(), 851 Groups: []GroupOutcome{ 852 { 853 InvocationResults: []HookOutcome{ 854 { 855 AnalyticsTags: hookanalytics.Analytics{}, 856 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 857 Status: StatusSuccess, 858 Action: ActionNone, 859 Message: "", 860 DebugMessages: nil, 861 Errors: nil, 862 Warnings: nil, 863 }, 864 }, 865 }, 866 { 867 InvocationResults: []HookOutcome{ 868 { 869 AnalyticsTags: hookanalytics.Analytics{}, 870 HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, 871 Status: StatusSuccess, 872 Action: ActionNone, 873 Message: "", 874 DebugMessages: nil, 875 Errors: nil, 876 Warnings: nil, 877 }, 878 { 879 AnalyticsTags: hookanalytics.Analytics{}, 880 HookID: HookID{ModuleCode: "module-1", HookImplCode: "baz"}, 881 Status: StatusSuccess, 882 Action: ActionNone, 883 Message: "", 884 DebugMessages: nil, 885 Errors: nil, 886 Warnings: nil, 887 }, 888 }, 889 }, 890 }, 891 }, 892 }, 893 }, 894 } 895 896 for _, test := range testCases { 897 t.Run(test.description, func(ti *testing.T) { 898 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 899 exec.SetAccount(test.givenAccount) 900 901 err := exec.ExecuteProcessedAuctionStage(&test.givenRequest) 902 903 assert.Equal(ti, test.expectedErr, err, "Unexpected stage reject.") 904 assert.Equal(ti, test.expectedRequest, *test.givenRequest.BidRequest, "Incorrect request update.") 905 assert.Equal(ti, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 906 907 stageOutcomes := exec.GetOutcomes() 908 if len(test.expectedStageOutcomes) == 0 { 909 assert.Empty(ti, stageOutcomes, "Incorrect stage outcomes.") 910 } else { 911 assertEqualStageOutcomes(ti, test.expectedStageOutcomes[0], stageOutcomes[0]) 912 } 913 }) 914 } 915 } 916 917 func TestExecuteBidderRequestStage(t *testing.T) { 918 bidderName := "the-bidder" 919 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 920 account := &config.Account{} 921 922 expectedBidderRequest := &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} 923 expectedUpdatedBidderRequest := &openrtb2.BidRequest{ 924 ID: "some-id", 925 User: &openrtb2.User{ 926 ID: "user-id", 927 Yob: 2000, 928 Consent: "true", 929 }, 930 } 931 932 testCases := []struct { 933 description string 934 givenBidderRequest *openrtb2.BidRequest 935 givenPlanBuilder hooks.ExecutionPlanBuilder 936 givenAccount *config.Account 937 expectedBidderRequest *openrtb2.BidRequest 938 expectedReject *RejectError 939 expectedModuleContexts *moduleContexts 940 expectedStageOutcomes []StageOutcome 941 }{ 942 { 943 description: "Payload not changed if hook execution plan empty", 944 givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, 945 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 946 givenAccount: account, 947 expectedBidderRequest: expectedBidderRequest, 948 expectedReject: nil, 949 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 950 expectedStageOutcomes: []StageOutcome{}, 951 }, 952 { 953 description: "Payload changed if hooks return mutations", 954 givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, 955 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 956 givenAccount: account, 957 expectedBidderRequest: expectedUpdatedBidderRequest, 958 expectedReject: nil, 959 expectedModuleContexts: foobarModuleCtx, 960 expectedStageOutcomes: []StageOutcome{ 961 { 962 Entity: entity(bidderName), 963 Stage: hooks.StageBidderRequest.String(), 964 Groups: []GroupOutcome{ 965 { 966 InvocationResults: []HookOutcome{ 967 { 968 AnalyticsTags: hookanalytics.Analytics{}, 969 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 970 Status: StatusSuccess, 971 Action: ActionUpdate, 972 Message: "", 973 DebugMessages: []string{ 974 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), 975 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), 976 }, 977 Errors: nil, 978 Warnings: nil, 979 }, 980 { 981 AnalyticsTags: hookanalytics.Analytics{}, 982 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 983 Status: StatusExecutionFailure, 984 Action: ActionUpdate, 985 Message: "", 986 DebugMessages: nil, 987 Errors: nil, 988 Warnings: []string{"failed to apply hook mutation: key not found"}, 989 }, 990 }, 991 }, 992 { 993 InvocationResults: []HookOutcome{ 994 { 995 AnalyticsTags: hookanalytics.Analytics{}, 996 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 997 Status: StatusFailure, 998 Action: "", 999 Message: "", 1000 DebugMessages: nil, 1001 Errors: []string{"hook execution failed: attribute not found"}, 1002 Warnings: nil, 1003 }, 1004 }, 1005 }, 1006 }, 1007 }, 1008 }, 1009 }, 1010 { 1011 description: "Stage execution can be rejected - and later hooks rejected", 1012 givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, 1013 givenPlanBuilder: TestRejectPlanBuilder{}, 1014 givenAccount: nil, 1015 expectedBidderRequest: expectedBidderRequest, 1016 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageBidderRequest.String()}, 1017 expectedModuleContexts: foobarModuleCtx, 1018 expectedStageOutcomes: []StageOutcome{ 1019 { 1020 ExecutionTime: ExecutionTime{}, 1021 Entity: entity(bidderName), 1022 Stage: hooks.StageBidderRequest.String(), 1023 Groups: []GroupOutcome{ 1024 { 1025 ExecutionTime: ExecutionTime{}, 1026 InvocationResults: []HookOutcome{ 1027 { 1028 ExecutionTime: ExecutionTime{}, 1029 AnalyticsTags: hookanalytics.Analytics{}, 1030 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 1031 Status: StatusExecutionFailure, 1032 Action: "", 1033 Message: "", 1034 DebugMessages: nil, 1035 Errors: []string{"unexpected error"}, 1036 Warnings: nil, 1037 }, 1038 }, 1039 }, 1040 { 1041 ExecutionTime: ExecutionTime{}, 1042 InvocationResults: []HookOutcome{ 1043 { 1044 ExecutionTime: ExecutionTime{}, 1045 AnalyticsTags: hookanalytics.Analytics{}, 1046 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1047 Status: StatusSuccess, 1048 Action: ActionReject, 1049 Message: "", 1050 DebugMessages: nil, 1051 Errors: []string{ 1052 `Module foobar (hook: foo) rejected request with code 0 at bidder_request stage`, 1053 }, 1054 Warnings: nil, 1055 }, 1056 }, 1057 }, 1058 }, 1059 }, 1060 }, 1061 }, 1062 { 1063 description: "Stage execution can be timed out", 1064 givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, 1065 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 1066 givenAccount: account, 1067 expectedBidderRequest: expectedUpdatedBidderRequest, 1068 expectedReject: nil, 1069 expectedModuleContexts: foobarModuleCtx, 1070 expectedStageOutcomes: []StageOutcome{ 1071 { 1072 ExecutionTime: ExecutionTime{}, 1073 Entity: entity(bidderName), 1074 Stage: hooks.StageBidderRequest.String(), 1075 Groups: []GroupOutcome{ 1076 { 1077 ExecutionTime: ExecutionTime{}, 1078 InvocationResults: []HookOutcome{ 1079 { 1080 ExecutionTime: ExecutionTime{}, 1081 AnalyticsTags: hookanalytics.Analytics{}, 1082 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1083 Status: StatusTimeout, 1084 Action: "", 1085 Message: "", 1086 DebugMessages: nil, 1087 Errors: []string{"Hook execution timeout"}, 1088 Warnings: nil, 1089 }, 1090 }, 1091 }, 1092 { 1093 ExecutionTime: ExecutionTime{}, 1094 InvocationResults: []HookOutcome{ 1095 { 1096 AnalyticsTags: hookanalytics.Analytics{}, 1097 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1098 Status: StatusSuccess, 1099 Action: ActionUpdate, 1100 Message: "", 1101 DebugMessages: []string{ 1102 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.yob, mutation type: %s", hookstage.MutationUpdate), 1103 fmt.Sprintf("Hook mutation successfully applied, affected key: bidRequest.user.consent, mutation type: %s", hookstage.MutationUpdate), 1104 }, 1105 Errors: nil, 1106 Warnings: nil, 1107 }, 1108 }, 1109 }, 1110 }, 1111 }, 1112 }, 1113 }, 1114 { 1115 description: "Modules contexts are preserved and correct", 1116 givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, 1117 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 1118 givenAccount: account, 1119 expectedBidderRequest: expectedBidderRequest, 1120 expectedReject: nil, 1121 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1122 "module-1": {"bidder-request-ctx-1": "some-ctx-1"}, 1123 "module-2": {"bidder-request-ctx-2": "some-ctx-2"}, 1124 }}, 1125 expectedStageOutcomes: []StageOutcome{ 1126 { 1127 ExecutionTime: ExecutionTime{}, 1128 Entity: entity(bidderName), 1129 Stage: hooks.StageBidderRequest.String(), 1130 Groups: []GroupOutcome{ 1131 { 1132 ExecutionTime: ExecutionTime{}, 1133 InvocationResults: []HookOutcome{ 1134 { 1135 ExecutionTime: ExecutionTime{}, 1136 AnalyticsTags: hookanalytics.Analytics{}, 1137 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 1138 Status: StatusSuccess, 1139 Action: ActionNone, 1140 Message: "", 1141 DebugMessages: nil, 1142 Errors: nil, 1143 Warnings: nil, 1144 }, 1145 }, 1146 }, 1147 { 1148 ExecutionTime: ExecutionTime{}, 1149 InvocationResults: []HookOutcome{ 1150 { 1151 ExecutionTime: ExecutionTime{}, 1152 AnalyticsTags: hookanalytics.Analytics{}, 1153 HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, 1154 Status: StatusSuccess, 1155 Action: ActionNone, 1156 Message: "", 1157 DebugMessages: nil, 1158 Errors: nil, 1159 Warnings: nil, 1160 }, 1161 }, 1162 }, 1163 }, 1164 }, 1165 }, 1166 }, 1167 } 1168 1169 for _, test := range testCases { 1170 t.Run(test.description, func(t *testing.T) { 1171 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 1172 exec.SetAccount(test.givenAccount) 1173 1174 reject := exec.ExecuteBidderRequestStage(test.givenBidderRequest, bidderName) 1175 1176 assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") 1177 assert.Equal(t, test.expectedBidderRequest, test.givenBidderRequest, "Incorrect bidder request.") 1178 assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 1179 1180 stageOutcomes := exec.GetOutcomes() 1181 if len(test.expectedStageOutcomes) == 0 { 1182 assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") 1183 } else { 1184 assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) 1185 } 1186 }) 1187 } 1188 } 1189 1190 func TestExecuteRawBidderResponseStage(t *testing.T) { 1191 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 1192 account := &config.Account{} 1193 resp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 1}}} 1194 expResp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 10}}} 1195 vEntity := entity("the-bidder") 1196 1197 testCases := []struct { 1198 description string 1199 givenPlanBuilder hooks.ExecutionPlanBuilder 1200 givenAccount *config.Account 1201 givenBidderResponse adapters.BidderResponse 1202 expectedBidderResponse adapters.BidderResponse 1203 expectedReject *RejectError 1204 expectedModuleContexts *moduleContexts 1205 expectedStageOutcomes []StageOutcome 1206 }{ 1207 { 1208 description: "Payload not changed if hook execution plan empty", 1209 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 1210 givenAccount: account, 1211 givenBidderResponse: resp, 1212 expectedBidderResponse: resp, 1213 expectedReject: nil, 1214 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 1215 expectedStageOutcomes: []StageOutcome{}, 1216 }, 1217 { 1218 description: "Payload changed if hooks return mutations", 1219 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 1220 givenAccount: account, 1221 givenBidderResponse: resp, 1222 expectedBidderResponse: expResp, 1223 expectedReject: nil, 1224 expectedModuleContexts: foobarModuleCtx, 1225 expectedStageOutcomes: []StageOutcome{ 1226 { 1227 Entity: vEntity, 1228 Stage: hooks.StageRawBidderResponse.String(), 1229 Groups: []GroupOutcome{ 1230 { 1231 InvocationResults: []HookOutcome{ 1232 { 1233 AnalyticsTags: hookanalytics.Analytics{}, 1234 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1235 Status: StatusSuccess, 1236 Action: ActionUpdate, 1237 Message: "", 1238 DebugMessages: []string{ 1239 fmt.Sprintf("Hook mutation successfully applied, affected key: bidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate), 1240 }, 1241 Errors: nil, 1242 Warnings: nil, 1243 }, 1244 }, 1245 }, 1246 }, 1247 }, 1248 }, 1249 }, 1250 { 1251 description: "Stage execution can be rejected", 1252 givenPlanBuilder: TestRejectPlanBuilder{}, 1253 givenAccount: nil, 1254 givenBidderResponse: resp, 1255 expectedBidderResponse: resp, 1256 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageRawBidderResponse.String()}, 1257 expectedModuleContexts: foobarModuleCtx, 1258 expectedStageOutcomes: []StageOutcome{ 1259 { 1260 Entity: vEntity, 1261 Stage: hooks.StageRawBidderResponse.String(), 1262 Groups: []GroupOutcome{ 1263 { 1264 InvocationResults: []HookOutcome{ 1265 { 1266 AnalyticsTags: hookanalytics.Analytics{}, 1267 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1268 Status: StatusSuccess, 1269 Action: ActionReject, 1270 Message: "", 1271 DebugMessages: nil, 1272 Errors: []string{ 1273 `Module foobar (hook: foo) rejected request with code 0 at raw_bidder_response stage`, 1274 }, 1275 Warnings: nil, 1276 }, 1277 }, 1278 }, 1279 }, 1280 }, 1281 }, 1282 }, 1283 { 1284 description: "Response can be changed when a hook times out", 1285 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 1286 givenAccount: account, 1287 givenBidderResponse: resp, 1288 expectedBidderResponse: expResp, 1289 expectedReject: nil, 1290 expectedModuleContexts: foobarModuleCtx, 1291 expectedStageOutcomes: []StageOutcome{ 1292 { 1293 Entity: vEntity, 1294 Stage: hooks.StageRawBidderResponse.String(), 1295 Groups: []GroupOutcome{ 1296 { 1297 InvocationResults: []HookOutcome{ 1298 { 1299 AnalyticsTags: hookanalytics.Analytics{}, 1300 HookID: HookID{"foobar", "foo"}, 1301 Status: StatusTimeout, 1302 Action: "", 1303 Message: "", 1304 DebugMessages: nil, 1305 Errors: []string{"Hook execution timeout"}, 1306 Warnings: nil, 1307 }, 1308 }, 1309 }, 1310 { 1311 InvocationResults: []HookOutcome{ 1312 { 1313 AnalyticsTags: hookanalytics.Analytics{}, 1314 HookID: HookID{"foobar", "bar"}, 1315 Status: StatusSuccess, 1316 Action: ActionUpdate, 1317 Message: "", 1318 DebugMessages: []string{ 1319 fmt.Sprintf("Hook mutation successfully applied, affected key: bidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate), 1320 }, 1321 Errors: nil, 1322 Warnings: nil, 1323 }, 1324 }, 1325 }, 1326 }, 1327 }, 1328 }, 1329 }, 1330 { 1331 description: "Modules contexts are preserved and correct", 1332 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 1333 givenAccount: account, 1334 givenBidderResponse: resp, 1335 expectedBidderResponse: expResp, 1336 expectedReject: nil, 1337 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1338 "module-1": {"raw-bidder-response-ctx-1": "some-ctx-1", "raw-bidder-response-ctx-3": "some-ctx-3"}, 1339 "module-2": {"raw-bidder-response-ctx-2": "some-ctx-2"}, 1340 }}, 1341 expectedStageOutcomes: []StageOutcome{ 1342 { 1343 Entity: vEntity, 1344 Stage: hooks.StageRawBidderResponse.String(), 1345 Groups: []GroupOutcome{ 1346 { 1347 InvocationResults: []HookOutcome{ 1348 { 1349 AnalyticsTags: hookanalytics.Analytics{}, 1350 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 1351 Status: StatusSuccess, 1352 Action: ActionNone, 1353 Message: "", 1354 DebugMessages: nil, 1355 Errors: nil, 1356 Warnings: nil, 1357 }, 1358 { 1359 AnalyticsTags: hookanalytics.Analytics{}, 1360 HookID: HookID{ModuleCode: "module-2", HookImplCode: "baz"}, 1361 Status: StatusSuccess, 1362 Action: ActionNone, 1363 Message: "", 1364 DebugMessages: nil, 1365 Errors: nil, 1366 Warnings: nil, 1367 }, 1368 }, 1369 }, 1370 { 1371 InvocationResults: []HookOutcome{ 1372 { 1373 AnalyticsTags: hookanalytics.Analytics{}, 1374 HookID: HookID{ModuleCode: "module-1", HookImplCode: "bar"}, 1375 Status: StatusSuccess, 1376 Action: ActionNone, 1377 Message: "", 1378 DebugMessages: nil, 1379 Errors: nil, 1380 Warnings: nil, 1381 }, 1382 }, 1383 }, 1384 }, 1385 }, 1386 }, 1387 }, 1388 } 1389 1390 for _, test := range testCases { 1391 t.Run(test.description, func(ti *testing.T) { 1392 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 1393 exec.SetAccount(test.givenAccount) 1394 1395 reject := exec.ExecuteRawBidderResponseStage(&test.givenBidderResponse, "the-bidder") 1396 1397 assert.Equal(ti, test.expectedReject, reject, "Unexpected stage reject.") 1398 assert.Equal(ti, test.expectedBidderResponse, test.givenBidderResponse, "Incorrect response update.") 1399 assert.Equal(ti, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 1400 1401 stageOutcomes := exec.GetOutcomes() 1402 if len(test.expectedStageOutcomes) == 0 { 1403 assert.Empty(ti, stageOutcomes, "Incorrect stage outcomes.") 1404 } else { 1405 assertEqualStageOutcomes(ti, test.expectedStageOutcomes[0], stageOutcomes[0]) 1406 } 1407 }) 1408 } 1409 } 1410 1411 func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { 1412 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 1413 account := &config.Account{} 1414 1415 expectedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1416 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1417 } 1418 expectedUpdatedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1419 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 10}}}, 1420 } 1421 1422 testCases := []struct { 1423 description string 1424 givenBiddersResponse map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid 1425 givenPlanBuilder hooks.ExecutionPlanBuilder 1426 givenAccount *config.Account 1427 expectedBiddersResponse map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid 1428 expectedReject *RejectError 1429 expectedModuleContexts *moduleContexts 1430 expectedStageOutcomes []StageOutcome 1431 }{ 1432 { 1433 description: "Payload not changed if hook execution plan empty", 1434 givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1435 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1436 }, 1437 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 1438 givenAccount: account, 1439 expectedBiddersResponse: expectedAllProcBidResponses, 1440 expectedReject: nil, 1441 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 1442 expectedStageOutcomes: []StageOutcome{}, 1443 }, 1444 { 1445 description: "Payload changed if hooks return mutations", 1446 givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1447 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1448 }, 1449 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 1450 givenAccount: account, 1451 expectedBiddersResponse: expectedUpdatedAllProcBidResponses, 1452 expectedReject: nil, 1453 expectedModuleContexts: foobarModuleCtx, 1454 expectedStageOutcomes: []StageOutcome{ 1455 { 1456 Entity: entityAllProcessedBidResponses, 1457 Stage: hooks.StageAllProcessedBidResponses.String(), 1458 Groups: []GroupOutcome{ 1459 { 1460 InvocationResults: []HookOutcome{ 1461 { 1462 AnalyticsTags: hookanalytics.Analytics{}, 1463 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1464 Status: StatusSuccess, 1465 Action: ActionUpdate, 1466 Message: "", 1467 DebugMessages: []string{ 1468 fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate), 1469 }, 1470 Errors: nil, 1471 Warnings: nil, 1472 }, 1473 { 1474 AnalyticsTags: hookanalytics.Analytics{}, 1475 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1476 Status: StatusExecutionFailure, 1477 Action: ActionUpdate, 1478 Message: "", 1479 DebugMessages: nil, 1480 Errors: nil, 1481 Warnings: []string{"failed to apply hook mutation: key not found"}, 1482 }, 1483 }, 1484 }, 1485 { 1486 InvocationResults: []HookOutcome{ 1487 { 1488 AnalyticsTags: hookanalytics.Analytics{}, 1489 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 1490 Status: StatusFailure, 1491 Action: "", 1492 Message: "", 1493 DebugMessages: nil, 1494 Errors: []string{"hook execution failed: attribute not found"}, 1495 Warnings: nil, 1496 }, 1497 }, 1498 }, 1499 }, 1500 }, 1501 }, 1502 }, 1503 { 1504 description: "Stage execution can't be rejected - stage doesn't support rejection", 1505 givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1506 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1507 }, 1508 givenPlanBuilder: TestRejectPlanBuilder{}, 1509 givenAccount: nil, 1510 expectedBiddersResponse: expectedUpdatedAllProcBidResponses, 1511 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageAllProcessedBidResponses.String()}, 1512 expectedModuleContexts: foobarModuleCtx, 1513 expectedStageOutcomes: []StageOutcome{ 1514 { 1515 Entity: entityAllProcessedBidResponses, 1516 Stage: hooks.StageAllProcessedBidResponses.String(), 1517 Groups: []GroupOutcome{ 1518 { 1519 InvocationResults: []HookOutcome{ 1520 { 1521 AnalyticsTags: hookanalytics.Analytics{}, 1522 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 1523 Status: StatusExecutionFailure, 1524 Action: "", 1525 Message: "", 1526 DebugMessages: nil, 1527 Errors: []string{"unexpected error"}, 1528 Warnings: nil, 1529 }, 1530 }, 1531 }, 1532 { 1533 InvocationResults: []HookOutcome{ 1534 { 1535 AnalyticsTags: hookanalytics.Analytics{}, 1536 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1537 Status: StatusExecutionFailure, 1538 Action: "", 1539 Message: "", 1540 DebugMessages: nil, 1541 Errors: []string{ 1542 fmt.Sprintf("Module (name: foobar, hook code: foo) tried to reject request on the %s stage that does not support rejection", hooks.StageAllProcessedBidResponses), 1543 }, 1544 Warnings: nil, 1545 }, 1546 }, 1547 }, 1548 { 1549 InvocationResults: []HookOutcome{ 1550 { 1551 AnalyticsTags: hookanalytics.Analytics{}, 1552 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1553 Status: StatusSuccess, 1554 Action: ActionUpdate, 1555 Message: "", 1556 DebugMessages: []string{ 1557 fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate), 1558 }, 1559 Errors: nil, 1560 Warnings: nil, 1561 }, 1562 }, 1563 }, 1564 }, 1565 }, 1566 }, 1567 }, 1568 { 1569 description: "Stage execution can be timed out", 1570 givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1571 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1572 }, 1573 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 1574 givenAccount: account, 1575 expectedBiddersResponse: expectedUpdatedAllProcBidResponses, 1576 expectedReject: nil, 1577 expectedModuleContexts: foobarModuleCtx, 1578 expectedStageOutcomes: []StageOutcome{ 1579 { 1580 Entity: entityAllProcessedBidResponses, 1581 Stage: hooks.StageAllProcessedBidResponses.String(), 1582 Groups: []GroupOutcome{ 1583 { 1584 InvocationResults: []HookOutcome{ 1585 { 1586 AnalyticsTags: hookanalytics.Analytics{}, 1587 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1588 Status: StatusTimeout, 1589 Action: "", 1590 Message: "", 1591 DebugMessages: nil, 1592 Errors: []string{"Hook execution timeout"}, 1593 Warnings: nil, 1594 }, 1595 }, 1596 }, 1597 { 1598 InvocationResults: []HookOutcome{ 1599 { 1600 AnalyticsTags: hookanalytics.Analytics{}, 1601 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1602 Status: StatusSuccess, 1603 Action: ActionUpdate, 1604 Message: "", 1605 DebugMessages: []string{ 1606 fmt.Sprintf("Hook mutation successfully applied, affected key: processedBidderResponse.bid.deal-priority, mutation type: %s", hookstage.MutationUpdate), 1607 }, 1608 Errors: nil, 1609 Warnings: nil, 1610 }, 1611 }, 1612 }, 1613 }, 1614 }, 1615 }, 1616 }, 1617 { 1618 description: "Modules contexts are preserved and correct", 1619 givenBiddersResponse: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ 1620 "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, 1621 }, 1622 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 1623 givenAccount: account, 1624 expectedBiddersResponse: expectedAllProcBidResponses, 1625 expectedReject: nil, 1626 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1627 "module-1": {"all-processed-bid-responses-ctx-1": "some-ctx-1"}, 1628 "module-2": {"all-processed-bid-responses-ctx-2": "some-ctx-2"}, 1629 }}, 1630 expectedStageOutcomes: []StageOutcome{ 1631 { 1632 Entity: entityAllProcessedBidResponses, 1633 Stage: hooks.StageAllProcessedBidResponses.String(), 1634 Groups: []GroupOutcome{ 1635 { 1636 InvocationResults: []HookOutcome{ 1637 { 1638 AnalyticsTags: hookanalytics.Analytics{}, 1639 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 1640 Status: StatusSuccess, 1641 Action: ActionNone, 1642 Message: "", 1643 DebugMessages: nil, 1644 Errors: nil, 1645 Warnings: nil, 1646 }, 1647 }, 1648 }, 1649 { 1650 InvocationResults: []HookOutcome{ 1651 { 1652 AnalyticsTags: hookanalytics.Analytics{}, 1653 HookID: HookID{ModuleCode: "module-2", HookImplCode: "bar"}, 1654 Status: StatusSuccess, 1655 Action: ActionNone, 1656 Message: "", 1657 DebugMessages: nil, 1658 Errors: nil, 1659 Warnings: nil, 1660 }, 1661 }, 1662 }, 1663 }, 1664 }, 1665 }, 1666 }, 1667 } 1668 1669 for _, test := range testCases { 1670 t.Run(test.description, func(t *testing.T) { 1671 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 1672 exec.SetAccount(test.givenAccount) 1673 1674 exec.ExecuteAllProcessedBidResponsesStage(test.givenBiddersResponse) 1675 1676 assert.Equal(t, test.expectedBiddersResponse, test.givenBiddersResponse, "Incorrect bidders response.") 1677 assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 1678 1679 stageOutcomes := exec.GetOutcomes() 1680 if len(test.expectedStageOutcomes) == 0 { 1681 assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") 1682 } else { 1683 assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) 1684 } 1685 }) 1686 } 1687 } 1688 1689 func TestExecuteAuctionResponseStage(t *testing.T) { 1690 foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} 1691 account := &config.Account{} 1692 resp := &openrtb2.BidResponse{CustomData: "some-custom-data"} 1693 expResp := &openrtb2.BidResponse{CustomData: "new-custom-data"} 1694 1695 testCases := []struct { 1696 description string 1697 givenPlanBuilder hooks.ExecutionPlanBuilder 1698 givenAccount *config.Account 1699 givenResponse *openrtb2.BidResponse 1700 expectedResponse *openrtb2.BidResponse 1701 expectedReject *RejectError 1702 expectedModuleContexts *moduleContexts 1703 expectedStageOutcomes []StageOutcome 1704 }{ 1705 { 1706 description: "Payload not changed if hook execution plan empty", 1707 givenPlanBuilder: hooks.EmptyPlanBuilder{}, 1708 givenAccount: account, 1709 givenResponse: resp, 1710 expectedResponse: resp, 1711 expectedReject: nil, 1712 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, 1713 expectedStageOutcomes: []StageOutcome{}, 1714 }, 1715 { 1716 description: "Payload changed if hooks return mutations", 1717 givenPlanBuilder: TestApplyHookMutationsBuilder{}, 1718 givenAccount: account, 1719 givenResponse: resp, 1720 expectedResponse: expResp, 1721 expectedReject: nil, 1722 expectedModuleContexts: foobarModuleCtx, 1723 expectedStageOutcomes: []StageOutcome{ 1724 { 1725 Entity: entityAuctionResponse, 1726 Stage: hooks.StageAuctionResponse.String(), 1727 Groups: []GroupOutcome{ 1728 { 1729 InvocationResults: []HookOutcome{ 1730 { 1731 AnalyticsTags: hookanalytics.Analytics{}, 1732 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1733 Status: StatusSuccess, 1734 Action: ActionUpdate, 1735 Message: "", 1736 DebugMessages: []string{ 1737 fmt.Sprintf("Hook mutation successfully applied, affected key: auctionResponse.bidResponse.custom-data, mutation type: %s", hookstage.MutationUpdate), 1738 }, 1739 Errors: nil, 1740 Warnings: nil, 1741 }, 1742 }, 1743 }, 1744 }, 1745 }, 1746 }, 1747 }, 1748 { 1749 description: "Stage execution can't be rejected - stage doesn't support rejection", 1750 givenPlanBuilder: TestRejectPlanBuilder{}, 1751 givenAccount: nil, 1752 givenResponse: resp, 1753 expectedResponse: expResp, 1754 expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageAuctionResponse.String()}, 1755 expectedModuleContexts: foobarModuleCtx, 1756 expectedStageOutcomes: []StageOutcome{ 1757 { 1758 Entity: entityAuctionResponse, 1759 Stage: hooks.StageAuctionResponse.String(), 1760 Groups: []GroupOutcome{ 1761 { 1762 InvocationResults: []HookOutcome{ 1763 { 1764 AnalyticsTags: hookanalytics.Analytics{}, 1765 HookID: HookID{ModuleCode: "foobar", HookImplCode: "baz"}, 1766 Status: StatusExecutionFailure, 1767 Action: "", 1768 Message: "", 1769 DebugMessages: nil, 1770 Errors: []string{"unexpected error"}, 1771 Warnings: nil, 1772 }, 1773 }, 1774 }, 1775 { 1776 InvocationResults: []HookOutcome{ 1777 { 1778 AnalyticsTags: hookanalytics.Analytics{}, 1779 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1780 Status: StatusExecutionFailure, 1781 Action: "", 1782 Message: "", 1783 DebugMessages: nil, 1784 Errors: []string{ 1785 fmt.Sprintf("Module (name: foobar, hook code: foo) tried to reject request on the %s stage that does not support rejection", hooks.StageAuctionResponse), 1786 }, 1787 Warnings: nil, 1788 }, 1789 }, 1790 }, 1791 { 1792 InvocationResults: []HookOutcome{ 1793 { 1794 AnalyticsTags: hookanalytics.Analytics{}, 1795 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1796 Status: StatusSuccess, 1797 Action: ActionUpdate, 1798 Message: "", 1799 DebugMessages: []string{ 1800 fmt.Sprintf("Hook mutation successfully applied, affected key: auctionResponse.bidResponse.custom-data, mutation type: %s", hookstage.MutationUpdate), 1801 }, 1802 Errors: nil, 1803 Warnings: nil, 1804 }, 1805 }, 1806 }, 1807 }, 1808 }, 1809 }, 1810 }, 1811 { 1812 description: "Request can be changed when a hook times out", 1813 givenPlanBuilder: TestWithTimeoutPlanBuilder{}, 1814 givenAccount: account, 1815 givenResponse: resp, 1816 expectedResponse: expResp, 1817 expectedReject: nil, 1818 expectedModuleContexts: foobarModuleCtx, 1819 expectedStageOutcomes: []StageOutcome{ 1820 { 1821 Entity: entityAuctionResponse, 1822 Stage: hooks.StageAuctionResponse.String(), 1823 Groups: []GroupOutcome{ 1824 { 1825 InvocationResults: []HookOutcome{ 1826 { 1827 AnalyticsTags: hookanalytics.Analytics{}, 1828 HookID: HookID{ModuleCode: "foobar", HookImplCode: "foo"}, 1829 Status: StatusTimeout, 1830 Action: "", 1831 Message: "", 1832 DebugMessages: nil, 1833 Errors: []string{"Hook execution timeout"}, 1834 Warnings: nil, 1835 }, 1836 }, 1837 }, 1838 { 1839 InvocationResults: []HookOutcome{ 1840 { 1841 AnalyticsTags: hookanalytics.Analytics{}, 1842 HookID: HookID{ModuleCode: "foobar", HookImplCode: "bar"}, 1843 Status: StatusSuccess, 1844 Action: ActionUpdate, 1845 Message: "", 1846 DebugMessages: []string{ 1847 fmt.Sprintf("Hook mutation successfully applied, affected key: auctionResponse.bidResponse.custom-data, mutation type: %s", hookstage.MutationUpdate), 1848 }, 1849 Errors: nil, 1850 Warnings: nil, 1851 }, 1852 }, 1853 }, 1854 }, 1855 }, 1856 }, 1857 }, 1858 { 1859 description: "Modules contexts are preserved and correct", 1860 givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, 1861 givenAccount: account, 1862 givenResponse: resp, 1863 expectedResponse: resp, 1864 expectedReject: nil, 1865 expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1866 "module-1": {"auction-response-ctx-1": "some-ctx-1", "auction-response-ctx-3": "some-ctx-3"}, 1867 "module-2": {"auction-response-ctx-2": "some-ctx-2"}, 1868 }}, 1869 expectedStageOutcomes: []StageOutcome{ 1870 { 1871 Entity: entityAuctionResponse, 1872 Stage: hooks.StageAuctionResponse.String(), 1873 Groups: []GroupOutcome{ 1874 { 1875 InvocationResults: []HookOutcome{ 1876 { 1877 AnalyticsTags: hookanalytics.Analytics{}, 1878 HookID: HookID{ModuleCode: "module-1", HookImplCode: "foo"}, 1879 Status: StatusSuccess, 1880 Action: ActionNone, 1881 Message: "", 1882 DebugMessages: nil, 1883 Errors: nil, 1884 Warnings: nil, 1885 }, 1886 { 1887 AnalyticsTags: hookanalytics.Analytics{}, 1888 HookID: HookID{ModuleCode: "module-2", HookImplCode: "baz"}, 1889 Status: StatusSuccess, 1890 Action: ActionNone, 1891 Message: "", 1892 DebugMessages: nil, 1893 Errors: nil, 1894 Warnings: nil, 1895 }, 1896 }, 1897 }, 1898 { 1899 InvocationResults: []HookOutcome{ 1900 { 1901 AnalyticsTags: hookanalytics.Analytics{}, 1902 HookID: HookID{ModuleCode: "module-1", HookImplCode: "bar"}, 1903 Status: StatusSuccess, 1904 Action: ActionNone, 1905 Message: "", 1906 DebugMessages: nil, 1907 Errors: nil, 1908 Warnings: nil, 1909 }, 1910 }, 1911 }, 1912 }, 1913 }, 1914 }, 1915 }, 1916 } 1917 1918 for _, test := range testCases { 1919 t.Run(test.description, func(t *testing.T) { 1920 exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 1921 exec.SetAccount(test.givenAccount) 1922 1923 exec.ExecuteAuctionResponseStage(test.givenResponse) 1924 1925 assert.Equal(t, test.expectedResponse, test.givenResponse, "Incorrect response update.") 1926 assert.Equal(t, test.expectedModuleContexts, exec.moduleContexts, "Incorrect module contexts") 1927 1928 stageOutcomes := exec.GetOutcomes() 1929 if len(test.expectedStageOutcomes) == 0 { 1930 assert.Empty(t, stageOutcomes, "Incorrect stage outcomes.") 1931 } else { 1932 assertEqualStageOutcomes(t, test.expectedStageOutcomes[0], stageOutcomes[0]) 1933 } 1934 }) 1935 } 1936 } 1937 1938 func TestInterStageContextCommunication(t *testing.T) { 1939 body := []byte(`{"foo": "bar"}`) 1940 reader := bytes.NewReader(body) 1941 exec := NewHookExecutor(TestWithModuleContextsPlanBuilder{}, EndpointAuction, &metricsConfig.NilMetricsEngine{}) 1942 req, err := http.NewRequest(http.MethodPost, "https://prebid.com/openrtb2/auction", reader) 1943 assert.NoError(t, err) 1944 1945 // test that context added at the entrypoint stage 1946 _, reject := exec.ExecuteEntrypointStage(req, body) 1947 assert.Nil(t, reject, "Unexpected reject from entrypoint stage.") 1948 assert.Equal( 1949 t, 1950 &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1951 "module-1": { 1952 "entrypoint-ctx-1": "some-ctx-1", 1953 "entrypoint-ctx-3": "some-ctx-3", 1954 }, 1955 "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, 1956 }}, 1957 exec.moduleContexts, 1958 "Wrong module contexts after executing entrypoint hook.", 1959 ) 1960 1961 // test that context added at the raw-auction stage merged with existing module contexts 1962 _, reject = exec.ExecuteRawAuctionStage(body) 1963 assert.Nil(t, reject, "Unexpected reject from raw-auction stage.") 1964 assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1965 "module-1": { 1966 "entrypoint-ctx-1": "some-ctx-1", 1967 "entrypoint-ctx-3": "some-ctx-3", 1968 "raw-auction-ctx-1": "some-ctx-1", 1969 "raw-auction-ctx-3": "some-ctx-3", 1970 }, 1971 "module-2": { 1972 "entrypoint-ctx-2": "some-ctx-2", 1973 "raw-auction-ctx-2": "some-ctx-2", 1974 }, 1975 }}, exec.moduleContexts, "Wrong module contexts after executing raw-auction hook.") 1976 1977 // test that context added at the processed-auction stage merged with existing module contexts 1978 err = exec.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) 1979 assert.Nil(t, err, "Unexpected reject from processed-auction stage.") 1980 assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 1981 "module-1": { 1982 "entrypoint-ctx-1": "some-ctx-1", 1983 "entrypoint-ctx-3": "some-ctx-3", 1984 "raw-auction-ctx-1": "some-ctx-1", 1985 "raw-auction-ctx-3": "some-ctx-3", 1986 "processed-auction-ctx-1": "some-ctx-1", 1987 "processed-auction-ctx-3": "some-ctx-3", 1988 }, 1989 "module-2": { 1990 "entrypoint-ctx-2": "some-ctx-2", 1991 "raw-auction-ctx-2": "some-ctx-2", 1992 "processed-auction-ctx-2": "some-ctx-2", 1993 }, 1994 }}, exec.moduleContexts, "Wrong module contexts after executing processed-auction hook.") 1995 1996 // test that context added at the raw bidder response stage merged with existing module contexts 1997 reject = exec.ExecuteRawBidderResponseStage(&adapters.BidderResponse{}, "some-bidder") 1998 assert.Nil(t, reject, "Unexpected reject from raw-bidder-response stage.") 1999 assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 2000 "module-1": { 2001 "entrypoint-ctx-1": "some-ctx-1", 2002 "entrypoint-ctx-3": "some-ctx-3", 2003 "raw-auction-ctx-1": "some-ctx-1", 2004 "raw-auction-ctx-3": "some-ctx-3", 2005 "processed-auction-ctx-1": "some-ctx-1", 2006 "processed-auction-ctx-3": "some-ctx-3", 2007 "raw-bidder-response-ctx-1": "some-ctx-1", 2008 "raw-bidder-response-ctx-3": "some-ctx-3", 2009 }, 2010 "module-2": { 2011 "entrypoint-ctx-2": "some-ctx-2", 2012 "raw-auction-ctx-2": "some-ctx-2", 2013 "processed-auction-ctx-2": "some-ctx-2", 2014 "raw-bidder-response-ctx-2": "some-ctx-2", 2015 }, 2016 }}, exec.moduleContexts, "Wrong module contexts after executing raw-bidder-response hook.") 2017 2018 // test that context added at the auction-response stage merged with existing module contexts 2019 exec.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) 2020 assert.Nil(t, reject, "Unexpected reject from raw-auction stage.") 2021 assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ 2022 "module-1": { 2023 "entrypoint-ctx-1": "some-ctx-1", 2024 "entrypoint-ctx-3": "some-ctx-3", 2025 "raw-auction-ctx-1": "some-ctx-1", 2026 "raw-auction-ctx-3": "some-ctx-3", 2027 "processed-auction-ctx-1": "some-ctx-1", 2028 "processed-auction-ctx-3": "some-ctx-3", 2029 "raw-bidder-response-ctx-1": "some-ctx-1", 2030 "raw-bidder-response-ctx-3": "some-ctx-3", 2031 "auction-response-ctx-1": "some-ctx-1", 2032 "auction-response-ctx-3": "some-ctx-3", 2033 }, 2034 "module-2": { 2035 "entrypoint-ctx-2": "some-ctx-2", 2036 "raw-auction-ctx-2": "some-ctx-2", 2037 "processed-auction-ctx-2": "some-ctx-2", 2038 "raw-bidder-response-ctx-2": "some-ctx-2", 2039 "auction-response-ctx-2": "some-ctx-2", 2040 }, 2041 }}, exec.moduleContexts, "Wrong module contexts after executing auction-response hook.") 2042 } 2043 2044 type TestApplyHookMutationsBuilder struct { 2045 hooks.EmptyPlanBuilder 2046 } 2047 2048 func (e TestApplyHookMutationsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 2049 return hooks.Plan[hookstage.Entrypoint]{ 2050 hooks.Group[hookstage.Entrypoint]{ 2051 Timeout: 10 * time.Millisecond, 2052 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2053 {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, 2054 {Module: "foobar", Code: "foobaz", Hook: mockFailedMutationHook{}}, 2055 {Module: "foobar", Code: "bar", Hook: mockUpdateQueryEntrypointHook{}}, 2056 }, 2057 }, 2058 hooks.Group[hookstage.Entrypoint]{ 2059 Timeout: 10 * time.Millisecond, 2060 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2061 {Module: "foobar", Code: "baz", Hook: mockUpdateBodyHook{}}, 2062 {Module: "foobar", Code: "foo", Hook: mockFailureHook{}}, 2063 }, 2064 }, 2065 } 2066 } 2067 2068 func (e TestApplyHookMutationsBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 2069 return hooks.Plan[hookstage.RawAuctionRequest]{ 2070 hooks.Group[hookstage.RawAuctionRequest]{ 2071 Timeout: 10 * time.Millisecond, 2072 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2073 {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, 2074 {Module: "foobar", Code: "bar", Hook: mockFailedMutationHook{}}, 2075 }, 2076 }, 2077 hooks.Group[hookstage.RawAuctionRequest]{ 2078 Timeout: 10 * time.Millisecond, 2079 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2080 {Module: "foobar", Code: "baz", Hook: mockFailureHook{}}, 2081 }, 2082 }, 2083 } 2084 } 2085 2086 func (e TestApplyHookMutationsBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 2087 return hooks.Plan[hookstage.ProcessedAuctionRequest]{ 2088 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2089 Timeout: 10 * time.Millisecond, 2090 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2091 {Module: "foobar", Code: "foo", Hook: mockUpdateBidRequestHook{}}, 2092 }, 2093 }, 2094 } 2095 } 2096 2097 func (e TestApplyHookMutationsBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 2098 return hooks.Plan[hookstage.BidderRequest]{ 2099 hooks.Group[hookstage.BidderRequest]{ 2100 Timeout: 10 * time.Millisecond, 2101 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2102 {Module: "foobar", Code: "foo", Hook: mockUpdateBidRequestHook{}}, 2103 {Module: "foobar", Code: "bar", Hook: mockFailedMutationHook{}}, 2104 }, 2105 }, 2106 hooks.Group[hookstage.BidderRequest]{ 2107 Timeout: 10 * time.Millisecond, 2108 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2109 {Module: "foobar", Code: "baz", Hook: mockFailureHook{}}, 2110 }, 2111 }, 2112 } 2113 } 2114 2115 func (e TestApplyHookMutationsBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 2116 return hooks.Plan[hookstage.RawBidderResponse]{ 2117 hooks.Group[hookstage.RawBidderResponse]{ 2118 Timeout: 10 * time.Millisecond, 2119 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2120 {Module: "foobar", Code: "foo", Hook: mockUpdateBidderResponseHook{}}, 2121 }, 2122 }, 2123 } 2124 } 2125 2126 func (e TestApplyHookMutationsBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 2127 return hooks.Plan[hookstage.AllProcessedBidResponses]{ 2128 hooks.Group[hookstage.AllProcessedBidResponses]{ 2129 Timeout: 10 * time.Millisecond, 2130 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2131 {Module: "foobar", Code: "foo", Hook: mockUpdateBiddersResponsesHook{}}, 2132 {Module: "foobar", Code: "bar", Hook: mockFailedMutationHook{}}, 2133 }, 2134 }, 2135 hooks.Group[hookstage.AllProcessedBidResponses]{ 2136 Timeout: 10 * time.Millisecond, 2137 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2138 {Module: "foobar", Code: "baz", Hook: mockFailureHook{}}, 2139 }, 2140 }, 2141 } 2142 } 2143 2144 func (e TestApplyHookMutationsBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 2145 return hooks.Plan[hookstage.AuctionResponse]{ 2146 hooks.Group[hookstage.AuctionResponse]{ 2147 Timeout: 1 * time.Millisecond, 2148 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2149 {Module: "foobar", Code: "foo", Hook: mockUpdateBidResponseHook{}}, 2150 }, 2151 }, 2152 } 2153 } 2154 2155 type TestRejectPlanBuilder struct { 2156 hooks.EmptyPlanBuilder 2157 } 2158 2159 func (e TestRejectPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 2160 return hooks.Plan[hookstage.Entrypoint]{ 2161 hooks.Group[hookstage.Entrypoint]{ 2162 Timeout: 10 * time.Millisecond, 2163 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2164 {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, 2165 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2166 }, 2167 }, 2168 hooks.Group[hookstage.Entrypoint]{ 2169 Timeout: 10 * time.Millisecond, 2170 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2171 // reject stage 2172 {Module: "foobar", Code: "bar", Hook: mockRejectHook{}}, 2173 // next hook rejected: we use timeout hook to make sure 2174 // that it runs longer than previous one, so it won't be executed earlier 2175 {Module: "foobar", Code: "baz", Hook: mockTimeoutHook{}}, 2176 }, 2177 }, 2178 // group of hooks rejected 2179 hooks.Group[hookstage.Entrypoint]{ 2180 Timeout: 10 * time.Millisecond, 2181 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2182 {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, 2183 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2184 }, 2185 }, 2186 } 2187 } 2188 2189 func (e TestRejectPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 2190 return hooks.Plan[hookstage.RawAuctionRequest]{ 2191 hooks.Group[hookstage.RawAuctionRequest]{ 2192 Timeout: 10 * time.Millisecond, 2193 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2194 {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, 2195 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2196 }, 2197 }, 2198 hooks.Group[hookstage.RawAuctionRequest]{ 2199 Timeout: 10 * time.Millisecond, 2200 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2201 {Module: "foobar", Code: "bar", Hook: mockRejectHook{}}, 2202 // next hook rejected: we use timeout hook to make sure 2203 // that it runs longer than previous one, so it won't be executed earlier 2204 {Module: "foobar", Code: "baz", Hook: mockTimeoutHook{}}, 2205 }, 2206 }, 2207 // group of hooks rejected 2208 hooks.Group[hookstage.RawAuctionRequest]{ 2209 Timeout: 10 * time.Millisecond, 2210 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2211 {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, 2212 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2213 }, 2214 }, 2215 } 2216 } 2217 2218 func (e TestRejectPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 2219 return hooks.Plan[hookstage.ProcessedAuctionRequest]{ 2220 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2221 Timeout: 10 * time.Millisecond, 2222 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2223 {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, 2224 }, 2225 }, 2226 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2227 Timeout: 10 * time.Millisecond, 2228 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2229 {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, 2230 }, 2231 }, 2232 } 2233 } 2234 2235 func (e TestRejectPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 2236 return hooks.Plan[hookstage.BidderRequest]{ 2237 hooks.Group[hookstage.BidderRequest]{ 2238 Timeout: 10 * time.Millisecond, 2239 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2240 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2241 }, 2242 }, 2243 hooks.Group[hookstage.BidderRequest]{ 2244 Timeout: 10 * time.Millisecond, 2245 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2246 {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, 2247 }, 2248 }, 2249 hooks.Group[hookstage.BidderRequest]{ 2250 Timeout: 10 * time.Millisecond, 2251 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2252 {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, 2253 }, 2254 }, 2255 } 2256 } 2257 2258 func (e TestRejectPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 2259 return hooks.Plan[hookstage.RawBidderResponse]{ 2260 hooks.Group[hookstage.RawBidderResponse]{ 2261 Timeout: 10 * time.Millisecond, 2262 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2263 {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, 2264 }, 2265 }, 2266 } 2267 } 2268 2269 func (e TestRejectPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 2270 return hooks.Plan[hookstage.AllProcessedBidResponses]{ 2271 hooks.Group[hookstage.AllProcessedBidResponses]{ 2272 Timeout: 10 * time.Millisecond, 2273 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2274 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2275 }, 2276 }, 2277 // rejection ignored, stage doesn't support rejection 2278 hooks.Group[hookstage.AllProcessedBidResponses]{ 2279 Timeout: 10 * time.Millisecond, 2280 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2281 {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, 2282 }, 2283 }, 2284 // hook executed and payload updated because this stage doesn't support rejection 2285 hooks.Group[hookstage.AllProcessedBidResponses]{ 2286 Timeout: 10 * time.Millisecond, 2287 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2288 {Module: "foobar", Code: "bar", Hook: mockUpdateBiddersResponsesHook{}}, 2289 }, 2290 }, 2291 } 2292 } 2293 2294 func (e TestRejectPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 2295 return hooks.Plan[hookstage.AuctionResponse]{ 2296 hooks.Group[hookstage.AuctionResponse]{ 2297 Timeout: 1 * time.Millisecond, 2298 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2299 {Module: "foobar", Code: "baz", Hook: mockErrorHook{}}, 2300 }, 2301 }, 2302 // rejection ignored, stage doesn't support rejection 2303 hooks.Group[hookstage.AuctionResponse]{ 2304 Timeout: 1 * time.Millisecond, 2305 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2306 {Module: "foobar", Code: "foo", Hook: mockRejectHook{}}, 2307 }, 2308 }, 2309 // hook executed and payload updated because this stage doesn't support rejection 2310 hooks.Group[hookstage.AuctionResponse]{ 2311 Timeout: 1 * time.Millisecond, 2312 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2313 {Module: "foobar", Code: "bar", Hook: mockUpdateBidResponseHook{}}, 2314 }, 2315 }, 2316 } 2317 } 2318 2319 type TestWithTimeoutPlanBuilder struct { 2320 hooks.EmptyPlanBuilder 2321 } 2322 2323 func (e TestWithTimeoutPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 2324 return hooks.Plan[hookstage.Entrypoint]{ 2325 hooks.Group[hookstage.Entrypoint]{ 2326 Timeout: 10 * time.Millisecond, 2327 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2328 {Module: "foobar", Code: "foo", Hook: mockUpdateHeaderEntrypointHook{}}, 2329 {Module: "foobar", Code: "bar", Hook: mockTimeoutHook{}}, 2330 }, 2331 }, 2332 hooks.Group[hookstage.Entrypoint]{ 2333 Timeout: 10 * time.Millisecond, 2334 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2335 {Module: "foobar", Code: "baz", Hook: mockUpdateBodyHook{}}, 2336 }, 2337 }, 2338 } 2339 } 2340 2341 func (e TestWithTimeoutPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 2342 return hooks.Plan[hookstage.RawAuctionRequest]{ 2343 hooks.Group[hookstage.RawAuctionRequest]{ 2344 Timeout: 10 * time.Millisecond, 2345 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2346 {Module: "foobar", Code: "foo", Hook: mockUpdateBodyHook{}}, 2347 }, 2348 }, 2349 hooks.Group[hookstage.RawAuctionRequest]{ 2350 Timeout: 10 * time.Millisecond, 2351 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2352 {Module: "foobar", Code: "bar", Hook: mockTimeoutHook{}}, 2353 }, 2354 }, 2355 } 2356 } 2357 2358 func (e TestWithTimeoutPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 2359 return hooks.Plan[hookstage.ProcessedAuctionRequest]{ 2360 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2361 Timeout: 10 * time.Millisecond, 2362 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2363 {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, 2364 }, 2365 }, 2366 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2367 Timeout: 10 * time.Millisecond, 2368 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2369 {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, 2370 }, 2371 }, 2372 } 2373 } 2374 2375 func (e TestWithTimeoutPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 2376 return hooks.Plan[hookstage.BidderRequest]{ 2377 hooks.Group[hookstage.BidderRequest]{ 2378 Timeout: 10 * time.Millisecond, 2379 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2380 {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, 2381 }, 2382 }, 2383 hooks.Group[hookstage.BidderRequest]{ 2384 Timeout: 10 * time.Millisecond, 2385 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2386 {Module: "foobar", Code: "bar", Hook: mockUpdateBidRequestHook{}}, 2387 }, 2388 }, 2389 } 2390 } 2391 2392 func (e TestWithTimeoutPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 2393 return hooks.Plan[hookstage.RawBidderResponse]{ 2394 hooks.Group[hookstage.RawBidderResponse]{ 2395 Timeout: 10 * time.Millisecond, 2396 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2397 {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, 2398 }, 2399 }, 2400 hooks.Group[hookstage.RawBidderResponse]{ 2401 Timeout: 10 * time.Millisecond, 2402 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2403 {Module: "foobar", Code: "bar", Hook: mockUpdateBidderResponseHook{}}, 2404 }, 2405 }, 2406 } 2407 } 2408 2409 func (e TestWithTimeoutPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 2410 return hooks.Plan[hookstage.AllProcessedBidResponses]{ 2411 hooks.Group[hookstage.AllProcessedBidResponses]{ 2412 Timeout: 10 * time.Millisecond, 2413 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2414 {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, 2415 }, 2416 }, 2417 hooks.Group[hookstage.AllProcessedBidResponses]{ 2418 Timeout: 10 * time.Millisecond, 2419 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2420 {Module: "foobar", Code: "bar", Hook: mockUpdateBiddersResponsesHook{}}, 2421 }, 2422 }, 2423 } 2424 } 2425 2426 func (e TestWithTimeoutPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 2427 return hooks.Plan[hookstage.AuctionResponse]{ 2428 hooks.Group[hookstage.AuctionResponse]{ 2429 Timeout: 1 * time.Millisecond, 2430 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2431 {Module: "foobar", Code: "foo", Hook: mockTimeoutHook{}}, 2432 }, 2433 }, 2434 hooks.Group[hookstage.AuctionResponse]{ 2435 Timeout: 1 * time.Millisecond, 2436 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2437 {Module: "foobar", Code: "bar", Hook: mockUpdateBidResponseHook{}}, 2438 }, 2439 }, 2440 } 2441 } 2442 2443 type TestWithModuleContextsPlanBuilder struct { 2444 hooks.EmptyPlanBuilder 2445 } 2446 2447 func (e TestWithModuleContextsPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 2448 return hooks.Plan[hookstage.Entrypoint]{ 2449 hooks.Group[hookstage.Entrypoint]{ 2450 Timeout: 10 * time.Millisecond, 2451 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2452 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "entrypoint-ctx-1", val: "some-ctx-1"}}, 2453 }, 2454 }, 2455 hooks.Group[hookstage.Entrypoint]{ 2456 Timeout: 10 * time.Millisecond, 2457 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2458 {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "entrypoint-ctx-2", val: "some-ctx-2"}}, 2459 {Module: "module-1", Code: "baz", Hook: mockModuleContextHook{key: "entrypoint-ctx-3", val: "some-ctx-3"}}, 2460 }, 2461 }, 2462 } 2463 } 2464 2465 func (e TestWithModuleContextsPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] { 2466 return hooks.Plan[hookstage.RawAuctionRequest]{ 2467 hooks.Group[hookstage.RawAuctionRequest]{ 2468 Timeout: 10 * time.Millisecond, 2469 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2470 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "raw-auction-ctx-1", val: "some-ctx-1"}}, 2471 {Module: "module-2", Code: "baz", Hook: mockModuleContextHook{key: "raw-auction-ctx-2", val: "some-ctx-2"}}, 2472 }, 2473 }, 2474 hooks.Group[hookstage.RawAuctionRequest]{ 2475 Timeout: 10 * time.Millisecond, 2476 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 2477 {Module: "module-1", Code: "bar", Hook: mockModuleContextHook{key: "raw-auction-ctx-3", val: "some-ctx-3"}}, 2478 }, 2479 }, 2480 } 2481 } 2482 2483 func (e TestWithModuleContextsPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] { 2484 return hooks.Plan[hookstage.ProcessedAuctionRequest]{ 2485 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2486 Timeout: 10 * time.Millisecond, 2487 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2488 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "processed-auction-ctx-1", val: "some-ctx-1"}}, 2489 }, 2490 }, 2491 hooks.Group[hookstage.ProcessedAuctionRequest]{ 2492 Timeout: 10 * time.Millisecond, 2493 Hooks: []hooks.HookWrapper[hookstage.ProcessedAuctionRequest]{ 2494 {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "processed-auction-ctx-2", val: "some-ctx-2"}}, 2495 {Module: "module-1", Code: "baz", Hook: mockModuleContextHook{key: "processed-auction-ctx-3", val: "some-ctx-3"}}, 2496 }, 2497 }, 2498 } 2499 } 2500 2501 func (e TestWithModuleContextsPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] { 2502 return hooks.Plan[hookstage.BidderRequest]{ 2503 hooks.Group[hookstage.BidderRequest]{ 2504 Timeout: 10 * time.Millisecond, 2505 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2506 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "bidder-request-ctx-1", val: "some-ctx-1"}}, 2507 }, 2508 }, 2509 hooks.Group[hookstage.BidderRequest]{ 2510 Timeout: 10 * time.Millisecond, 2511 Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{ 2512 {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "bidder-request-ctx-2", val: "some-ctx-2"}}, 2513 }, 2514 }, 2515 } 2516 } 2517 2518 func (e TestWithModuleContextsPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] { 2519 return hooks.Plan[hookstage.RawBidderResponse]{ 2520 hooks.Group[hookstage.RawBidderResponse]{ 2521 Timeout: 10 * time.Millisecond, 2522 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2523 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "raw-bidder-response-ctx-1", val: "some-ctx-1"}}, 2524 {Module: "module-2", Code: "baz", Hook: mockModuleContextHook{key: "raw-bidder-response-ctx-2", val: "some-ctx-2"}}, 2525 }, 2526 }, 2527 hooks.Group[hookstage.RawBidderResponse]{ 2528 Timeout: 10 * time.Millisecond, 2529 Hooks: []hooks.HookWrapper[hookstage.RawBidderResponse]{ 2530 {Module: "module-1", Code: "bar", Hook: mockModuleContextHook{key: "raw-bidder-response-ctx-3", val: "some-ctx-3"}}, 2531 }, 2532 }, 2533 } 2534 } 2535 2536 func (e TestWithModuleContextsPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] { 2537 return hooks.Plan[hookstage.AllProcessedBidResponses]{ 2538 hooks.Group[hookstage.AllProcessedBidResponses]{ 2539 Timeout: 10 * time.Millisecond, 2540 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2541 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "all-processed-bid-responses-ctx-1", val: "some-ctx-1"}}, 2542 }, 2543 }, 2544 hooks.Group[hookstage.AllProcessedBidResponses]{ 2545 Timeout: 10 * time.Millisecond, 2546 Hooks: []hooks.HookWrapper[hookstage.AllProcessedBidResponses]{ 2547 {Module: "module-2", Code: "bar", Hook: mockModuleContextHook{key: "all-processed-bid-responses-ctx-2", val: "some-ctx-2"}}, 2548 }, 2549 }, 2550 } 2551 } 2552 2553 func (e TestWithModuleContextsPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] { 2554 return hooks.Plan[hookstage.AuctionResponse]{ 2555 hooks.Group[hookstage.AuctionResponse]{ 2556 Timeout: 1 * time.Millisecond, 2557 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2558 {Module: "module-1", Code: "foo", Hook: mockModuleContextHook{key: "auction-response-ctx-1", val: "some-ctx-1"}}, 2559 {Module: "module-2", Code: "baz", Hook: mockModuleContextHook{key: "auction-response-ctx-2", val: "some-ctx-2"}}, 2560 }, 2561 }, 2562 hooks.Group[hookstage.AuctionResponse]{ 2563 Timeout: 1 * time.Millisecond, 2564 Hooks: []hooks.HookWrapper[hookstage.AuctionResponse]{ 2565 {Module: "module-1", Code: "bar", Hook: mockModuleContextHook{key: "auction-response-ctx-3", val: "some-ctx-3"}}, 2566 }, 2567 }, 2568 } 2569 } 2570 2571 type TestAllHookResultsBuilder struct { 2572 hooks.EmptyPlanBuilder 2573 } 2574 2575 func (e TestAllHookResultsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] { 2576 return hooks.Plan[hookstage.Entrypoint]{ 2577 hooks.Group[hookstage.Entrypoint]{ 2578 Timeout: 10 * time.Millisecond, 2579 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2580 {Module: "module.x-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}}, 2581 {Module: "module.x-1", Code: "code-3", Hook: mockTimeoutHook{}}, 2582 {Module: "module.x-1", Code: "code-4", Hook: mockFailureHook{}}, 2583 {Module: "module.x-1", Code: "code-5", Hook: mockErrorHook{}}, 2584 {Module: "module.x-1", Code: "code-6", Hook: mockFailedMutationHook{}}, 2585 {Module: "module.x-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}}, 2586 }, 2587 }, 2588 // place the reject hook in a separate group because it rejects the stage completely 2589 // thus we can not make accurate mock calls if it is processed in parallel with others 2590 hooks.Group[hookstage.Entrypoint]{ 2591 Timeout: 10 * time.Second, 2592 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 2593 {Module: "module.x-1", Code: "code-2", Hook: mockRejectHook{}}, 2594 }, 2595 }, 2596 } 2597 }