diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 39b1e945f7d..feb866e6b83 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -162,6 +162,7 @@ func getTestExtraRequestInfo(t *testing.T, filename string, spec *testSpec, isAm } else if isVideoTest { reqInfo.PbsEntryPoint = "video" } + reqInfo = updateRequestInfoForOW(reqInfo, filename) // OW specific : required for oRTB bidder return &reqInfo } diff --git a/adapters/adapterstest/test_json_ow.go b/adapters/adapterstest/test_json_ow.go new file mode 100644 index 00000000000..cd4790ce13e --- /dev/null +++ b/adapters/adapterstest/test_json_ow.go @@ -0,0 +1,20 @@ +package adapterstest + +import ( + "strings" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// updateRequestInfoForOW updates the reqInfo as per OW requirement +func updateRequestInfoForOW(reqInfo adapters.ExtraRequestInfo, fileName string) adapters.ExtraRequestInfo { + // for oRTB bidders (having prefix as 'ortb_', set the bidderName from file) + if strings.HasPrefix(fileName, "ortb_") { + files := strings.Split(fileName, "/") + if len(files) > 0 { + reqInfo.BidderCoreName = openrtb_ext.BidderName(files[0]) + } + } + return reqInfo +} diff --git a/adapters/bidder.go b/adapters/bidder.go index 8bb1b5b4e13..5ed8cd2ea15 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -156,6 +156,8 @@ type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string CurrencyConversions currency.Conversions + + BidderCoreName openrtb_ext.BidderName // OW specific: required for oRTB bidder } func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { diff --git a/adapters/ortbbidder/ortb_test_multi_requestmode/exemplary/multi_imps_video.json b/adapters/ortbbidder/ortb_test_multi_requestmode/exemplary/multi_imps_video.json new file mode 100644 index 00000000000..5bb92ef28c4 --- /dev/null +++ b/adapters/ortbbidder/ortb_test_multi_requestmode/exemplary/multi_imps_video.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_1", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_1": "value_1" + } + } + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_2", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_2": "value_2" + } + } + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test_bidder.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_1", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_1": "value_1" + } + } + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_2", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_2": "value_2" + } + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "magnite", + "bid": [{ + "id": "bid-1", + "impid": "test-imp-id-1", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 2 + },{ + "id": "bid-2", + "impid": "test-imp-id-2", + "price": 10, + "adm": "some-test-ad-2", + "crid": "crid_10", + "mtype": 2 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid-1", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype":2 + }, + "type": "video" + }, + { + "bid": { + "id": "bid-2", + "impid": "test-imp-id-2", + "price": 10, + "adm": "some-test-ad-2", + "crid": "crid_10", + "mtype":2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/ortbbidder/ortb_test_single_requestmode/exemplary/multi_imps_video.json b/adapters/ortbbidder/ortb_test_single_requestmode/exemplary/multi_imps_video.json new file mode 100644 index 00000000000..efb1cb93bf1 --- /dev/null +++ b/adapters/ortbbidder/ortb_test_single_requestmode/exemplary/multi_imps_video.json @@ -0,0 +1,166 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_1", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_1": "value_1" + } + } + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_2", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_2": "value_2" + } + } + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test_bidder.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_1", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_1": "value_1" + } + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "magnite", + "bid": [{ + "id": "bid-1", + "impid": "test-imp-id-1", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 2 + }] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://test_bidder.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-2", + "video": { + "mimes": ["video/mp4"] + }, + "tagid": "tagid_2", + "ext": { + "prebid": { + "bidder": { + "magnite": { + "bidder_param_2": "value_2" + } + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "magnite", + "bid": [{ + "id": "bid-2", + "impid": "test-imp-id-2", + "price": 10, + "adm": "some-test-ad-2", + "crid": "crid_10", + "mtype": 2 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid-1", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype":2 + }, + "type": "video" + } + ] + }, + { + "bids": [ + { + "bid": { + "id": "bid-2", + "impid": "test-imp-id-2", + "price": 10, + "adm": "some-test-ad-2", + "crid": "crid_10", + "mtype":2 + }, + "type": "video" + } + ] + } + ] + } diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go new file mode 100644 index 00000000000..8dfc88bd1fc --- /dev/null +++ b/adapters/ortbbidder/ortbbidder.go @@ -0,0 +1,153 @@ +package ortbbidder + +import ( + "encoding/json" + "fmt" + "net/http" + "sync" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// oRTBAdapter implements adapters.Bidder interface +// Note: Single instance of oRTBAdapter can run concurrently for multiple oRTB bidders; +// hence, do not store any bidder-specific data in this structure. +type oRTBAdapter struct { + BidderInfo config.BidderInfos +} + +var ortbAdapter *oRTBAdapter +var once sync.Once + +// InitORTBAdapter initialises the instance of oRTBAdapter +func InitORTBAdapter(infos config.BidderInfos) { + once.Do(func() { + ortbAdapter = &oRTBAdapter{ + BidderInfo: infos, + } + }) +} + +// Builder returns an instance of oRTB adapter initialised by InitORTBAdapter, +// it returns an error if InitORTBAdapter is not called yet. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + if ortbAdapter == nil { + return nil, fmt.Errorf("oRTB bidder is not initialised") + } + return ortbAdapter, nil +} + +// MakeRequests prepares oRTB bidder-specific request information using which prebid server make call(s) to bidder. +func (o *oRTBAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if request == nil || requestInfo == nil { + return nil, []error{fmt.Errorf("Found either nil request or nil requestInfo")} + } + // fetch bidder specific info from bidder-info.yaml + bidderInfo, found := o.BidderInfo[string(requestInfo.BidderCoreName)] + if !found { + return nil, []error{fmt.Errorf("bidder-info not found for bidder-[%s]", requestInfo.BidderCoreName)} + } + + // bidder request supports single impression in single HTTP call. + if bidderInfo.OpenWrap.RequestMode == config.RequestModeSingle { + requestData := make([]*adapters.RequestData, 0, len(request.Imp)) + requestCopy := *request + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} // requestCopy contains single impression + reqData, err := prepareRequestData(&requestCopy, bidderInfo.Endpoint) + if err != nil { + return nil, []error{err} + } + requestData = append(requestData, reqData) + } + return requestData, nil + } + + // bidder request supports multi impressions in single HTTP call. + requestData, err := prepareRequestData(request, bidderInfo.Endpoint) + if err != nil { + return nil, []error{err} + } + return []*adapters.RequestData{requestData}, nil +} + +// prepareRequestData generates the RequestData by marshalling the request and returns it +func prepareRequestData(request *openrtb2.BidRequest, endpoint string) (*adapters.RequestData, error) { + if request == nil { + return nil, fmt.Errorf("found nil request") + } + body, err := json.Marshal(request) + if err != nil { + return nil, err + } + return &adapters.RequestData{ + Method: http.MethodPost, + Uri: endpoint, + Body: body, + }, nil +} + +// MakeBids prepares bidderResponse from the oRTB bidder server's http.Response +func (o *oRTBAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData == nil || adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + // initialise bidResponse with zero bids + bidResponse := adapters.BidderResponse{ + Bids: make([]*adapters.TypedBid, 0), + } + var errs []error + for _, seatBid := range response.SeatBid { + for bidInd, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[bidInd], + BidType: bidType, + }) + } + } + return &bidResponse, errs +} + +// getMediaTypeForBid returns the BidType as per the bid.MType field +// bidExt.Prebid.Type has high priority over bid.MType +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + var bidType openrtb_ext.BidType + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) + } + } + switch bid.MType { + case openrtb2.MarkupBanner: + bidType = openrtb_ext.BidTypeBanner + case openrtb2.MarkupVideo: + bidType = openrtb_ext.BidTypeVideo + case openrtb2.MarkupAudio: + bidType = openrtb_ext.BidTypeAudio + case openrtb2.MarkupNative: + bidType = openrtb_ext.BidTypeNative + default: + return bidType, fmt.Errorf("Failed to parse bid mType for bidID \"%s\"", bid.ID) + } + return bidType, nil +} diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go new file mode 100644 index 00000000000..038147b82aa --- /dev/null +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -0,0 +1,457 @@ +package ortbbidder + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestPrepareRequestData(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + endpoint string + } + type want struct { + requestData *adapters.RequestData + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "Valid_Request", + args: args{ + request: &openrtb2.BidRequest{ + ID: "123", + Imp: []openrtb2.Imp{{ID: "imp1"}}, + }, + endpoint: "https://example.com", + }, + want: want{ + requestData: &adapters.RequestData{ + Method: http.MethodPost, + Uri: "https://example.com", + Body: []byte(`{"id":"123","imp":[{"id":"imp1"}]}`), + }, + err: nil, + }, + }, + { + name: "Nil_Request", + args: args{ + request: nil, + endpoint: "https://example.com", + }, + want: want{ + requestData: nil, + err: fmt.Errorf("found nil request"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reqData, err := prepareRequestData(tt.args.request, tt.args.endpoint) + assert.Equal(t, reqData, tt.want.requestData, "mismatched requestData") + assert.Equal(t, err, tt.want.err, "mismatched error") + }) + } +} + +func TestBuilder(t *testing.T) { + originalAdapter := ortbAdapter + defer func() { + ortbAdapter = originalAdapter + }() + type args struct { + bidderName openrtb_ext.BidderName + config config.Adapter + server config.Server + } + tests := []struct { + name string + args args + want adapters.Bidder + wantErr error + setup func() + }{ + { + name: "ortbBidder_is_nil", + args: args{}, + want: nil, + wantErr: fmt.Errorf("oRTB bidder is not initialised"), + setup: func() { + ortbAdapter = nil + }, + }, + { + name: "ortbBidder_is_not_nil", + args: args{}, + want: &oRTBAdapter{}, + wantErr: nil, + setup: func() { + ortbAdapter = &oRTBAdapter{} + }, + }, + } + for _, tt := range tests { + tt.setup() + got, err := Builder(tt.args.bidderName, tt.args.config, tt.args.server) + assert.Equal(t, got, tt.want, "mismatched adapter for %v", tt.name) + assert.Equal(t, err, tt.wantErr, "mismatched error for %v", tt.name) + } +} + +func TestMakeRequests(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + requestInfo *adapters.ExtraRequestInfo + } + type want struct { + requestData []*adapters.RequestData + errors []error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "request_is_nil", + args: args{}, + want: want{ + errors: []error{fmt.Errorf("Found either nil request or nil requestInfo")}, + }, + }, + { + name: "requestInfo_is_nil", + args: args{}, + want: want{ + errors: []error{fmt.Errorf("Found either nil request or nil requestInfo")}, + }, + }, + { + name: "bidderInfo_absent", + args: args{ + request: &openrtb2.BidRequest{}, + requestInfo: &adapters.ExtraRequestInfo{ + BidderCoreName: "xyz", + }, + }, + want: want{ + errors: []error{fmt.Errorf("bidder-info not found for bidder-[xyz]")}, + }, + }, + { + name: "multi_requestmode_to_form_requestdata", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + requestInfo: &adapters.ExtraRequestInfo{ + BidderCoreName: openrtb_ext.BidderName("ortb_test_multi_requestmode"), + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + }, + }, + }, + }, + { + name: "single_requestmode_to_form_requestdata", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + requestInfo: &adapters.ExtraRequestInfo{ + BidderCoreName: openrtb_ext.BidderName("ortb_test_single_requestmode"), + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), + }, + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapter := &oRTBAdapter{ + BidderInfo: getBidderInfos(), + } + requestData, errors := adapter.MakeRequests(tt.args.request, tt.args.requestInfo) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errors, errors, "mismatched errors") + }) + } +} + +func TestMakeBids(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + requestData *adapters.RequestData + responseData *adapters.ResponseData + } + type want struct { + response *adapters.BidderResponse + errors []error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "responseData_is_nil", + args: args{ + responseData: nil, + }, + want: want{ + response: nil, + errors: nil, + }, + }, + { + name: "StatusNoContent_in_responseData", + args: args{ + responseData: &adapters.ResponseData{StatusCode: http.StatusNoContent}, + }, + want: want{ + response: nil, + errors: nil, + }, + }, + { + name: "StatusBadRequest_in_responseData", + args: args{ + responseData: &adapters.ResponseData{StatusCode: http.StatusBadRequest}, + }, + want: want{ + response: nil, + errors: []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", http.StatusBadRequest), + }}, + }, + }, + { + name: "getMediaTypeForBid_returns_error", + args: args{ + responseData: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id":"bid-resp-id","seatbid":[{"seat":"test_bidder","bid":[{"id":"bid-1","mtype":2},{"id":"bid-2","mtype":5}]}]}`), + }, + }, + want: want{ + response: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid-1", + MType: 2, + }, + BidType: "video", + }, + }, + }, + errors: []error{fmt.Errorf("Failed to parse bid mType for bidID \"bid-2\"")}, + }, + }, + { + name: "valid_response", + args: args{ + responseData: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id":"bid-resp-id","seatbid":[{"seat":"test_bidder","bid":[{"id":"bid-1","mtype":2}]}]}`), + }, + }, + want: want{ + response: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "bid-1", + MType: 2, + }, + BidType: "video", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapter := &oRTBAdapter{ + BidderInfo: getBidderInfos(), + } + response, errs := adapter.MakeBids(tt.args.request, tt.args.requestData, tt.args.responseData) + assert.Equalf(t, tt.want.response, response, "mismatched response") + assert.Equalf(t, tt.want.errors, errs, "mismatched errors") + }) + } +} + +func TestGetMediaTypeForBid(t *testing.T) { + type args struct { + bid openrtb2.Bid + } + type want struct { + bidType openrtb_ext.BidType + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "Valid_Banner_Bid", + args: args{ + bid: openrtb2.Bid{ID: "1", MType: openrtb2.MarkupBanner}, + }, + want: want{ + bidType: openrtb_ext.BidTypeBanner, + err: nil, + }, + }, + { + name: "Valid_Video_Bid", + args: args{ + bid: openrtb2.Bid{ID: "2", MType: openrtb2.MarkupVideo}, + }, + want: want{ + bidType: openrtb_ext.BidTypeVideo, + err: nil, + }, + }, + { + name: "Valid_Audio_Bid", + args: args{ + bid: openrtb2.Bid{ID: "3", MType: openrtb2.MarkupAudio}, + }, + want: want{ + bidType: openrtb_ext.BidTypeAudio, + err: nil, + }, + }, + { + name: "Valid_Native_Bid", + args: args{ + bid: openrtb2.Bid{ID: "4", MType: openrtb2.MarkupNative}, + }, + want: want{ + bidType: openrtb_ext.BidTypeNative, + err: nil, + }, + }, + { + name: "Invalid_Bid_Type", + args: args{ + bid: openrtb2.Bid{ID: "5", MType: 123}, + }, + want: want{ + bidType: "", + err: fmt.Errorf("Failed to parse bid mType for bidID \"5\""), + }, + }, + { + name: "bidExt.prebid.type_has_high_priority", + args: args{ + bid: openrtb2.Bid{ID: "5", MType: openrtb2.MarkupVideo, Ext: json.RawMessage(`{"prebid":{"type":"video"}}`)}, + }, + want: want{ + bidType: "video", + err: nil, + }, + }, + { + name: "bidExt.prebid_is_missing_fallback_to_bid.mtype", + args: args{ + bid: openrtb2.Bid{ID: "5", MType: openrtb2.MarkupVideo, Ext: json.RawMessage(`{}`)}, + }, + want: want{ + bidType: "video", + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bidType, err := getMediaTypeForBid(tt.args.bid) + assert.Equal(t, tt.want.bidType, bidType, "mismatched bidType") + assert.Equal(t, tt.want.err, err, "mismatched error") + }) + } +} + +func TestJsonSamplesForTestBidder(t *testing.T) { + originalAdapter := ortbAdapter + defer func() { + ortbAdapter = originalAdapter + }() + ortbAdapter = &oRTBAdapter{ + BidderInfo: getBidderInfos(), + } + bidder, buildErr := Builder("", config.Adapter{}, config.Server{}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "ortb_test_single_requestmode", bidder) + adapterstest.RunJSONBidderTest(t, "ortb_test_multi_requestmode", bidder) +} + +func getBidderInfos() config.BidderInfos { + return config.BidderInfos{ + "ortb_test_single_requestmode": config.BidderInfo{ + Endpoint: "http://test_bidder.com", + OpenWrap: config.OpenWrap{ + RequestMode: "single", + }, + }, + "ortb_test_multi_requestmode": config.BidderInfo{ + Endpoint: "http://test_bidder.com", + OpenWrap: config.OpenWrap{ + RequestMode: "multi", + }, + }, + } +} + +func TestInitORTBAdapter(t *testing.T) { + t.Run("init_ortb_adapter", func(t *testing.T) { + InitORTBAdapter(config.BidderInfos{}) + assert.NotNilf(t, ortbAdapter, "ortbAdapter should not be nil") + }) +} diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 559038c0ffe..65e24153a88 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -50,6 +50,7 @@ type BidderInfo struct { // EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server EndpointCompression string `yaml:"endpointCompression" mapstructure:"endpointCompression"` OpenRTB *OpenRTBInfo `yaml:"openrtb" mapstructure:"openrtb"` + OpenWrap OpenWrap `yaml:"openwrap" mapstructure:"openwrap"` } type aliasNillableFields struct { diff --git a/config/bidderinfo_ow.go b/config/bidderinfo_ow.go new file mode 100644 index 00000000000..e696b38382a --- /dev/null +++ b/config/bidderinfo_ow.go @@ -0,0 +1,15 @@ +package config + +type RequestMode string + +const ( + RequestModeSingle RequestMode = "single" +) + +// OpenWrap stores the openwrap specific bidder-info configuration +type OpenWrap struct { + // requestMode specifies whether bidder supports single/multi impressions per HTTP request + // requestMode="single" means bidder supports only one impression per request + // if this parameter is not specified then we assumes that bidder supports multi impression + RequestMode RequestMode `yaml:"requestMode" mapstructure:"requestMode"` +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 6c6c2e106ce..052bfb4fbf6 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -131,6 +131,7 @@ import ( "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/operaads" "github.com/prebid/prebid-server/adapters/orbidder" + "github.com/prebid/prebid-server/adapters/ortbbidder" "github.com/prebid/prebid-server/adapters/outbrain" "github.com/prebid/prebid-server/adapters/ownadx" "github.com/prebid/prebid-server/adapters/pangle" @@ -339,6 +340,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOperaads: operaads.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, + openrtb_ext.BidderORTBMagnite: ortbbidder.Builder, // OW specific : oRTB bidder for magnite openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderOwnAdx: ownadx.Builder, openrtb_ext.BidderPangle: pangle.Builder, diff --git a/exchange/exchange.go b/exchange/exchange.go index 5ff5c25aea7..63fbe7ecd57 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -746,6 +746,7 @@ func (e *exchange) getAllBids( reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader + reqInfo.BidderCoreName = bidderRequest.BidderCoreName // OW specific: required for oRTB bidder bidReqOptions := bidRequestOptions{ accountDebugAllowed: accountDebugAllowed, diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 747735f5284..dc0b835c8e7 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -22,6 +22,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/ortbbidder" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -70,6 +71,7 @@ func TestNewExchange(t *testing.T) { t.Fatal(err) } + ortbbidder.InitORTBAdapter(biddersInfo) // OW specific: required for oRTB bidder adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) diff --git a/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go b/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go index 7a590cd54ca..8f8a378f444 100644 --- a/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go +++ b/modules/pubmatic/openwrap/adapters/default_bidder_parameter_test.go @@ -111,7 +111,7 @@ func TestGetType(t *testing.T) { func TestParseBidderParams(t *testing.T) { parseBidderParams("../../static/bidder-params") - assert.Equal(t, 174, len(adapterParams), "Length of expected entries should match") + assert.Equal(t, 175, len(adapterParams), "Length of expected entries should match") // calculate this number using X-Y // where X is calculated using command - `ls -l | wc -l` (substract 1 from result) // Y is calculated using command `grep -EinR 'oneof|not|anyof|dependenc' static/bidder-params | grep -v "description" | grep -oE './.*.json' | uniq | wc -l` @@ -119,7 +119,7 @@ func TestParseBidderParams(t *testing.T) { func TestParseBidderSchemaDefinitions(t *testing.T) { schemaDefinitions, _ := parseBidderSchemaDefinitions("../../../../static/bidder-params") - assert.Equal(t, 210, len(schemaDefinitions), "Length of expected entries should match") + assert.Equal(t, 211, len(schemaDefinitions), "Length of expected entries should match") // calculate this number using command - `ls -l | wc -l` (substract 1 from result) } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 9d2cd68a4a7..22dafb52f00 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -159,6 +159,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderOpenx, BidderOperaads, BidderOrbidder, + BidderORTBMagnite, BidderOutbrain, BidderOwnAdx, BidderPangle, @@ -456,6 +457,7 @@ const ( BidderOpenx BidderName = "openx" BidderOperaads BidderName = "operaads" BidderOrbidder BidderName = "orbidder" + BidderORTBMagnite BidderName = "ortb_magnite" // OW specific: oRTB bidder for magnite BidderOutbrain BidderName = "outbrain" BidderOwnAdx BidderName = "ownadx" BidderPangle BidderName = "pangle" diff --git a/router/router.go b/router/router.go index a17fb327747..7daf595694d 100644 --- a/router/router.go +++ b/router/router.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/prebid/prebid-server/adapters/ortbbidder" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -239,6 +240,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) + ortbbidder.InitORTBAdapter(cfg.BidderInfos) // OW specific : required for oRTB adapter adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, cfg.BidderInfos, r.MetricsEngine) if len(adaptersErrs) > 0 { errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) diff --git a/router/router_test.go b/router/router_test.go index 39398e700ea..94df917ae0b 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -54,7 +54,8 @@ func TestNewJsonDirectoryServer(t *testing.T) { } for _, adapterFile := range adapterFiles { - if adapterFile.IsDir() && adapterFile.Name() != "adapterstest" { + // OW specific: ignore ortbbidder because we are maintaining single adapterFile for multiple bidders + if adapterFile.IsDir() && adapterFile.Name() != "adapterstest" && adapterFile.Name() != "ortbbidder" { ensureHasKey(t, data, adapterFile.Name()) } } diff --git a/static/bidder-info/ortb_magnite.yaml b/static/bidder-info/ortb_magnite.yaml new file mode 100644 index 00000000000..20b2aa306c3 --- /dev/null +++ b/static/bidder-info/ortb_magnite.yaml @@ -0,0 +1,26 @@ +# sample bidder-info yaml for magnite bidder +endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server" +maintainer: + email: "header-bidding@pubmatic.com" +gvlVendorID: 76 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + iframe: + url: "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect={{.RedirectURL}}" + userMacro: "" + redirect: + url: "https://image8.pubmatic.com/AdServer/ImgSync?p=159706&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&pu={{.RedirectURL}}" + userMacro: "#PMUID" +openrtb: + gpp-supported: true diff --git a/static/bidder-params/ortb_magnite.json b/static/bidder-params/ortb_magnite.json new file mode 100644 index 00000000000..af11cec587c --- /dev/null +++ b/static/bidder-params/ortb_magnite.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "magnite (oRTB Integration) Adapter Params", + "description": "A schema which validates params accepted by the magnite (oRTB Integration) adapter", + "type": "object", + "properties": { + "bidder-param": { + "type": "string", + "description": "Example of bidder-param" + } + }, + "required": ["bidder-param"] + }