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