diff --git a/adapters/resetdigital/params_test.go b/adapters/resetdigital/params_test.go new file mode 100644 index 0000000000..4098b44493 --- /dev/null +++ b/adapters/resetdigital/params_test.go @@ -0,0 +1,58 @@ +package resetdigital + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +// TestValidParams tests valid parameter(s) declared in openrtb_ext/imp_resetdigital.go +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderResetDigital, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected ResetDigital params: %s \n Error: %s", validParam, err) + } + } +} + +// TestValidParams tests invalid parameter(s) declared in openrtb_ext/imp_resetdigital.go +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderResetDigital, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected ResetDigital params: %s", invalidParam) + } + } +} + +// list of valid parameter(s) test cases +var validParams = []string{ + `{"placement_id":"1000"}`, + `{"placement_id":"0"}`, + `{"placement_id":"abc"}`, + `{"placement_id":"123abc"}`, + `{}`, + `{"cp":"1000"}`, +} + +// list of invalid parameter(s) test cases +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{"placement_id":}`, + `{"placement_id":""}`, +} diff --git a/adapters/resetdigital/resetdigital.go b/adapters/resetdigital/resetdigital.go new file mode 100644 index 0000000000..78aa6aef40 --- /dev/null +++ b/adapters/resetdigital/resetdigital.go @@ -0,0 +1,317 @@ +package resetdigital + +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/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template + endpointUri string +} + +type resetDigitalRequest struct { + Site resetDigitalSite `json:"site"` + Imps []resetDigitalImp `json:"imps"` +} +type resetDigitalSite struct { + Domain string `json:"domain"` + Referrer string `json:"referrer"` +} +type resetDigitalImp struct { + ZoneID resetDigitalImpZone `json:"zone_id"` + BidID string `json:"bid_id"` + ImpID string `json:"imp_id"` + Ext resetDigitalImpExt `json:"ext"` + MediaTypes resetDigitalMediaTypes `json:"media_types"` +} +type resetDigitalImpZone struct { + PlacementID string `json:"placementId"` +} +type resetDigitalImpExt struct { + Gpid string `json:"gpid"` +} +type resetDigitalMediaTypes struct { + Banner resetDigitalMediaType `json:"banner,omitempty"` + Video resetDigitalMediaType `json:"video,omitempty"` + Audio resetDigitalMediaType `json:"audio,omitempty"` +} +type resetDigitalMediaType struct { + Sizes [][]int64 `json:"sizes,omitempty"` + Mimes []string `json:"mimes,omitempty"` +} +type resetDigitalBidResponse struct { + Bids []resetDigitalBid `json:"bids"` +} +type resetDigitalBid struct { + BidID string `json:"bid_id"` + ImpID string `json:"imp_id"` + CPM float64 `json:"cpm"` + CID string `json:"cid,omitempty"` + CrID string `json:"crid,omitempty"` + AdID string `json:"adid"` + W string `json:"w,omitempty"` + H string `json:"h,omitempty"` + Seat string `json:"seat"` + HTML string `json:"html"` +} + +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{ + endpoint: template, + } + return bidder, nil +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + + addNonEmptyHeaders(&headers, map[string]string{ + "Content-Type": "application/json;charset=utf-8", + "Accept": "application/json", + }) + + if request != nil && request.Device != nil { + addNonEmptyHeaders(&headers, map[string]string{ + "Accept-Language": request.Device.Language, + "User-Agent": request.Device.UA, + "X-Forwarded-For": request.Device.IP, + "X-Real-Ip": request.Device.IP, + }) + } + if request != nil && request.Site != nil { + addNonEmptyHeaders(&headers, map[string]string{ + "Referer": request.Site.Page, + }) + } + + return headers +} + +func addNonEmptyHeaders(headers *http.Header, headerValues map[string]string) { + for key, value := range headerValues { + if len(value) > 0 { + headers.Add(key, value) + } + } +} + +func (a *adapter) MakeRequests(requestData *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var ( + requests []*adapters.RequestData + errors []error + ) + + for _, imp := range requestData.Imp { + bidType, err := getBidType(imp) + if err != nil { + errors = append(errors, err) + continue + } + + splittedRequestData, err := processDataFromRequest(requestData, imp, bidType) + if err != nil { + errors = append(errors, err) + continue + } + + requestBody, err := json.Marshal(splittedRequestData) + + if err != nil { + errors = append(errors, err) + continue + } + + requests = append(requests, &adapters.RequestData{ + Method: "POST", + Uri: a.endpointUri, + Body: requestBody, + Headers: getHeaders(requestData), + ImpIDs: []string{imp.ID}, + }) + } + + return requests, errors +} + +func processDataFromRequest(requestData *openrtb2.BidRequest, imp openrtb2.Imp, bidType openrtb_ext.BidType) (resetDigitalRequest, error) { + var reqData resetDigitalRequest + + if requestData.Site != nil { + reqData.Site.Domain = requestData.Site.Domain + reqData.Site.Referrer = requestData.Site.Page + } + + rdImp := resetDigitalImp{ + BidID: requestData.ID, + ImpID: imp.ID, + } + + if bidType == openrtb_ext.BidTypeBanner && imp.Banner != nil { + var tempH, tempW int64 + if imp.Banner.H != nil { + tempH = *imp.Banner.H + } + if imp.Banner.W != nil { + tempW = *imp.Banner.W + } + if tempH > 0 && tempW > 0 { + rdImp.MediaTypes.Banner.Sizes = append(rdImp.MediaTypes.Banner.Sizes, []int64{tempW, tempH}) + } + } + if bidType == openrtb_ext.BidTypeVideo && imp.Video != nil { + var tempH, tempW int64 + if imp.Video.H != nil { + tempH = *imp.Video.H + } + if imp.Video.W != nil { + tempW = *imp.Video.W + } + if tempH > 0 && tempW > 0 { + rdImp.MediaTypes.Video.Sizes = append(rdImp.MediaTypes.Video.Sizes, []int64{tempW, tempH}) + } + if imp.Video.MIMEs != nil { + rdImp.MediaTypes.Video.Mimes = append(rdImp.MediaTypes.Video.Mimes, imp.Video.MIMEs...) + } + } + if bidType == openrtb_ext.BidTypeAudio && imp.Audio != nil && imp.Audio.MIMEs != nil { + rdImp.MediaTypes.Audio.Mimes = append(rdImp.MediaTypes.Audio.Mimes, imp.Audio.MIMEs...) + } + + var bidderExt adapters.ExtImpBidder + var resetDigitalExt openrtb_ext.ImpExtResetDigital + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return resetDigitalRequest{}, err + } + if err := json.Unmarshal(bidderExt.Bidder, &resetDigitalExt); err != nil { + return resetDigitalRequest{}, err + } + rdImp.ZoneID.PlacementID = resetDigitalExt.PlacementID + + reqData.Imps = append(reqData.Imps, rdImp) + + return reqData, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + // Return early if the response contains no content + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + // Check for errors in the response status code + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + // Parse the response body into a single bid response + var response resetDigitalBidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + // Ensure there is exactly one bid in the response + if len(response.Bids) != 1 { + return nil, []error{fmt.Errorf("expected exactly one bid in the response, but got %d", len(response.Bids))} + } + + // Extract the single bid + resetDigitalBid := &response.Bids[0] + + // Map the incoming impression to its ID for media type determination + requestImp, found := findRequestImpByID(request.Imp, resetDigitalBid.ImpID) + if !found { + return nil, []error{fmt.Errorf("no matching impression found for ImpID %s", resetDigitalBid.ImpID)} + } + + // Convert the bid into an OpenRTB bid + bid, err := getBidFromResponse(resetDigitalBid) + if err != nil { + return nil, []error{err} + } + + // Determine the bid type based on the impression + bidType := GetMediaTypeForImp(requestImp) + + // Construct the bidder response + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + bidResponse.Currency = "USD" // Default currency + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: bid, + BidType: bidType, + Seat: openrtb_ext.BidderName(resetDigitalBid.Seat), + }) + + return bidResponse, nil +} + +// findRequestImpByID searches for an impression by its ID in the list of impressions +func findRequestImpByID(imps []openrtb2.Imp, impID string) (openrtb2.Imp, bool) { + for _, imp := range imps { + if imp.ID == impID { + return imp, true + } + } + return openrtb2.Imp{}, false +} + +func getBidFromResponse(bidResponse *resetDigitalBid) (*openrtb2.Bid, error) { + + bid := &openrtb2.Bid{ + ID: bidResponse.BidID, + Price: bidResponse.CPM, + ImpID: bidResponse.ImpID, + CID: bidResponse.CID, + CrID: bidResponse.CrID, + AdM: bidResponse.HTML, + } + + w, err := strconv.ParseInt(bidResponse.W, 10, 64) + if err != nil { + return nil, err + } + bid.W = w + + h, err := strconv.ParseInt(bidResponse.H, 10, 64) + if err != nil { + return nil, err + } + bid.H = h + return bid, nil +} + +func getBidType(imp openrtb2.Imp) (openrtb_ext.BidType, error) { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } + + return "", fmt.Errorf("failed to find matching imp for bid %s", imp.ID) +} + +func GetMediaTypeForImp(reqImp openrtb2.Imp) openrtb_ext.BidType { + + if reqImp.Video != nil { + return openrtb_ext.BidTypeVideo + } + if reqImp.Audio != nil { + return openrtb_ext.BidTypeAudio + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/resetdigital/resetdigital_test.go b/adapters/resetdigital/resetdigital_test.go new file mode 100644 index 0000000000..9efba33f30 --- /dev/null +++ b/adapters/resetdigital/resetdigital_test.go @@ -0,0 +1,21 @@ +package resetdigital + +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" +) + +func TestJsonSamples(t *testing.T) { + + bidder, buildErr := Builder(openrtb_ext.BidderResetDigital, config.Adapter{ + Endpoint: "https://test.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "resetdigitaltest", bidder) +} diff --git a/adapters/resetdigital/resetdigitaltest/exemplary/simple-audio.json b/adapters/resetdigital/resetdigitaltest/exemplary/simple-audio.json new file mode 100644 index 0000000000..d032b4c6f3 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/exemplary/simple-audio.json @@ -0,0 +1,176 @@ +{ + "mockBidRequest": { + "id": "12345", + "at": 1, + "bcat": [ + "IAB24", + "IAB25", + "IAB26" + ], + "cur": [ + "USD" + ], + "source": { + "fd": 0, + "tid": "00000FBCE10DE8FA", + "schain": { + "complete": 1, + "ver": "1.0", + "nodes": [ + { + "asi": "resetdigital.co", + "sid": "176", + "hp": 1 + } + ] + } + }, + "device": { + "ip": "35.146.176.97", + "js": 1, + "language": "en", + "ua": "PostmanRuntime/7.42.0" + }, + "user": { + "id": "000011200BA1065C", + "buyeruid": "000011200BA1065C" + }, + "app": { + "id": "test.com", + "bundle": "test.com", + "name": "test.com", + "content": { + "livestream": 1, + "genre": "F%C3%BAtbol", + "cat": [ + "" + ], + "language": "es" + }, + "publisher": { + "id": "176" + }, + "keywords": "" + }, + "imp": [ + { + "id": "001", + "bidfloor": 1.429, + "tagid": "667", + "secure": 1, + "audio": { + "protocols": [ + 2, + 3, + 5, + 6 + ], + "mimes": [ + "audio/mp4", + "audio/mp3" + ], + "api": [], + "minduration": 15, + "maxduration": 60, + "startdelay": -1 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + } + ], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 400 + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "POST", + "body": { + "imps": [ + { + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": {}, + "video": {}, + "audio": { + "mimes": [ + "audio/mp4", + "audio/mp3" + ] + } + }, + "zone_id": { + "placementId": "placement-id-1" + } + } + ], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [ + { + "bid_id": "01", + "imp_id": "001", + "cpm": 9.11, + "dealid": "RD-1000164", + "cid": "1000048-1002918", + "crid": "1003893", + "adid": "1003893", + "language": "en", + "w": "0", + "h": "0", + "seat": "resetdigital", + "html": "test markup" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "test markup", + "cid": "1000048-1002918", + "crid": "1003893", + "id": "01", + "impid": "001", + "price": 9.11 + }, + "type": "audio", + "seat": "resetdigital" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/resetdigital/resetdigitaltest/exemplary/simple-banner.json b/adapters/resetdigital/resetdigitaltest/exemplary/simple-banner.json new file mode 100644 index 0000000000..2a0fcacd55 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/exemplary/simple-banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "audio": {}, + "video": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [{ + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "300", + "h": "250", + "seat": "resetdigital", + "html": "test markup" + }] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "test markup", + "cid": "1002088", + "crid": "1000763-1002088", + "id": "01", + "impid": "001", + "price": 1.00, + "w": 300, + "h": 250 + }, + "type": "banner", + "seat": "resetdigital" + }] + }] +} diff --git a/adapters/resetdigital/resetdigitaltest/exemplary/simple-video.json b/adapters/resetdigital/resetdigitaltest/exemplary/simple-video.json new file mode 100644 index 0000000000..ce4e1916fd --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/exemplary/simple-video.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [ + { + "id": "001", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + } + ], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "POST", + "body": { + "imps": [ + { + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": {}, + "video": { + "sizes": [ + [ + 900, + 250 + ] + ], + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + } + ], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [ + { + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "900", + "h": "250", + "seat": "resetdigital", + "html": "test markup" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "test markup", + "cid": "1002088", + "crid": "1000763-1002088", + "id": "01", + "impid": "001", + "price": 1.00, + "w": 900, + "h": 250 + }, + "type": "video", + "seat": "resetdigital" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-height.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-height.json new file mode 100644 index 0000000000..d1c40ffaba --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-height.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [{ + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "300", + "h": "123456789012345678901234567890123456789012345678901234567890", + "seat": "resetdigital", + "html": "test markup" + }] + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "strconv.ParseInt: parsing \"123456789012345678901234567890123456789012345678901234567890\": value out of range", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-width.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-width.json new file mode 100644 index 0000000000..8c118bfecd --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-bid-width.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [{ + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "123456789012345678901234567890123456789012345678901234567890", + "h": "250", + "seat": "resetdigital", + "html": "test markup" + }] + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "strconv.ParseInt: parsing \"123456789012345678901234567890123456789012345678901234567890\": value out of range", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/invalid-cur.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-cur.json new file mode 100644 index 0000000000..3dcf7a7656 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-cur.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "test-invalid-cur", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + } + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "test-invalid-cur", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 400, + "body": { + "error": "Unexpected status code: 400. Run with request.debug = 1 for more info" + } + } + }], + "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/resetdigital/resetdigitaltest/supplemental/invalid-device.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-device.json new file mode 100644 index 0000000000..7b0fc8c264 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-device.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-invalid-device", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ] + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "test-invalid-device", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 400, + "body": { + "error": "Unexpected status code: 400. Run with request.debug = 1 for more info" + } + } + }], + "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/resetdigital/resetdigitaltest/supplemental/invalid-media-def.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-media-def.json new file mode 100644 index 0000000000..0c24e666b9 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-media-def.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500, + "debug": 1 + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "failed to find matching imp for bid 001", + "comparison": "literal" + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/invalid-placement-id.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-placement-id.json new file mode 100644 index 0000000000..14959b36d1 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-placement-id.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placement_id": 1 + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500, + "debug": 1 + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field ImpExtResetDigital.placement_id of type string", + "comparison": "literal" + } + ], + "expectedBidResponses": [] + } diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/invalid-video-dim.json b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-video-dim.json new file mode 100644 index 0000000000..4e1f43d13f --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/invalid-video-dim.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "video": { + "w": 0, + "h": 480, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500, + "debug": 1 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": {}, + "video": { + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 204, + "body": { + "error": "Unexpected status code: 204. Run with request.debug = 1 for more info" + } + } + }], + "expectedBidResponses": [] + } diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/missing-currency.json b/adapters/resetdigital/resetdigitaltest/supplemental/missing-currency.json new file mode 100644 index 0000000000..bd438f7857 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/missing-currency.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "001", + "banner": { + "h": 300, + "w": 250 + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "12345", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 250, + 300 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [{ + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "300", + "h": "250", + "seat": "resetdigital", + "html": "test markup" + }] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "test markup", + "cid": "1002088", + "crid": "1000763-1002088", + "id": "01", + "impid": "001", + "price": 1.00, + "w": 300, + "h": 250 + }, + "type": "banner", + "seat": "resetdigital" + }] + }] +} diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/multi-format.json b/adapters/resetdigital/resetdigitaltest/supplemental/multi-format.json new file mode 100644 index 0000000000..4de554752f --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/multi-format.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-multi-format", + "imp": [{ + "id": "001", + "banner": { + "w": 300, + "h": 600 + }, + "video": { + "w": 900, + "h": 250, + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "test-multi-format", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": { + "sizes": [ + [ + 300, + 600 + ] + ] + }, + "video": {}, + "audio": {} + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "bids": [{ + "bid_id": "01", + "imp_id": "001", + "cpm": 1.00, + "cid": "1002088", + "crid": "1000763-1002088", + "adid": "1002088", + "w": "300", + "h": "250", + "seat": "resetdigital", + "html": "" + }] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "", + "cid": "1002088", + "crid": "1000763-1002088", + "id": "01", + "impid": "001", + "price": 1.00, + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "resetdigital" + }] + }] + } + \ No newline at end of file diff --git a/adapters/resetdigital/resetdigitaltest/supplemental/unknown-media.json b/adapters/resetdigital/resetdigitaltest/supplemental/unknown-media.json new file mode 100644 index 0000000000..5d9fb57363 --- /dev/null +++ b/adapters/resetdigital/resetdigitaltest/supplemental/unknown-media.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-unknown-media-type", + "imp": [{ + "id": "001", + "audio": { + "mimes": ["audio/mpeg"] + }, + "ext": { + "bidder": { + "placement_id": "placement-id-1" + } + } + }], + "site": { + "domain": "https://test.com", + "page": "https://test.com/2016/06/12" + }, + "cur": [ + "USD" + ] + }, + "httpCalls": [{ + "expectedRequest": { + "method": "POST", + "body": { + "imps": [{ + "bid_id": "test-unknown-media-type", + "ext": { + "gpid": "" + }, + "imp_id": "001", + "media_types": { + "banner": {}, + "video": {}, + "audio": { + "mimes": ["audio/mpeg"] + } + }, + "zone_id": { + "placementId": "placement-id-1" + } + }], + "site": { + "domain": "https://test.com", + "referrer": "https://test.com/2016/06/12" + } + }, + "impIDs": [ + "001" + ] + }, + "mockResponse": { + "status": 400, + "body": { + "error": "unknown media type for bid imp ID 003" + } + } + }], + "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/exchange/adapter_builders.go b/exchange/adapter_builders.go index 97a03dde2a..80028cb727 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -165,6 +165,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/qt" "github.com/prebid/prebid-server/v3/adapters/readpeak" "github.com/prebid/prebid-server/v3/adapters/relevantdigital" + "github.com/prebid/prebid-server/v3/adapters/resetdigital" "github.com/prebid/prebid-server/v3/adapters/revcontent" "github.com/prebid/prebid-server/v3/adapters/richaudience" "github.com/prebid/prebid-server/v3/adapters/rise" @@ -398,6 +399,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderQT: qt.Builder, openrtb_ext.BidderReadpeak: readpeak.Builder, openrtb_ext.BidderRelevantDigital: relevantdigital.Builder, + openrtb_ext.BidderResetDigital: resetdigital.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index fd7893a5a5..753f59526c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -183,6 +183,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderQT, BidderReadpeak, BidderRelevantDigital, + BidderResetDigital, BidderRevcontent, BidderRichaudience, BidderRise, @@ -520,6 +521,7 @@ const ( BidderQT BidderName = "qt" BidderReadpeak BidderName = "readpeak" BidderRelevantDigital BidderName = "relevantdigital" + BidderResetDigital BidderName = "resetdigital" BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" diff --git a/openrtb_ext/imp_resetdigital.go b/openrtb_ext/imp_resetdigital.go new file mode 100644 index 0000000000..6e3f800ab9 --- /dev/null +++ b/openrtb_ext/imp_resetdigital.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtResetDigital struct { + PlacementID string `json:"placement_id"` +} diff --git a/static/bidder-info/resetdigital.yaml b/static/bidder-info/resetdigital.yaml new file mode 100644 index 0000000000..db4e70ae76 --- /dev/null +++ b/static/bidder-info/resetdigital.yaml @@ -0,0 +1,14 @@ +endpoint: http://b-us-east14.resetdigital.co:9001 +maintainer: + email: biddersupport@resetdigital.co +capabilities: + site: + mediaTypes: + - banner + - video + - audio +gvlVendorID: 1162 +userSync: + redirect: + url: https://sync.resetdigital.co/csync?redir={{.RedirectURL}} + userMacro: '$USER_ID' diff --git a/static/bidder-params/resetdigital.json b/static/bidder-params/resetdigital.json new file mode 100644 index 0000000000..4d0538893f --- /dev/null +++ b/static/bidder-params/resetdigital.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Reset Digital Adapter Params", + "description": "A schema which validates params accepted by the Reset Digital adapter", + "type": "object", + "properties": { + "placement_id": { + "type": "string", + "minLength": 1, + "description": "Placement ID for the Reset Digital ad unit. This is the identifier for the ad unit on the Reset Digital platform, and its optional" + } + } +}