diff --git a/adapters/melozen/melozen.go b/adapters/melozen/melozen.go new file mode 100644 index 00000000000..cb76274865b --- /dev/null +++ b/adapters/melozen/melozen.go @@ -0,0 +1,185 @@ +package melozen + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpointTemplate *template.Template +} + +// Builder builds a new instance of the MeloZen adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, 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: template, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errors []error + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requestCopy := *request + for _, imp := range request.Imp { + // Extract Melozen Params + var strImpExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &strImpExt); err != nil { + errors = append(errors, err) + continue + } + var strImpParams openrtb_ext.ImpExtMeloZen + if err := json.Unmarshal(strImpExt.Bidder, &strImpParams); err != nil { + errors = append(errors, err) + continue + } + + url, err := macros.ResolveMacros(a.endpointTemplate, macros.EndpointTemplateParams{PublisherID: strImpParams.PubId}) + if err != nil { + errors = append(errors, err) + continue + } + // Convert Floor into USD + if imp.BidFloor > 0 && imp.BidFloorCur != "" && !strings.EqualFold(imp.BidFloorCur, "USD") { + convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") + if err != nil { + errors = append(errors, err) + continue + } + imp.BidFloorCur = "USD" + imp.BidFloor = convertedValue + } + + impressionsByMediaType, err := splitImpressionsByMediaType(&imp) + if err != nil { + errors = append(errors, err) + continue + } + + for _, impression := range impressionsByMediaType { + requestCopy.Imp = []openrtb2.Imp{impression} + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errors = append(errors, err) + continue + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: requestJSON, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), + } + requests = append(requests, requestData) + } + } + + return requests, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidReq openrtb2.BidRequest + if err := json.Unmarshal(requestData.Body, &bidReq); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidderResponse := adapters.NewBidderResponse() + var errors []error + for _, seatBid := range bidResp.SeatBid { + for i := range seatBid.Bid { + bid := &seatBid.Bid[i] + bidType, err := getMediaTypeForBid(*bid) + if err != nil { + errors = append(errors, err) + continue + } + + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + BidType: bidType, + Bid: bid, + }) + } + } + return bidderResponse, errors +} + +func splitImpressionsByMediaType(impression *openrtb2.Imp) ([]openrtb2.Imp, error) { + if impression.Banner == nil && impression.Native == nil && impression.Video == nil { + return nil, &errortypes.BadInput{Message: "Invalid MediaType. MeloZen only supports Banner, Video and Native."} + } + + impressions := make([]openrtb2.Imp, 0, 2) + + if impression.Banner != nil { + impCopy := *impression + impCopy.Video = nil + impCopy.Native = nil + impressions = append(impressions, impCopy) + } + + if impression.Video != nil { + impCopy := *impression + impCopy.Banner = nil + impCopy.Native = nil + impressions = append(impressions, impCopy) + } + + if impression.Native != nil { + impCopy := *impression + impCopy.Banner = nil + impCopy.Video = nil + impressions = append(impressions, impCopy) + } + + return impressions, nil +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + + 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)) + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse bid mediatype for impression \"%s\"", bid.ImpID), + } +} diff --git a/adapters/melozen/melozen_test.go b/adapters/melozen/melozen_test.go new file mode 100644 index 00000000000..0191ab73182 --- /dev/null +++ b/adapters/melozen/melozen_test.go @@ -0,0 +1,30 @@ +package melozen + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + + bidder, buildErr := Builder(openrtb_ext.BidderMeloZen, config.Adapter{ + Endpoint: "https://example.com/rtb/v2/bid?publisher_id={{.PublisherID}}", + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "melozentest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderMeloZen, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/melozen/melozentest/exemplary/app-banner.json b/adapters/melozen/melozentest/exemplary/app-banner.json new file mode 100644 index 00000000000..6cdb82bf5ad --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/app-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "device": { + "w": 1200, + "h": 900 + }, + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + } + }, + "impIDs":["banner-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-banner", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-banner", + "impid": "banner-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-banner", + "impid": "banner-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/melozen/melozentest/exemplary/app-native.json b/adapters/melozen/melozentest/exemplary/app-native.json new file mode 100644 index 00000000000..f93abd44bea --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/app-native.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "web-native", + "imp": [ + { + "id": "native-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "native": { + "ver": "1.2", + "request": "placeholder request" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "web-native", + "imp": [ + { + "id": "native-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "native": { + "ver": "1.2", + "request": "placeholder request" + } + } + ] + }, + "impIDs":["native-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-native", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-native", + "impid": "native-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-native", + "impid": "native-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/melozen/melozentest/exemplary/app-video.json b/adapters/melozen/melozentest/exemplary/app-video.json new file mode 100644 index 00000000000..3d913c43e44 --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/app-video.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest": { + "id": "app-video", + "tmax": 3000, + "imp": [ + { + "id": "video-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + } + ], + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "app-video", + "tmax": 3000, + "imp": [ + { + "id": "video-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + } + ], + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "impIDs": [ + "video-imp-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "app-video", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "app-video", + "impid": "video-imp-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "app-video", + "impid": "video-imp-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/melozen/melozentest/exemplary/multi-imps.json b/adapters/melozen/melozentest/exemplary/multi-imps.json new file mode 100644 index 00000000000..916c74cb685 --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/multi-imps.json @@ -0,0 +1,239 @@ +{ + "mockBidRequest": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id-1", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "banner-imp-id-2", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id-1", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "impIDs":["banner-imp-id-1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-banner", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-banner", + "impid": "banner-imp-id-1", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + }, + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id-2", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "impIDs":["banner-imp-id-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-banner", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-banner", + "impid": "banner-imp-id-2", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 600, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-banner", + "impid": "banner-imp-id-1", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-banner", + "impid": "banner-imp-id-2", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 600, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/melozen/melozentest/exemplary/web-banner.json b/adapters/melozen/melozentest/exemplary/web-banner.json new file mode 100644 index 00000000000..0439baa1033 --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/web-banner.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "baner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "test": 0, + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "baner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "impIDs": [ + "baner-imp-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-banner", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-banner", + "impid": "baner-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-banner", + "impid": "baner-imp-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/melozen/melozentest/exemplary/web-video.json b/adapters/melozen/melozentest/exemplary/web-video.json new file mode 100644 index 00000000000..b4f179bdc55 --- /dev/null +++ b/adapters/melozen/melozentest/exemplary/web-video.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "web-video", + "tmax": 3000, + "imp": [ + { + "id": "video-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "video": { + "w": 640, + "h": 480, + "mimes": ["video/mp4"], + "placement": 1 + } + } + ], + "test": 0, + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "web-video", + "tmax": 3000, + "imp": [ + { + "id": "video-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "video": { + "w": 640, + "h": 480, + "mimes": ["video/mp4"], + "placement": 1 + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "impIDs":["video-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-video", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "web-video", + "impid": "video-imp-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "web-video", + "impid": "video-imp-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/melozen/melozentest/supplemental/bad-media-type-request.json b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json new file mode 100644 index 00000000000..f6c17a70b8f --- /dev/null +++ b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "unsupported-request", + "imp": [ + { + "id": "unsupported-imp", + "unupported": { + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ], + "site": { + "id": "siteID" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid MediaType. MeloZen only supports Banner, Video and Native.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/melozen/melozentest/supplemental/no-fill.json b/adapters/melozen/melozentest/supplemental/no-fill.json new file mode 100644 index 00000000000..7dd600a72b6 --- /dev/null +++ b/adapters/melozen/melozentest/supplemental/no-fill.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "device": { + "w": 1200, + "h": 900 + }, + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + } + }, + "impIDs": [ + "banner-imp-id" + ] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/melozen/melozentest/supplemental/response-status-400.json b/adapters/melozen/melozentest/supplemental/response-status-400.json new file mode 100644 index 00000000000..969875b86ec --- /dev/null +++ b/adapters/melozen/melozentest/supplemental/response-status-400.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "web-banner", + "tmax": 3000, + "imp": [ + { + "id": "banner-imp-id", + "ext": { + "bidder": { + "pubId": "386276e072" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "device": { + "w": 1200, + "h": 900 + }, + "app": { + "bundle": "com.fake.app", + "publisher": { + "id": "42", + "name": "whatever.pub" + } + } + }, + "impIDs": [ + "banner-imp-id" + ] + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/melozen/melozentest/supplemental/response-status-not-200.json b/adapters/melozen/melozentest/supplemental/response-status-not-200.json new file mode 100644 index 00000000000..9b26ee58091 --- /dev/null +++ b/adapters/melozen/melozentest/supplemental/response-status-not-200.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.test.testapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.test.testapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json new file mode 100644 index 00000000000..b6a1c1f7268 --- /dev/null +++ b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "impIDs": [ + "test-imp-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "another-imp-id", + "price": 3.5, + "w": 900, + "h": 250, + "ext": {} + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [{"currency":"USD","bids":[]}], + "expectedMakeBidsErrors": [ + { + "value": "Failed to parse bid mediatype for impression \"another-imp-id\"", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/melozen/params_test.go b/adapters/melozen/params_test.go new file mode 100644 index 00000000000..7e1be7f0db0 --- /dev/null +++ b/adapters/melozen/params_test.go @@ -0,0 +1,50 @@ +package melozen + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/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.BidderMeloZen, 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.BidderMeloZen, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"pubId": "12345"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"pubId": ""}`, + `{"pubId": 12345}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index b79d03a8afd..6e5c30652c4 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -129,6 +129,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/marsmedia" "github.com/prebid/prebid-server/v2/adapters/mediago" "github.com/prebid/prebid-server/v2/adapters/medianet" + "github.com/prebid/prebid-server/v2/adapters/melozen" "github.com/prebid/prebid-server/v2/adapters/metax" "github.com/prebid/prebid-server/v2/adapters/mgid" "github.com/prebid/prebid-server/v2/adapters/mgidX" @@ -353,6 +354,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMediafuse: appnexus.Builder, openrtb_ext.BidderMediaGo: mediago.Builder, openrtb_ext.BidderMedianet: medianet.Builder, + openrtb_ext.BidderMeloZen: melozen.Builder, openrtb_ext.BidderMetaX: metax.Builder, openrtb_ext.BidderMgid: mgid.Builder, openrtb_ext.BidderMgidX: mgidX.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 46d70c7f998..286361b6df7 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -147,6 +147,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderMediafuse, BidderMediaGo, BidderMedianet, + BidderMeloZen, BidderMetaX, BidderMgid, BidderMgidX, @@ -468,6 +469,7 @@ const ( BidderMediafuse BidderName = "mediafuse" BidderMediaGo BidderName = "mediago" BidderMedianet BidderName = "medianet" + BidderMeloZen BidderName = "melozen" BidderMetaX BidderName = "metax" BidderMgid BidderName = "mgid" BidderMgidX BidderName = "mgidX" diff --git a/openrtb_ext/imp_melozen.go b/openrtb_ext/imp_melozen.go new file mode 100644 index 00000000000..598df6a28e9 --- /dev/null +++ b/openrtb_ext/imp_melozen.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtMeloZen struct { + PubId string `json:"pubId"` +} diff --git a/static/bidder-info/melozen.yaml b/static/bidder-info/melozen.yaml new file mode 100644 index 00000000000..391e0a8d43b --- /dev/null +++ b/static/bidder-info/melozen.yaml @@ -0,0 +1,19 @@ +# We have the following regional endpoint domains: us-east and us-west +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://prebid.melozen.com/rtb/v2/bid?publisher_id={{.PublisherID}}" +endpointCompression: gzip +geoscope: + - global +maintainer: + email: DSP@melodong.com +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/melozen.json b/static/bidder-params/melozen.json new file mode 100644 index 00000000000..6b5cef5b3fd --- /dev/null +++ b/static/bidder-params/melozen.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "MeloZen Adapter Params", + "description": "A schema which validates params accepted by the MeloZen adapter", + "type": "object", + "properties": { + "pubId": { + "type": "string", + "minLength": 1, + "description": "The unique identifier for the publisher." + } + }, + "required": ["pubId"] +}