diff --git a/adapters/waardex/params_test.go b/adapters/waardex/params_test.go new file mode 100644 index 00000000000..d97726ba884 --- /dev/null +++ b/adapters/waardex/params_test.go @@ -0,0 +1,62 @@ +package waardex + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderWaardex, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderWaardex, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ + "zoneId": 1 + }`, + `{ + "zoneId": 12345 + }`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{ + "zoneId": 0 + }`, + `{ + "zoneId": -1 + }`, + `{ + "zoneId": "1" + }`, +} diff --git a/adapters/waardex/waardex.go b/adapters/waardex/waardex.go new file mode 100644 index 00000000000..86f1fe8d91c --- /dev/null +++ b/adapters/waardex/waardex.go @@ -0,0 +1,255 @@ +package waardex + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + EndpointTemplate *template.Template +} + +// MakeRequests prepares request information for prebid-server core +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + impressionsByZone, impErrs := groupImpressionsByZone(request.Imp) + errs = append(errs, impErrs...) + if len(impressionsByZone) == 0 { + return nil, errs + } + result := make([]*adapters.RequestData, 0, len(impressionsByZone)) + for k, imps := range impressionsByZone { + bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) + if err != nil { + errs = append(errs, err) + } else { + result = append(result, bidRequest) + } + } + return result, errs +} + +// groupImpressionsByZone validates imps and groups them by Waardex-specific parameter `zoneId`. +func groupImpressionsByZone(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpWaardex][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpWaardex][]openrtb2.Imp) + errors := make([]error, 0, len(imps)) + for idx := range imps { + imp := imps[idx] + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + imp.Ext = nil + // Additional validation is handled by the core JSON schema (static/bidder-params/waardex.json). + if isMultiFormatImp(&imp) { + splImps := splitMultiFormatImp(&imp) + if len(splImps) == 0 { + continue + } + if _, exists := res[*impExt]; !exists { + res[*impExt] = make([]openrtb2.Imp, 0, 2) + } + res[*impExt] = append(res[*impExt], splImps...) + } else { + if _, exists := res[*impExt]; !exists { + res[*impExt] = make([]openrtb2.Imp, 0, 2) + } + res[*impExt] = append(res[*impExt], imp) + } + } + return res, errors +} + +func isMultiFormatImp(imp *openrtb2.Imp) bool { + formatCount := 0 + if imp.Video != nil { + formatCount++ + } + if imp.Audio != nil { + formatCount++ + } + if imp.Banner != nil { + formatCount++ + } + if imp.Native != nil { + formatCount++ + } + return formatCount > 1 +} + +func splitMultiFormatImp(imp *openrtb2.Imp) []openrtb2.Imp { + splitImps := make([]openrtb2.Imp, 0, 2) + if imp.Banner != nil { + impCopy := *imp + impCopy.Video = nil + impCopy.Native = nil + impCopy.Audio = nil + splitImps = append(splitImps, impCopy) + } + if imp.Video != nil { + impCopy := *imp + impCopy.Banner = nil + impCopy.Native = nil + impCopy.Audio = nil + splitImps = append(splitImps, impCopy) + } + return splitImps +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpWaardex, error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("imp.ext is invalid: %s", err.Error()), + } + } + var waardexExt openrtb_ext.ExtImpWaardex + if err := jsonutil.Unmarshal(bidderExt.Bidder, &waardexExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("imp.ext.bidder is invalid: %s", err.Error()), + } + } + return &waardexExt, nil +} + +func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpWaardex, imps []openrtb2.Imp) (*adapters.RequestData, error) { + newBidRequest := createBidRequest(prebidBidRequest, imps) + reqJSON, err := json.Marshal(newBidRequest) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + url, err := adapter.buildEndpointURL(params) + if err != nil { + return nil, err + } + + return &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil +} + +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, imps []openrtb2.Imp) *openrtb2.BidRequest { + bidRequest := *prebidBidRequest + bidRequest.Imp = imps + if bidRequest.Site != nil { + // Need to copy Site as Request is a shallow copy + site := *bidRequest.Site + site.Publisher = nil + bidRequest.Site = &site + } + if bidRequest.App != nil { + // Need to copy App as Request is a shallow copy + app := *bidRequest.App + app.Publisher = nil + bidRequest.App = &app + } + return &bidRequest +} + +// Builds endpoint url based on adapter-specific pub settings from imp.ext +func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpWaardex) (string, error) { + endpointParams := macros.EndpointTemplateParams{ZoneID: strconv.Itoa(params.ZoneId)} + return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) +} + +// MakeBids translates Waardex bid response to prebid-server specific format +func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode != http.StatusOK { + return nil, []error{ + newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), + } + } + var bidResp openrtb2.BidResponse + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{ + newBadServerResponseError(fmt.Sprintf("Bad server response: %v", err)), + } + } + + if len(bidResp.SeatBid) != 1 { + return nil, []error{ + newBadServerResponseError(fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))), + } + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } + var errs []error + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidType, err := getMediaTypeForBid(&bid) + if err != nil { + errs = append(errs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + return bidResponse, errs +} + +// getMediaTypeForImp figures out which media type this bid is for +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + default: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d", bid.MType), + } + } +} + +func newBadServerResponseError(message string) error { + return &errortypes.BadServerResponse{ + Message: message, + } +} + +// Builder builds a new instance of the waardex adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + urlTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + EndpointTemplate: urlTemplate, + } + return bidder, nil +} diff --git a/adapters/waardex/waardex_test.go b/adapters/waardex/waardex_test.go new file mode 100644 index 00000000000..2e66ce12e79 --- /dev/null +++ b/adapters/waardex/waardex_test.go @@ -0,0 +1,28 @@ +package waardex + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderWaardex, config.Adapter{ + Endpoint: "http://justbidit2.xyz:8800/hb?zone={{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "waardextest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderWaardex, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/waardex/waardextest/exemplary/multiformat-impression.json b/adapters/waardex/waardextest/exemplary/multiformat-impression.json new file mode 100644 index 00000000000..3925d766bc2 --- /dev/null +++ b/adapters/waardex/waardextest/exemplary/multiformat-impression.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "imp": [ + { + "id": "multi-adunit", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 102, + "host": "tag.test.com" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=102", + "body": { + "id": "0000000000001", + "imp": [ + { + "id": "multi-adunit", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "multi-adunit", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001" + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "impIDs": [ + "multi-adunit", + "multi-adunit" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [ + { + "bid": [ + { + "id": "bid02", + "impid": "multi-adunit", + "price": 2.25, + "cid": "1001", + "crid": "2002", + "adid": "2002", + "adm": "", + "mtype": 1, + "adomain": [ + "tag-example.com" + ] + }, + { + "id": "bid03", + "impid": "multi-adunit", + "price": 1.25, + "cid": "601", + "crid": "702", + "adid": "702", + "adm": "", + "cat": [ + "IAB2" + ], + "mtype": 2, + "adomain": [ + "video-example.com" + ] + } + ] + } + ], + "bidid": "wehM-93KGr0" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid02", + "impid": "multi-adunit", + "price": 2.25, + "adm": "", + "adomain": [ + "tag-example.com" + ], + "cid": "1001", + "adid": "2002", + "crid": "2002", + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "bid03", + "impid": "multi-adunit", + "price": 1.25, + "adm": "", + "adomain": [ + "video-example.com" + ], + "cid": "601", + "adid": "702", + "crid": "702", + "cat": [ + "IAB2" + ], + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/waardex/waardextest/exemplary/multiple-impressions-different-zones.json b/adapters/waardex/waardextest/exemplary/multiple-impressions-different-zones.json new file mode 100644 index 00000000000..b29d75c587e --- /dev/null +++ b/adapters/waardex/waardextest/exemplary/multiple-impressions-different-zones.json @@ -0,0 +1,227 @@ +{ + "mockBidRequest": { + "id": "0000000000003", + "imp": [ + { + "id": "banner-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 301, + "host": "tag.test.com" + } + } + }, + { + "id": "banner-4", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 302, + "host": "tag.test.com" + } + } + } + ], + "site": { + "page": "http://example.com/multi-zone.html", + "publisher": { + "id": "3" + } + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=301", + "body": { + "id": "0000000000003", + "imp": [ + { + "id": "banner-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "http://example.com/multi-zone.html" + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": [ + "USD" + ] + }, + "impIDs": [ + "banner-3" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "resp-0002", + "seatbid": [ + { + "bid": [ + { + "id": "bid-301-1", + "impid": "banner-3", + "price": 0.95, + "cid": "7001", + "crid": "8001", + "adid": "8001", + "adm": "", + "mtype": 1, + "adomain": [ + "example-3.com" + ], + "w": 300, + "h": 250 + } + ] + } + ], + "bidid": "resp-0002-bid", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=302", + "body": { + "id": "0000000000003", + "imp": [ + { + "id": "banner-4", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + } + } + ], + "site": { + "page": "http://example.com/multi-zone.html" + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": [ + "USD" + ] + }, + "impIDs": [ + "banner-4" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "resp-0003", + "seatbid": [ + { + "bid": [ + { + "id": "bid-302-1", + "impid": "banner-4", + "price": 0.75, + "cid": "7002", + "crid": "8002", + "adid": "8002", + "adm": "", + "mtype": 1, + "adomain": [ + "example-4.com" + ], + "w": 728, + "h": 90 + } + ] + } + ], + "bidid": "resp-0003-bid", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-301-1", + "impid": "banner-3", + "price": 0.95, + "adm": "", + "adomain": [ + "example-3.com" + ], + "cid": "7001", + "adid": "8001", + "crid": "8001", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-302-1", + "impid": "banner-4", + "price": 0.75, + "adm": "", + "adomain": [ + "example-4.com" + ], + "cid": "7002", + "adid": "8002", + "crid": "8002", + "w": 728, + "h": 90, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/waardex/waardextest/exemplary/multiple-impressions-same-zone.json b/adapters/waardex/waardextest/exemplary/multiple-impressions-same-zone.json new file mode 100644 index 00000000000..198f3a0652f --- /dev/null +++ b/adapters/waardex/waardextest/exemplary/multiple-impressions-same-zone.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "0000000000002", + "imp": [ + { + "id": "banner-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 201, + "host": "tag.test.com" + } + } + }, + { + "id": "banner-2", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 201, + "host": "tag.test.com" + } + } + } + ], + "site": { + "page": "http://example.com/multi-zone.html", + "publisher": { + "id": "3" + } + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=201", + "body": { + "id": "0000000000002", + "imp": [ + { + "id": "banner-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "banner-2", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "site": { + "page": "http://example.com/multi-zone.html" + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": [ + "USD" + ] + }, + "impIDs": [ + "banner-1", + "banner-2" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "resp-0001", + "seatbid": [ + { + "bid": [ + { + "id": "bid-201-1", + "impid": "banner-1", + "price": 1.1, + "cid": "5001", + "crid": "6001", + "adid": "6001", + "adm": "", + "mtype": 1, + "adomain": [ + "example-1.com" + ], + "w": 300, + "h": 250 + }, + { + "id": "bid-201-2", + "impid": "banner-2", + "price": 1.4, + "cid": "5002", + "crid": "6002", + "adid": "6002", + "adm": "", + "mtype": 1, + "adomain": [ + "example-2.com" + ], + "w": 300, + "h": 600 + } + ] + } + ], + "bidid": "resp-0001-bid", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-201-1", + "impid": "banner-1", + "price": 1.1, + "adm": "", + "adomain": [ + "example-1.com" + ], + "cid": "5001", + "adid": "6001", + "crid": "6001", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "bid-201-2", + "impid": "banner-2", + "price": 1.4, + "adm": "", + "adomain": [ + "example-2.com" + ], + "cid": "5002", + "adid": "6002", + "crid": "6002", + "w": 300, + "h": 600, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/waardex/waardextest/exemplary/single-banner-impression.json b/adapters/waardex/waardextest/exemplary/single-banner-impression.json new file mode 100644 index 00000000000..3b276f32872 --- /dev/null +++ b/adapters/waardex/waardextest/exemplary/single-banner-impression.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "imp": [ + { + "id": "adunit-1", + "banner": { + "format": [{"w": 300,"h": 200}, {"w": 300,"h": 250}] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ], + "site": { + "page": "http://example.com/test.html", + "publisher": { + "id": "1" + } + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": ["TYR"] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "0000000000001", + "imp": [ + { + "id": "adunit-1", + "banner": { + "format": [{"w": 300, "h": 200}, {"w": 300, "h": 250}] + } + } + ], + "site": { + "page": "http://example.com/test.html" + }, + "user": { + "buyeruid": "A-38327932832" + }, + "cur": ["TYR"] + }, + "impIDs":["adunit-1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "mtype": 1, + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0", + "cur": "TYR" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "TYR", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"], + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/waardex/waardextest/exemplary/single-video-impression.json b/adapters/waardex/waardextest/exemplary/single-video-impression.json new file mode 100644 index 00000000000..a3ed60a74bf --- /dev/null +++ b/adapters/waardex/waardextest/exemplary/single-video-impression.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "zoneId": 102, + "host": "tag.test.com" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=102", + "body": { + "id": "0000000000001", + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001" + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "impIDs":["video-adunit-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [ + { + "bid": [ + { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "cid": "1001", + "crid": "2002", + "adid": "2002", + "adm": "", + "cat": [ + "IAB2" + ], + "mtype": 2, + "adomain": [ + "video-example.com" + ] + } + ] + } + ], + "bidid": "wehM-93KGr0" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "adm": "", + "adomain": [ + "video-example.com" + ], + "cid": "1001", + "adid": "2002", + "crid": "2002", + "cat": [ + "IAB2" + ], + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/waardex/waardextest/supplemental/all-impressions-invalid.json b/adapters/waardex/waardextest/supplemental/all-impressions-invalid.json new file mode 100644 index 00000000000..8349d722185 --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/all-impressions-invalid.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": "malformed" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "imp.ext is invalid: expect { or n, but found \"", + "comparison": "literal" + } + ], + "httpCalls": [] +} diff --git a/adapters/waardex/waardextest/supplemental/bad-response.json b/adapters/waardex/waardextest/supplemental/bad-response.json new file mode 100644 index 00000000000..bdc48f793d5 --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/bad-response.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + "impIDs": [ + "imp-1" + ] + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: .*", + "comparison": "regex" + } + ] +} diff --git a/adapters/waardex/waardextest/supplemental/invalid-seatbid-count.json b/adapters/waardex/waardextest/supplemental/invalid-seatbid-count.json new file mode 100644 index 00000000000..35994a950ab --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/invalid-seatbid-count.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + "impIDs": [ + "imp-1" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "resp-1", + "seatbid": [] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} diff --git a/adapters/waardex/waardextest/supplemental/multiformat-audio-native.json b/adapters/waardex/waardextest/supplemental/multiformat-audio-native.json new file mode 100644 index 00000000000..c4771051ffe --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/multiformat-audio-native.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "audio": { + "mimes": [ + "audio/mp4" + ], + "minduration": 1, + "maxduration": 30 + }, + "native": { + "request": "{\"ver\":\"1.2\"}" + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [] +} diff --git a/adapters/waardex/waardextest/supplemental/multiformat-impression-invalid-ext.json b/adapters/waardex/waardextest/supplemental/multiformat-impression-invalid-ext.json new file mode 100644 index 00000000000..736742542d1 --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/multiformat-impression-invalid-ext.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "imp": [ + { + "id": "multi-adunit", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": "malformed" + }, + { + "id": "multi-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": "not-json" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "imp.ext is invalid: expect { or n, but found \"", + "comparison": "literal" + }, + { + "value": "imp.ext.bidder is invalid: expect { or n, but found \"", + "comparison": "literal" + } + ], + "httpCalls": [] +} diff --git a/adapters/waardex/waardextest/supplemental/status-204.json b/adapters/waardex/waardextest/supplemental/status-204.json new file mode 100644 index 00000000000..73c66022bcd --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/status-204.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + "impIDs": [ + "imp-1" + ] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/waardex/waardextest/supplemental/status-not-200.json b/adapters/waardex/waardextest/supplemental/status-not-200.json new file mode 100644 index 00000000000..b7a6d0ee491 --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/status-not-200.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + "impIDs": [ + "imp-1" + ] + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 400", + "comparison": "literal" + } + ] +} diff --git a/adapters/waardex/waardextest/supplemental/unsupported-mtype.json b/adapters/waardex/waardextest/supplemental/unsupported-mtype.json new file mode 100644 index 00000000000..40b0f546e3b --- /dev/null +++ b/adapters/waardex/waardextest/supplemental/unsupported-mtype.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 101, + "host": "tag.test.com" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://justbidit2.xyz:8800/hb?zone=101", + "body": { + "id": "req-1", + "imp": [ + { + "id": "imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + "impIDs": [ + "imp-1" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "resp-1", + "seatbid": [ + { + "bid": [ + { + "id": "bid-1", + "impid": "imp-1", + "price": 1.25, + "mtype": 99, + "adm": "" + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 99", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index af5c2889672..c5427d4dec5 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -247,6 +247,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/vox" "github.com/prebid/prebid-server/v3/adapters/vrtcal" "github.com/prebid/prebid-server/v3/adapters/vungle" + "github.com/prebid/prebid-server/v3/adapters/waardex" "github.com/prebid/prebid-server/v3/adapters/xeworks" "github.com/prebid/prebid-server/v3/adapters/yahooAds" "github.com/prebid/prebid-server/v3/adapters/yandex" @@ -513,6 +514,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVox: vox.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, + openrtb_ext.BidderWaardex: waardex.Builder, openrtb_ext.BidderXeworks: xeworks.Builder, openrtb_ext.BidderYahooAds: yahooAds.Builder, openrtb_ext.BidderYandex: yandex.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 435e92eb439..445f1c0be8a 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -265,6 +265,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderVox, BidderVrtcal, BidderVungle, + BidderWaardex, BidderXeworks, BidderYahooAds, BidderYandex, @@ -636,6 +637,7 @@ const ( BidderVox BidderName = "vox" BidderVrtcal BidderName = "vrtcal" BidderVungle BidderName = "vungle" + BidderWaardex BidderName = "waardex" BidderXeworks BidderName = "xeworks" BidderYahooAds BidderName = "yahooAds" BidderYandex BidderName = "yandex" diff --git a/openrtb_ext/imp_waardex.go b/openrtb_ext/imp_waardex.go new file mode 100644 index 00000000000..0330ea45f0a --- /dev/null +++ b/openrtb_ext/imp_waardex.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// Waardex defines the contract for bidrequest.imp[i].ext.prebid.bidder.waardex +type ExtImpWaardex struct { + ZoneId int `json:"zoneId"` +} diff --git a/static/bidder-info/waardex.yaml b/static/bidder-info/waardex.yaml new file mode 100644 index 00000000000..fb3f482fefc --- /dev/null +++ b/static/bidder-info/waardex.yaml @@ -0,0 +1,21 @@ +# We have the following regional endpoint domains: useast, uswest +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "http://REGION.justbidit2.xyz:8800/hb?zone={{.ZoneID}}" +maintainer: + email: it@waardex.com +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + redirect: + # waardex supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address support@waardex.com to ask about enabling user sync. + supports: + - redirect +endpointCompression: "GZIP" diff --git a/static/bidder-params/waardex.json b/static/bidder-params/waardex.json new file mode 100644 index 00000000000..395a8bbd01e --- /dev/null +++ b/static/bidder-params/waardex.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Waardex Adapter Params", + "description": "A schema which validates params accepted by the Waardex adapter", + + "type": "object", + "properties": { + "zoneId": { + "type": "integer", + "minimum": 1, + "description": "Publisher Id to use." + } + }, + "required": ["zoneId"] +}