diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index c18ebd5af96..7770a2ec342 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -5996,7 +5996,7 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mct }, hookstage.MutationUpdate, "bidRequest", "site.domain", ) - mctx.ModuleContext = map[string]interface{}{"some-ctx": "some-ctx"} + mctx.ModuleContext.Set("some-ctx", "some-ctx") return hookstage.HookResult[hookstage.BidderRequestPayload]{ChangeSet: c, ModuleContext: mctx.ModuleContext}, nil } diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index fee4e7385a7..d0d8073ddaa 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -43,24 +43,27 @@ func (ctx executionContext) getModuleContext(moduleName string) hookstage.Module // moduleContexts preserves data the module wants to pass to itself from earlier stages to later stages. type moduleContexts struct { sync.RWMutex - ctxs map[string]hookstage.ModuleContext // format: {"module_name": hookstage.ModuleContext} + ctxs map[string]*hookstage.ModuleContext // format: {"module_name": hookstage.ModuleContext} } -func (mc *moduleContexts) put(moduleName string, mCtx hookstage.ModuleContext) { +func (mc *moduleContexts) put(moduleName string, mCtx *hookstage.ModuleContext) { + if mc == nil { + return + } mc.Lock() defer mc.Unlock() - newCtx := mCtx - if existingCtx, ok := mc.ctxs[moduleName]; ok && existingCtx != nil { - for k, v := range mCtx { - existingCtx[k] = v - } - newCtx = existingCtx + existingCtx, ok := mc.ctxs[moduleName] + if !ok { + mc.ctxs[moduleName] = mCtx + return } - mc.ctxs[moduleName] = newCtx + + // Add new data to existing context + existingCtx.SetAll(mCtx.GetAll()) } -func (mc *moduleContexts) get(moduleName string) (hookstage.ModuleContext, bool) { +func (mc *moduleContexts) get(moduleName string) (*hookstage.ModuleContext, bool) { mc.RLock() defer mc.RUnlock() mCtx, ok := mc.ctxs[moduleName] @@ -72,4 +75,4 @@ type stageModuleContext struct { groupCtx []groupModuleContext } -type groupModuleContext map[string]hookstage.ModuleContext +type groupModuleContext map[string]*hookstage.ModuleContext diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index c33cfcdda57..eb66caf64ab 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -68,7 +68,7 @@ func NewHookExecutor(builder hooks.ExecutionPlanBuilder, endpoint string, me met endpoint: endpoint, planBuilder: builder, stageOutcomes: []StageOutcome{}, - moduleContexts: &moduleContexts{ctxs: make(map[string]hookstage.ModuleContext)}, + moduleContexts: &moduleContexts{ctxs: make(map[string]*hookstage.ModuleContext)}, metricEngine: me, } } diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index dd309dffa9a..3dd0e76c02f 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -61,7 +61,7 @@ func TestExecuteEntrypointStage(t *testing.T) { const body string = `{"name": "John", "last_name": "Doe"}` const urlString string = "https://prebid.com/openrtb2/auction" - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} testCases := []struct { description string @@ -84,7 +84,7 @@ func TestExecuteEntrypointStage(t *testing.T) { expectedHeader: http.Header{}, expectedQuery: url.Values{}, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -299,9 +299,18 @@ func TestExecuteEntrypointStage(t *testing.T) { expectedHeader: http.Header{}, expectedQuery: url.Values{}, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"entrypoint-ctx-1": "some-ctx-1", "entrypoint-ctx-3": "some-ctx-3"}, - "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -418,7 +427,7 @@ func TestExecuteRawAuctionStage(t *testing.T) { const bodyUpdated string = `{"last_name": "Doe", "foo": "bar"}` const urlString string = "https://prebid.com/openrtb2/auction" - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} testCases := []struct { description string @@ -437,7 +446,7 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenPlanBuilder: hooks.EmptyPlanBuilder{}, expectedBody: body, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -613,9 +622,18 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, expectedBody: body, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"raw-auction-ctx-1": "some-ctx-1", "raw-auction-ctx-3": "some-ctx-3"}, - "module-2": {"raw-auction-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("raw-auction-ctx-1", "some-ctx-1") + mc.Set("raw-auction-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("raw-auction-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -691,7 +709,7 @@ func TestExecuteRawAuctionStage(t *testing.T) { } func TestExecuteProcessedAuctionStage(t *testing.T) { - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} req := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} reqUpdated := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id", Yob: 2000, Consent: "true"}} @@ -710,7 +728,7 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: req, expectedErr: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -831,9 +849,18 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: req, expectedErr: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"processed-auction-ctx-1": "some-ctx-1", "processed-auction-ctx-3": "some-ctx-3"}, - "module-2": {"processed-auction-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("processed-auction-ctx-1", "some-ctx-1") + mc.Set("processed-auction-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("processed-auction-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -910,7 +937,7 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { func TestExecuteBidderRequestStage(t *testing.T) { bidderName := "the-bidder" - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} expectedBidderRequest := &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} expectedUpdatedBidderRequest := &openrtb2.BidRequest{ @@ -938,7 +965,7 @@ func TestExecuteBidderRequestStage(t *testing.T) { givenPlanBuilder: hooks.EmptyPlanBuilder{}, expectedBidderRequest: expectedBidderRequest, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -1106,9 +1133,17 @@ func TestExecuteBidderRequestStage(t *testing.T) { givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, expectedBidderRequest: expectedBidderRequest, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"bidder-request-ctx-1": "some-ctx-1"}, - "module-2": {"bidder-request-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("bidder-request-ctx-1", "some-ctx-1") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("bidder-request-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -1218,7 +1253,7 @@ func buildDefaultActivityConfig(componentName string, allow bool) config.Activit } func TestExecuteRawBidderResponseStage(t *testing.T) { - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} resp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 1}}} expResp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 10}}} vEntity := entity("the-bidder") @@ -1238,7 +1273,7 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { givenBidderResponse: resp, expectedBidderResponse: resp, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -1357,9 +1392,18 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { givenBidderResponse: resp, expectedBidderResponse: expResp, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"raw-bidder-response-ctx-1": "some-ctx-1", "raw-bidder-response-ctx-3": "some-ctx-3"}, - "module-2": {"raw-bidder-response-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("raw-bidder-response-ctx-1", "some-ctx-1") + mc.Set("raw-bidder-response-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("raw-bidder-response-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -1435,7 +1479,7 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { } func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} expectedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, @@ -1461,7 +1505,7 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { givenPlanBuilder: hooks.EmptyPlanBuilder{}, expectedBiddersResponse: expectedAllProcBidResponses, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -1642,9 +1686,17 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, expectedBiddersResponse: expectedAllProcBidResponses, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"all-processed-bid-responses-ctx-1": "some-ctx-1"}, - "module-2": {"all-processed-bid-responses-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("all-processed-bid-responses-ctx-1", "some-ctx-1") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("all-processed-bid-responses-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -1709,7 +1761,7 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { } func TestExecuteAuctionResponseStage(t *testing.T) { - foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} + foobarModuleCtx := &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}} resp := &openrtb2.BidResponse{CustomData: "some-custom-data"} expResp := &openrtb2.BidResponse{CustomData: "new-custom-data"} @@ -1728,7 +1780,7 @@ func TestExecuteAuctionResponseStage(t *testing.T) { givenResponse: resp, expectedResponse: resp, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -1877,9 +1929,18 @@ func TestExecuteAuctionResponseStage(t *testing.T) { givenResponse: resp, expectedResponse: resp, expectedReject: nil, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"auction-response-ctx-1": "some-ctx-1", "auction-response-ctx-3": "some-ctx-3"}, - "module-2": {"auction-response-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("auction-response-ctx-1", "some-ctx-1") + mc.Set("auction-response-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("auction-response-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -2000,7 +2061,7 @@ func TestExecuteExitpointStage(t *testing.T) { expectedResponseHeaders: http.Header{ "Content-Type": []string{"application/json"}, }, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{}}, expectedStageOutcomes: []StageOutcome{}, }, { @@ -2027,7 +2088,7 @@ func TestExecuteExitpointStage(t *testing.T) { expectedResponseHeaders: http.Header{ "Content-Type": []string{"application/xml"}, }, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}}, expectedStageOutcomes: []StageOutcome{ { Entity: "exitpoint", @@ -2068,7 +2129,7 @@ func TestExecuteExitpointStage(t *testing.T) { }, expectedResponse: ``, expectedResponseHeaders: http.Header{"Content-Type": []string{"application/xml"}}, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}}, expectedStageOutcomes: []StageOutcome{ { Entity: entityExitpoint, @@ -2144,7 +2205,7 @@ func TestExecuteExitpointStage(t *testing.T) { }, expectedResponse: ``, expectedResponseHeaders: http.Header{"Content-Type": []string{"application/xml"}}, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{"foobar": nil}}, expectedStageOutcomes: []StageOutcome{ { Entity: entityExitpoint, @@ -2202,9 +2263,18 @@ func TestExecuteExitpointStage(t *testing.T) { }, expectedResponse: &openrtb2.BidResponse{ID: "test-id"}, expectedResponseHeaders: http.Header{"Content-Type": []string{"application/json"}}, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": {"exitpoint-ctx-1": "some-ctx-1", "exitpoint-ctx-3": "some-ctx-3"}, - "module-2": {"exitpoint-ctx-2": "some-ctx-2"}, + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("exitpoint-ctx-1", "some-ctx-1") + mc.Set("exitpoint-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("exitpoint-ctx-2", "some-ctx-2") + return mc + }(), }}, expectedStageOutcomes: []StageOutcome{ { @@ -2277,7 +2347,7 @@ func TestExecuteExitpointStage(t *testing.T) { expectedResponseHeaders: http.Header{ "Content-Type": []string{"application/json"}, }, - expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ + expectedModuleContexts: &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ "module-1": nil, "module-2": nil, }}, @@ -2345,12 +2415,18 @@ func TestInterStageContextCommunication(t *testing.T) { assert.Nil(t, reject, "Unexpected reject from entrypoint stage.") assert.Equal( t, - &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": { - "entrypoint-ctx-1": "some-ctx-1", - "entrypoint-ctx-3": "some-ctx-3", - }, - "module-2": {"entrypoint-ctx-2": "some-ctx-2"}, + &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + return mc + }(), }}, exec.moduleContexts, "Wrong module contexts after executing entrypoint hook.", @@ -2359,83 +2435,99 @@ func TestInterStageContextCommunication(t *testing.T) { // test that context added at the raw-auction stage merged with existing module contexts _, reject = exec.ExecuteRawAuctionStage(body) assert.Nil(t, reject, "Unexpected reject from raw-auction stage.") - assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": { - "entrypoint-ctx-1": "some-ctx-1", - "entrypoint-ctx-3": "some-ctx-3", - "raw-auction-ctx-1": "some-ctx-1", - "raw-auction-ctx-3": "some-ctx-3", - }, - "module-2": { - "entrypoint-ctx-2": "some-ctx-2", - "raw-auction-ctx-2": "some-ctx-2", - }, + assert.Equal(t, &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + mc.Set("raw-auction-ctx-1", "some-ctx-1") + mc.Set("raw-auction-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + mc.Set("raw-auction-ctx-2", "some-ctx-2") + return mc + }(), }}, exec.moduleContexts, "Wrong module contexts after executing raw-auction hook.") // test that context added at the processed-auction stage merged with existing module contexts err = exec.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) assert.Nil(t, err, "Unexpected reject from processed-auction stage.") - assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": { - "entrypoint-ctx-1": "some-ctx-1", - "entrypoint-ctx-3": "some-ctx-3", - "raw-auction-ctx-1": "some-ctx-1", - "raw-auction-ctx-3": "some-ctx-3", - "processed-auction-ctx-1": "some-ctx-1", - "processed-auction-ctx-3": "some-ctx-3", - }, - "module-2": { - "entrypoint-ctx-2": "some-ctx-2", - "raw-auction-ctx-2": "some-ctx-2", - "processed-auction-ctx-2": "some-ctx-2", - }, + assert.Equal(t, &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + mc.Set("raw-auction-ctx-1", "some-ctx-1") + mc.Set("raw-auction-ctx-3", "some-ctx-3") + mc.Set("processed-auction-ctx-1", "some-ctx-1") + mc.Set("processed-auction-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + mc.Set("raw-auction-ctx-2", "some-ctx-2") + mc.Set("processed-auction-ctx-2", "some-ctx-2") + return mc + }(), }}, exec.moduleContexts, "Wrong module contexts after executing processed-auction hook.") // test that context added at the raw bidder response stage merged with existing module contexts reject = exec.ExecuteRawBidderResponseStage(&adapters.BidderResponse{}, "some-bidder") assert.Nil(t, reject, "Unexpected reject from raw-bidder-response stage.") - assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": { - "entrypoint-ctx-1": "some-ctx-1", - "entrypoint-ctx-3": "some-ctx-3", - "raw-auction-ctx-1": "some-ctx-1", - "raw-auction-ctx-3": "some-ctx-3", - "processed-auction-ctx-1": "some-ctx-1", - "processed-auction-ctx-3": "some-ctx-3", - "raw-bidder-response-ctx-1": "some-ctx-1", - "raw-bidder-response-ctx-3": "some-ctx-3", - }, - "module-2": { - "entrypoint-ctx-2": "some-ctx-2", - "raw-auction-ctx-2": "some-ctx-2", - "processed-auction-ctx-2": "some-ctx-2", - "raw-bidder-response-ctx-2": "some-ctx-2", - }, + assert.Equal(t, &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + mc.Set("raw-auction-ctx-1", "some-ctx-1") + mc.Set("raw-auction-ctx-3", "some-ctx-3") + mc.Set("processed-auction-ctx-1", "some-ctx-1") + mc.Set("processed-auction-ctx-3", "some-ctx-3") + mc.Set("raw-bidder-response-ctx-1", "some-ctx-1") + mc.Set("raw-bidder-response-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + mc.Set("raw-auction-ctx-2", "some-ctx-2") + mc.Set("processed-auction-ctx-2", "some-ctx-2") + mc.Set("raw-bidder-response-ctx-2", "some-ctx-2") + return mc + }(), }}, exec.moduleContexts, "Wrong module contexts after executing raw-bidder-response hook.") // test that context added at the auction-response stage merged with existing module contexts exec.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) assert.Nil(t, reject, "Unexpected reject from raw-auction stage.") - assert.Equal(t, &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ - "module-1": { - "entrypoint-ctx-1": "some-ctx-1", - "entrypoint-ctx-3": "some-ctx-3", - "raw-auction-ctx-1": "some-ctx-1", - "raw-auction-ctx-3": "some-ctx-3", - "processed-auction-ctx-1": "some-ctx-1", - "processed-auction-ctx-3": "some-ctx-3", - "raw-bidder-response-ctx-1": "some-ctx-1", - "raw-bidder-response-ctx-3": "some-ctx-3", - "auction-response-ctx-1": "some-ctx-1", - "auction-response-ctx-3": "some-ctx-3", - }, - "module-2": { - "entrypoint-ctx-2": "some-ctx-2", - "raw-auction-ctx-2": "some-ctx-2", - "processed-auction-ctx-2": "some-ctx-2", - "raw-bidder-response-ctx-2": "some-ctx-2", - "auction-response-ctx-2": "some-ctx-2", - }, + assert.Equal(t, &moduleContexts{ctxs: map[string]*hookstage.ModuleContext{ + "module-1": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-1", "some-ctx-1") + mc.Set("entrypoint-ctx-3", "some-ctx-3") + mc.Set("raw-auction-ctx-1", "some-ctx-1") + mc.Set("raw-auction-ctx-3", "some-ctx-3") + mc.Set("processed-auction-ctx-1", "some-ctx-1") + mc.Set("processed-auction-ctx-3", "some-ctx-3") + mc.Set("raw-bidder-response-ctx-1", "some-ctx-1") + mc.Set("raw-bidder-response-ctx-3", "some-ctx-3") + mc.Set("auction-response-ctx-1", "some-ctx-1") + mc.Set("auction-response-ctx-3", "some-ctx-3") + return mc + }(), + "module-2": func() *hookstage.ModuleContext { + mc := hookstage.NewModuleContext() + mc.Set("entrypoint-ctx-2", "some-ctx-2") + mc.Set("raw-auction-ctx-2", "some-ctx-2") + mc.Set("processed-auction-ctx-2", "some-ctx-2") + mc.Set("raw-bidder-response-ctx-2", "some-ctx-2") + mc.Set("auction-response-ctx-2", "some-ctx-2") + return mc + }(), }}, exec.moduleContexts, "Wrong module contexts after executing auction-response hook.") } diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index cb3c95b9d1f..a5857157d56 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -201,42 +201,50 @@ type mockModuleContextHook struct { } func (e mockModuleContextHook) HandleEntrypointHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.EntrypointPayload) (hookstage.HookResult[hookstage.EntrypointPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.EntrypointPayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleRawAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleProcessedAuctionHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.ProcessedAuctionRequestPayload) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleBidderRequestHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.BidderRequestPayload) (hookstage.HookResult[hookstage.BidderRequestPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.BidderRequestPayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleRawBidderResponseHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.RawBidderResponsePayload) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.RawBidderResponsePayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleAllProcessedBidResponsesHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.AllProcessedBidResponsesPayload) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleAuctionResponseHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.AuctionResponsePayload) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.AuctionResponsePayload]{ModuleContext: miCtx.ModuleContext}, nil } func (e mockModuleContextHook) HandleExitpointHook(_ context.Context, miCtx hookstage.ModuleInvocationContext, _ hookstage.ExitpointPayload) (hookstage.HookResult[hookstage.ExitpointPayload], error) { - miCtx.ModuleContext = map[string]interface{}{e.key: e.val} + miCtx.ModuleContext = hookstage.NewModuleContext() + miCtx.ModuleContext.Set(e.key, e.val) return hookstage.HookResult[hookstage.ExitpointPayload]{ModuleContext: miCtx.ModuleContext}, nil } diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index ee2f2ec89a6..b0b1c00089e 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -2,6 +2,8 @@ package hookstage import ( "encoding/json" + "maps" + "sync" "github.com/prebid/prebid-server/v4/hooks/hookanalytics" ) @@ -16,7 +18,7 @@ type HookResult[T any] struct { Warnings []string DebugMessages []string AnalyticsTags hookanalytics.Analytics - ModuleContext ModuleContext // holds values that the module wants to pass to itself at later stages + ModuleContext *ModuleContext // holds values that the module wants to pass to itself at later stages } // ModuleInvocationContext holds data passed to the module hook during invocation. @@ -28,11 +30,77 @@ type ModuleInvocationContext struct { // Endpoint represents the path of the current endpoint. Endpoint string // ModuleContext holds values that the module passes to itself from the previous stages. - ModuleContext ModuleContext + ModuleContext *ModuleContext // HookImplCode is the hook_impl_code for a module instance to differentiate between multiple hooks HookImplCode string } // ModuleContext holds arbitrary data passed between module hooks at different stages. // We use interface as we do not know exactly how the modules will use their inner context. -type ModuleContext map[string]interface{} +type ModuleContext struct { + sync.RWMutex + data map[string]any +} + +// NewModuleContext creates a new module context +func NewModuleContext() *ModuleContext { + moduleContext := ModuleContext{ + data: make(map[string]any), + } + return &moduleContext +} + +// Get retrieves a value from the module context with read lock +func (mc *ModuleContext) Get(key string) (any, bool) { + if mc == nil { + return nil, false + } + mc.RLock() + defer mc.RUnlock() + if mc.data == nil { + return nil, false + } + val, ok := mc.data[key] + return val, ok +} + +// Set stores a value in the module context with write lock +func (mc *ModuleContext) Set(key string, value any) { + if mc == nil { + return + } + mc.Lock() + defer mc.Unlock() + if mc.data == nil { + mc.data = make(map[string]any) + } + mc.data[key] = value +} + +// GetAll returns a copy of all data in the context +func (mc *ModuleContext) GetAll() map[string]any { + if mc == nil { + return nil + } + mc.RLock() + defer mc.RUnlock() + if mc.data == nil { + return nil + } + result := make(map[string]any, len(mc.data)) + maps.Copy(result, mc.data) + return result +} + +// SetAll merges the provided data into the module context +func (mc *ModuleContext) SetAll(data map[string]any) { + if mc == nil { + return + } + mc.Lock() + defer mc.Unlock() + if mc.data == nil { + mc.data = make(map[string]any, len(data)) + } + maps.Copy(mc.data, data) +} diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go index 511b85960b7..9599e43de15 100644 --- a/modules/fiftyonedegrees/devicedetection/evidence_extractor.go +++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go @@ -57,16 +57,18 @@ func merge(val1, val2 []stringEvidence) []stringEvidence { return evidence } -func (x *defaultEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { +func (x *defaultEvidenceExtractor) extract(ctx *hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { if ctx == nil { return nil, "", errors.New("context is nil") } - suaStrings, err := x.getEvidenceStrings(ctx[evidenceFromSuaCtxKey]) + evidenceFromSuaCtx, _ := ctx.Get(evidenceFromSuaCtxKey) + suaStrings, err := x.getEvidenceStrings(evidenceFromSuaCtx) if err != nil { return nil, "", fmt.Errorf("error extracting sua evidence: %w", err) } - headerString, err := x.getEvidenceStrings(ctx[evidenceFromHeadersCtxKey]) + evidenceFromHeadersCtx, _ := ctx.Get(evidenceFromHeadersCtxKey) + headerString, err := x.getEvidenceStrings(evidenceFromHeadersCtx) if err != nil { return nil, "", fmt.Errorf("error extracting header evidence: %w", err) } diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go index 7e2aa6616d8..8c183af559d 100644 --- a/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go +++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go @@ -129,7 +129,7 @@ func TestExtract(t *testing.T) { tests := []struct { name string - ctx hookstage.ModuleContext + ctx *hookstage.ModuleContext wantEvidenceCount int wantUserAgent string wantError bool @@ -141,96 +141,120 @@ func TestExtract(t *testing.T) { }, { name: "empty", - ctx: hookstage.ModuleContext{ - evidenceFromSuaCtxKey: []stringEvidence{}, - evidenceFromHeadersCtxKey: []stringEvidence{}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{}) + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{}) + return ctx + }(), wantEvidenceCount: 0, wantUserAgent: "", }, { name: "from_headers", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{uaEvidence1}) + return ctx + }(), wantEvidenceCount: 1, wantUserAgent: "uav1", }, { name: "from_headers_no_user_agent", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{evidence1}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{evidence1}) + return ctx + }(), wantError: true, }, { name: "from_sua", - ctx: hookstage.ModuleContext{ - evidenceFromSuaCtxKey: []stringEvidence{uaEvidence1}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{uaEvidence1}) + return ctx + }(), wantEvidenceCount: 1, wantUserAgent: "uav1", }, { name: "from_sua_no_user_agent", - ctx: hookstage.ModuleContext{ - evidenceFromSuaCtxKey: []stringEvidence{evidence1}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{evidence1}) + return ctx + }(), wantError: true, }, { name: "from_headers_error", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: "bad value", - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, "bad value") + return ctx + }(), wantError: true, }, { name: "from_sua_error", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{}, - evidenceFromSuaCtxKey: "bad value", - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{}) + ctx.Set(evidenceFromSuaCtxKey, "bad value") + return ctx + }(), wantError: true, }, { name: "from_sua_and_headers", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, - evidenceFromSuaCtxKey: []stringEvidence{evidence1}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{uaEvidence1}) + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{evidence1}) + return ctx + }(), wantEvidenceCount: 2, wantUserAgent: "uav1", }, { name: "from_sua_and_headers_sua_can_overwrite_if_ua_present", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, - evidenceFromSuaCtxKey: []stringEvidence{uaEvidence2}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{uaEvidence1}) + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{uaEvidence2}) + return ctx + }(), wantEvidenceCount: 1, wantUserAgent: "uav2", }, { name: "empty_string_values", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{emptyEvidence}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{emptyEvidence}) + return ctx + }(), wantError: true, }, { name: "empty_sua_values", - ctx: hookstage.ModuleContext{ - evidenceFromSuaCtxKey: []stringEvidence{emptyEvidence}, - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromSuaCtxKey, []stringEvidence{emptyEvidence}) + return ctx + }(), wantError: true, }, { name: "mixed_valid_and_invalid", - ctx: hookstage.ModuleContext{ - evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, - evidenceFromSuaCtxKey: "bad value", - }, + ctx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(evidenceFromHeadersCtxKey, []stringEvidence{uaEvidence1}) + ctx.Set(evidenceFromSuaCtxKey, "bad value") + return ctx + }(), wantError: true, }, } diff --git a/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go index f1429ef7e1e..996845a2f0c 100644 --- a/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go +++ b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go @@ -18,10 +18,10 @@ func handleAuctionEntryPointRequestHook(cfg config, payload hookstage.Entrypoint evidenceFromSua := evidenceExtractor.fromSuaPayload(payload.Body) // create a Module context and set the evidence from headers, evidence from sua and dd enabled flag - moduleContext := make(hookstage.ModuleContext) - moduleContext[evidenceFromHeadersCtxKey] = evidenceFromHeaders - moduleContext[evidenceFromSuaCtxKey] = evidenceFromSua - moduleContext[ddEnabledCtxKey] = true + moduleContext := hookstage.NewModuleContext() + moduleContext.Set(evidenceFromHeadersCtxKey, evidenceFromHeaders) + moduleContext.Set(evidenceFromSuaCtxKey, evidenceFromSua) + moduleContext.Set(ddEnabledCtxKey, true) return hookstage.HookResult[hookstage.EntrypointPayload]{ ModuleContext: moduleContext, diff --git a/modules/fiftyonedegrees/devicedetection/module.go b/modules/fiftyonedegrees/devicedetection/module.go index 72c0f60d94f..4dddc577f4b 100644 --- a/modules/fiftyonedegrees/devicedetection/module.go +++ b/modules/fiftyonedegrees/devicedetection/module.go @@ -83,7 +83,7 @@ type accountValidator interface { type evidenceExtractor interface { fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence fromSuaPayload(payload []byte) []stringEvidence - extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) + extract(ctx *hookstage.ModuleContext) ([]onpremise.Evidence, string, error) } func (m Module) HandleEntrypointHook( diff --git a/modules/fiftyonedegrees/devicedetection/module_test.go b/modules/fiftyonedegrees/devicedetection/module_test.go index 5566f8e25ac..7f757cd42d4 100644 --- a/modules/fiftyonedegrees/devicedetection/module_test.go +++ b/modules/fiftyonedegrees/devicedetection/module_test.go @@ -44,7 +44,7 @@ func (m *mockEvidenceExtractor) fromSuaPayload(payload []byte) []stringEvidence return args.Get(0).([]stringEvidence) } -func (m *mockEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { +func (m *mockEvidenceExtractor) extract(ctx *hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { args := m.Called(ctx) res := args.Get(0) @@ -131,16 +131,18 @@ func TestHandleEntrypointHookAccountAllowed(t *testing.T) { result, err := module.HandleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, hookstage.EntrypointPayload{}) assert.NoError(t, err) + evidenceFromHeadersCtx, _ := result.ModuleContext.Get(evidenceFromHeadersCtxKey) assert.Equal( - t, result.ModuleContext[evidenceFromHeadersCtxKey], []stringEvidence{{ + t, evidenceFromHeadersCtx, []stringEvidence{{ Prefix: "123", Key: "key", Value: "val", }}, ) + evidenceFromSuaCtx, _ := result.ModuleContext.Get(evidenceFromSuaCtxKey) assert.Equal( - t, result.ModuleContext[evidenceFromSuaCtxKey], []stringEvidence{{ + t, evidenceFromSuaCtx, []stringEvidence{{ Prefix: "123", Key: "User-Agent", Value: "ua", @@ -179,9 +181,8 @@ func TestHandleRawAuctionHookExtractError(t *testing.T) { accountValidator: &mockValidator, } - mctx := make(hookstage.ModuleContext) - - mctx[ddEnabledCtxKey] = true + mctx := hookstage.NewModuleContext() + mctx.Set(ddEnabledCtxKey, true) result, err := module.HandleRawAuctionHook( context.TODO(), hookstage.ModuleInvocationContext{ @@ -280,8 +281,8 @@ func TestHandleRawAuctionHookEnrichment(t *testing.T) { accountValidator: &mockValidator, } - mctx := make(hookstage.ModuleContext) - mctx[ddEnabledCtxKey] = true + mctx := hookstage.NewModuleContext() + mctx.Set(ddEnabledCtxKey, true) result, err := module.HandleRawAuctionHook( nil, hookstage.ModuleInvocationContext{ @@ -483,8 +484,8 @@ func TestHandleRawAuctionHookEnrichmentWithErrors(t *testing.T) { accountValidator: &mockValidator, } - mctx := make(hookstage.ModuleContext) - mctx[ddEnabledCtxKey] = true + mctx := hookstage.NewModuleContext() + mctx.Set(ddEnabledCtxKey, true) result, err := module.HandleRawAuctionHook( nil, hookstage.ModuleInvocationContext{ diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go index 14abbec43ab..9542777ae08 100644 --- a/modules/prebid/ortb2blocking/hook_bidderrequest.go +++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go @@ -47,7 +47,10 @@ func handleBidderRequestHook( updateCatTax(cfg, payload, &blockingAttributes, &changeSet) result.ChangeSet = changeSet - result.ModuleContext = hookstage.ModuleContext{payload.Bidder: blockingAttributes} + if result.ModuleContext == nil { + result.ModuleContext = hookstage.NewModuleContext() + } + result.ModuleContext.Set(payload.Bidder, blockingAttributes) return result, nil } diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index deeb3b02967..cbdae937352 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -15,10 +15,10 @@ import ( func handleRawBidderResponseHook( cfg config, payload hookstage.RawBidderResponsePayload, - moduleCtx hookstage.ModuleContext, + moduleCtx *hookstage.ModuleContext, ) (result hookstage.HookResult[hookstage.RawBidderResponsePayload], err error) { bidder := payload.Bidder - blockAttrsVal, ok := moduleCtx[bidder] + blockAttrsVal, ok := moduleCtx.Get(bidder) if !ok { // if there are no blocking attributes for this bidder just pass empty blockingAttributes for further processing // other values from config must still be checked diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index 7d002d29c8e..85be537bb6c 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -343,8 +343,9 @@ func TestHandleBidderRequestHook(t *testing.T) { }, }, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{ - ModuleContext: map[string]interface{}{ - bidder: blockingAttributes{ + ModuleContext: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{ bAdv: []string{bAdvA, bAdvB}, bApp: []string{bApp3}, bCat: []string{bCat1, bCat2, bCat3, bCat4}, @@ -357,8 +358,9 @@ func TestHandleBidderRequestHook(t *testing.T) { "ImpID2": toInt([]adcom1.CreativeAttribute{bAttr1, bAttr8, bAttr9, bAttr10}), }, catTax: catTax, - }, - }, + }) + return mctx + }(), Warnings: []string{ // multiple warnings may be added (per condition) "More than one condition matches request. Bidder: appnexus, request media types: audio, banner, native, video", @@ -389,12 +391,14 @@ func TestHandleBidderRequestHook(t *testing.T) { }, }, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{ - ModuleContext: map[string]interface{}{ - bidder: blockingAttributes{ + ModuleContext: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{ bAdv: []string{bAdvA, bAdvB, bAdvC}, bAttr: map[string][]int{}, - }, - }, + }) + return mctx + }(), }, expectedError: nil, }, @@ -439,9 +443,13 @@ func TestHandleBidderRequestHook(t *testing.T) { }, }, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{ - ModuleContext: map[string]interface{}{bidder: blockingAttributes{ - bAttr: map[string][]int{}, - }}, + ModuleContext: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{ + bAttr: map[string][]int{}, + }) + return mctx + }(), }, expectedError: nil, }, @@ -459,9 +467,13 @@ func TestHandleBidderRequestHook(t *testing.T) { Imp: []openrtb2.Imp{{ID: "ImpID1", Video: &openrtb2.Video{}}}, }, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{ - ModuleContext: map[string]interface{}{bidder: blockingAttributes{ - bAttr: map[string][]int{}, - }}, + ModuleContext: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{ + bAttr: map[string][]int{}, + }) + return mctx + }(), }, expectedError: nil, }, @@ -579,7 +591,7 @@ func TestHandleBidderRequestHook(t *testing.T) { hookstage.ModuleInvocationContext{ AccountConfig: test.config, Endpoint: hookexecution.EndpointAuction, - ModuleContext: map[string]interface{}{}, + ModuleContext: hookstage.NewModuleContext(), }, payload, ) @@ -605,7 +617,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { description string payload hookstage.RawBidderResponsePayload config json.RawMessage - moduleCtx hookstage.ModuleContext + moduleCtx *hookstage.ModuleContext expectedBids []*adapters.TypedBid expectedHookResult hookstage.HookResult[hookstage.RawBidderResponsePayload] expectedError error @@ -655,7 +667,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, }, }, - moduleCtx: map[string]interface{}{bidder: "boo"}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, "boo") + return mctx + }(), expectedError: hookexecution.NewFailure("could not cast blocking attributes for bidder `appnexus`, module context has incorrect data"), }, { @@ -676,7 +692,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bAdv: []string{"forbidden_domain"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -724,7 +744,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{"other-bidder": blockingAttributes{bAdv: []string{"forbidden_domain"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set("other-bidder", blockingAttributes{bAdv: []string{"forbidden_domain"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -767,7 +791,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bAdv: []string{"forbidden_domain"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -810,7 +838,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bAdv: []string{"forbidden_domain"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -850,7 +882,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -898,7 +934,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -941,7 +981,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bAdv: []string{"forbidden_domain"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -1062,7 +1106,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bCat: []string{"fishing"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bCat: []string{"fishing"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -1188,7 +1236,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{catTax: 2}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{catTax: 2}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -1277,7 +1329,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bApp: []string{"forbidden_bundle"}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bApp: []string{"forbidden_bundle"}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -1376,7 +1432,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAttr: map[string][]int{impID1: {1}}}}, + moduleCtx: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set(bidder, blockingAttributes{bAttr: map[string][]int{impID1: {1}}}) + return mctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ @@ -1499,13 +1559,17 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, }, }, - moduleCtx: map[string]interface{}{bidder: blockingAttributes{ - bAdv: []string{"forbidden_domain"}, - bCat: []string{"fishing"}, - catTax: 2, - bApp: []string{"forbidden_bundle"}, - bAttr: map[string][]int{impID1: {1}}}, - }, + moduleCtx: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(bidder, blockingAttributes{ + bAdv: []string{"forbidden_domain"}, + bCat: []string{"fishing"}, + catTax: 2, + bApp: []string{"forbidden_bundle"}, + bAttr: map[string][]int{impID1: {1}}, + }) + return ctx + }(), expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{ AnalyticsTags: hookanalytics.Analytics{ Activities: []hookanalytics.Activity{ diff --git a/modules/scientiamobile/wurfl_devicedetection/module.go b/modules/scientiamobile/wurfl_devicedetection/module.go index 53bb26e74d9..7cbf8d3a6db 100644 --- a/modules/scientiamobile/wurfl_devicedetection/module.go +++ b/modules/scientiamobile/wurfl_devicedetection/module.go @@ -75,8 +75,8 @@ func (m Module) HandleEntrypointHook(ctx context.Context, invocationCtx hookstag header[k] = payload.Request.Header.Get(k) } } - moduleContext := make(hookstage.ModuleContext) - moduleContext[wurflHeaderCtxKey] = header + moduleContext := hookstage.NewModuleContext() + moduleContext.Set(wurflHeaderCtxKey, header) result.ModuleContext = moduleContext return result, nil @@ -101,7 +101,11 @@ func (m Module) HandleRawAuctionHook( return result, nil } - rawHeaders, ok := invocationCtx.ModuleContext[wurflHeaderCtxKey].(map[string]string) + headers, ok := invocationCtx.ModuleContext.Get(wurflHeaderCtxKey) + if !ok { + return result, hookexecution.NewFailure("invalid module context type") + } + rawHeaders, ok := headers.(map[string]string) if !ok { return result, hookexecution.NewFailure("invalid module context type") } diff --git a/modules/scientiamobile/wurfl_devicedetection/module_test.go b/modules/scientiamobile/wurfl_devicedetection/module_test.go index 64aaefe7fde..8dc0ee48ee7 100644 --- a/modules/scientiamobile/wurfl_devicedetection/module_test.go +++ b/modules/scientiamobile/wurfl_devicedetection/module_test.go @@ -150,7 +150,8 @@ func TestHandleEntrypointHook(t *testing.T) { } else { assert.NoError(t, err) assert.NotNil(t, result.ModuleContext) - assert.Equal(t, tc.expectedModuleCtx[wurflHeaderCtxKey], result.ModuleContext[wurflHeaderCtxKey]) + headers, _ := result.ModuleContext.Get(wurflHeaderCtxKey) + assert.Equal(t, tc.expectedModuleCtx[wurflHeaderCtxKey], headers) } }) } @@ -184,11 +185,13 @@ func TestHandleRawAuctionHook(t *testing.T) { extCaps: false, }, invocationCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - wurflHeaderCtxKey: map[string]string{ + ModuleContext: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(wurflHeaderCtxKey, map[string]string{ "User-Agent": "Mozilla/5.0", - }, - }, + }) + return ctx + }(), }, payload: []byte(`{"device":{"ua":"Mozilla/5.0"}}`), expectedErr: false, @@ -242,11 +245,13 @@ func TestHandleRawAuctionHook(t *testing.T) { extCaps: true, }, invocationCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - wurflHeaderCtxKey: map[string]string{ + ModuleContext: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(wurflHeaderCtxKey, map[string]string{ "User-Agent": "Mozilla/5.0", - }, - }, + }) + return ctx + }(), }, payload: []byte(`{"device":{"ua":"Mozilla/5.0"}}`), expectedErr: false, @@ -288,11 +293,13 @@ func TestHandleRawAuctionHook(t *testing.T) { extCaps: true, }, invocationCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - wurflHeaderCtxKey: map[string]string{ + ModuleContext: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(wurflHeaderCtxKey, map[string]string{ "User-Agent": "Mozilla/5.0", - }, - }, + }) + return ctx + }(), }, payload: []byte(`{"device":{"ua":"Mozilla/5.0", "ext": {"test": 1}}}`), expectedErr: false, @@ -328,11 +335,13 @@ func TestHandleRawAuctionHook(t *testing.T) { extCaps: false, }, invocationCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - wurflHeaderCtxKey: map[string]string{ + ModuleContext: func() *hookstage.ModuleContext { + ctx := hookstage.NewModuleContext() + ctx.Set(wurflHeaderCtxKey, map[string]string{ "User-Agent": "Mozilla/5.0", - }, - }, + }) + return ctx + }(), }, payload: []byte(`{"device":{"ua":"Mozilla/5.0"}}`), expectedErr: false, diff --git a/modules/scope3/rtd/module.go b/modules/scope3/rtd/module.go index ab4ac3b4595..9ad4e617029 100644 --- a/modules/scope3/rtd/module.go +++ b/modules/scope3/rtd/module.go @@ -186,11 +186,11 @@ func (m *Module) HandleEntrypointHook( miCtx hookstage.ModuleInvocationContext, payload hookstage.EntrypointPayload, ) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + moduleContext := hookstage.NewModuleContext() + moduleContext.Set(asyncRequestKey, m.NewAsyncRequest(payload.Request)) // Initialize module context with sync.Map for thread-safe segment storage return hookstage.HookResult[hookstage.EntrypointPayload]{ - ModuleContext: hookstage.ModuleContext{ - asyncRequestKey: m.NewAsyncRequest(payload.Request), - }, + ModuleContext: moduleContext, }, nil } @@ -203,7 +203,23 @@ func (m *Module) HandleProcessedAuctionHook( var ret hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload] analyticsNamePrefix := "HandleProcessedAuctionHook." - asyncRequest, ok := miCtx.ModuleContext[asyncRequestKey].(*AsyncRequest) + request, ok := miCtx.ModuleContext.Get(asyncRequestKey) + if !ok { + // Log error but don't fail the auction + ret.AnalyticsTags = hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{{ + Name: analyticsNamePrefix + asyncRequestKey, + Status: hookanalytics.ActivityStatusError, + Results: []hookanalytics.Result{{ + Status: hookanalytics.ResultStatusError, + Values: map[string]interface{}{"error": "failed to get async request from module context"}, + }}, + }}, + } + return ret, nil + } + + asyncRequest, ok := request.(*AsyncRequest) if !ok { // Log error but don't fail the auction ret.AnalyticsTags = hookanalytics.Analytics{ @@ -233,7 +249,24 @@ func (m *Module) HandleAuctionResponseHook( ) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) { analyticsNamePrefix := "HandleAuctionResponseHook." var ret hookstage.HookResult[hookstage.AuctionResponsePayload] - asyncRequest, ok := miCtx.ModuleContext[asyncRequestKey].(*AsyncRequest) + + request, ok := miCtx.ModuleContext.Get(asyncRequestKey) + if !ok { + // Log error but don't fail the auction + ret.AnalyticsTags = hookanalytics.Analytics{ + Activities: []hookanalytics.Activity{{ + Name: analyticsNamePrefix + asyncRequestKey, + Status: hookanalytics.ActivityStatusError, + Results: []hookanalytics.Result{{ + Status: hookanalytics.ResultStatusError, + Values: map[string]interface{}{"error": "failed to get async request from module context"}, + }}, + }}, + } + return ret, nil + } + + asyncRequest, ok := request.(*AsyncRequest) if !ok { // Log error but don't fail the auction ret.AnalyticsTags = hookanalytics.Analytics{ diff --git a/modules/scope3/rtd/module_test.go b/modules/scope3/rtd/module_test.go index a9339d32425..6b40353cf07 100644 --- a/modules/scope3/rtd/module_test.go +++ b/modules/scope3/rtd/module_test.go @@ -83,16 +83,19 @@ func TestHandleEntrypointHook(t *testing.T) { result, err := module.HandleEntrypointHook(ctx, miCtx, payload) assert.NoError(t, err) - assert.NotNil(t, result.ModuleContext[asyncRequestKey]) + asyncRequest, _ := result.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) } func TestHandleAuctionResponseHook_NoSegments(t *testing.T) { module := &Module{} ctx := context.Background() miCtx := hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - "segments": &sync.Map{}, - }, + ModuleContext: func() *hookstage.ModuleContext { + mctx := hookstage.NewModuleContext() + mctx.Set("segments", &sync.Map{}) + return mctx + }(), } payload := hookstage.AuctionResponsePayload{} @@ -256,7 +259,8 @@ func TestScope3APIIntegrationNoSegments(t *testing.T) { // Test entrypoint hook entrypointResult, err := module.HandleEntrypointHook(ctx, hookstage.ModuleInvocationContext{}, getTestEntrypointPayload(t)) require.NoError(t, err) - assert.NotNil(t, entrypointResult.ModuleContext[asyncRequestKey]) + asyncRequest, _ := entrypointResult.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) payload := hookstage.ProcessedAuctionRequestPayload{ Request: &openrtb_ext.RequestWrapper{ @@ -359,7 +363,8 @@ func TestScope3APIIntegrationWithTargeting(t *testing.T) { // Test entrypoint hook entrypointResult, err := module.HandleEntrypointHook(ctx, hookstage.ModuleInvocationContext{}, getTestEntrypointPayload(t)) require.NoError(t, err) - assert.NotNil(t, entrypointResult.ModuleContext[asyncRequestKey]) + asyncRequest, _ := entrypointResult.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) // Create test request payload width := int64(300) @@ -541,7 +546,8 @@ func TestScope3APIIntegrationWithTargetingNoScope3Section(t *testing.T) { // Test entrypoint hook entrypointResult, err := module.HandleEntrypointHook(ctx, hookstage.ModuleInvocationContext{}, getTestEntrypointPayload(t)) require.NoError(t, err) - assert.NotNil(t, entrypointResult.ModuleContext[asyncRequestKey]) + asyncRequest, _ := entrypointResult.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) // Create test request payload width := int64(300) @@ -684,7 +690,8 @@ func TestScope3APIIntegrationWithExistingPrebidTargeting(t *testing.T) { // Test entrypoint hook entrypointResult, err := module.HandleEntrypointHook(ctx, hookstage.ModuleInvocationContext{}, getTestEntrypointPayload(t)) require.NoError(t, err) - assert.NotNil(t, entrypointResult.ModuleContext[asyncRequestKey]) + asyncRequest, _ := entrypointResult.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) // Create test request payload width := int64(300) @@ -794,7 +801,8 @@ func TestScope3APIIntegrationWithExistingPrebidNoTargeting(t *testing.T) { // Test entrypoint hook entrypointResult, err := module.HandleEntrypointHook(ctx, hookstage.ModuleInvocationContext{}, getTestEntrypointPayload(t)) require.NoError(t, err) - assert.NotNil(t, entrypointResult.ModuleContext[asyncRequestKey]) + asyncRequest, _ := entrypointResult.ModuleContext.Get(asyncRequestKey) + assert.NotNil(t, asyncRequest) // Create test request payload width := int64(300)