diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go new file mode 100644 index 00000000000..ac2b314b96f --- /dev/null +++ b/adapters/pulsepoint/params_test.go @@ -0,0 +1,59 @@ +package pulsepoint + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/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-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderPulsepoint, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected pulsepoint params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the pubmatic schema rejects all the imp.ext fields we don't support. +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.BidderPulsepoint, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected pulsepoint params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"cp":1000, "ct": 2000}`, + `{"cp":1001, "ct": 2001}`, + `{"cp":1001, "ct": 2001, "cf": "1x1"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"cp":"1000"}`, + `{"ct":"1000"}`, + `{"cp":1000}`, + `{"ct":1000}`, + `{"cp":1000, "ct":"1000"}`, + `{"cp":1000, "ct": "abcd"}`, + `{"cp":"abcd", "ct": 1000}`, + `{"cp":"1000.2", "ct": "1000.1"}`, +} diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 23f11f9a40a..b07a2cba66f 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,17 +1,19 @@ package pulsepoint import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" + + "bytes" + "context" + "io/ioutil" "strings" "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -23,15 +25,169 @@ type PulsePointAdapter struct { URI string } +// Builds an instance of PulsePointAdapter +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &PulsePointAdapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + + var err error + pubID := "" + imps := make([]openrtb.Imp, 0, len(request.Imp)) + for i := 0; i < len(request.Imp); i++ { + imp := request.Imp[i] + var bidderExt adapters.ExtImpBidder + if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + var pulsepointExt openrtb_ext.ExtImpPulsePoint + if err = json.Unmarshal(bidderExt.Bidder, &pulsepointExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + // parse pubid and keep it for reference + if pubID == "" && pulsepointExt.PubID > 0 { + pubID = strconv.Itoa(pulsepointExt.PubID) + } + // tag id to be sent + imp.TagID = strconv.Itoa(pulsepointExt.TagID) + imps = append(imps, imp) + } + + // verify there are valid impressions + if len(imps) == 0 { + return nil, errs + } + + // add the publisher id from ext to the site.pub.id or app.pub.id + if request.Site != nil { + site := *request.Site + if site.Publisher != nil { + publisher := *site.Publisher + publisher.ID = pubID + site.Publisher = &publisher + } else { + site.Publisher = &openrtb.Publisher{ID: pubID} + } + request.Site = &site + } else if request.App != nil { + app := *request.App + if app.Publisher != nil { + publisher := *app.Publisher + publisher.ID = pubID + app.Publisher = &publisher + } else { + app.Publisher = &openrtb.Publisher{ID: pubID} + } + request.App = &app + } + + request.Imp = imps + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + // passback + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + // bad requests + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + // error + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + // parse response + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + // map imps by id + impsByID := make(map[string]openrtb.Imp) + for i := 0; i < len(internalRequest.Imp); i++ { + impsByID[internalRequest.Imp[i].ID] = internalRequest.Imp[i] + } + + var errs []error + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + imp := impsByID[bid.ImpID] + bidType := getBidType(imp) + if &imp != nil && bidType != "" { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + } + return bidResponse, errs +} + +func getBidType(imp openrtb.Imp) openrtb_ext.BidType { + // derive the bidtype purely from the impression itself + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative + } + return "" +} + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + +func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { + a := adapters.NewHTTPAdapter(config) + + return &PulsePointAdapter{ + http: a, + URI: uri, + } +} + // used for cookies and such func (a *PulsePointAdapter) Name() string { return "pulsepoint" } -func (a *PulsePointAdapter) SkipNoCookies() bool { - return false -} - // parameters for pulsepoint adapter. type PulsepointParams struct { PublisherId int `json:"cp"` @@ -39,6 +195,10 @@ type PulsepointParams struct { AdSize string `json:"cf"` } +func (a *PulsePointAdapter) SkipNoCookies() bool { + return false +} + func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) @@ -195,11 +355,6 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde return bids, nil } -func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PulsePointAdapter{ - http: a, - URI: uri, - } -} +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index 3eede431a12..33023d0500a 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,26 +1,43 @@ package pulsepoint import ( + "encoding/json" + "net/http" + "testing" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" + "bytes" "context" - "encoding/json" "fmt" "io/ioutil" - "net/http" "net/http/httptest" - "testing" "time" - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/usersync" ) +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderPulsepoint, config.Adapter{ + Endpoint: "http://bidder.pulsepoint.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) +} + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + /** * Verify adapter names are setup correctly. */ @@ -75,7 +92,6 @@ func TestPulsePointOpenRTBRequest(t *testing.T) { bidder := req.Bidders[0] adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) adapter.Call(ctx, req, bidder) - fmt.Println(service.LastBidRequest) adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) @@ -290,3 +306,7 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service.LastBidRequest = &lastBidRequest return service } + +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json new file mode 100644 index 00000000000..e99ca648572 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json new file mode 100644 index 00000000000..d4cf797d219 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json new file mode 100644 index 00000000000..6e16f997661 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app" + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "app": { + "bundle": "com.publisher.app", + "publisher": { + "id": "1234" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json new file mode 100644 index 00000000000..6d658a2423a --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html" + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json new file mode 100644 index 00000000000..1a10355344c --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }, { + "id": "video-1", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }, { + "id": "video-1", + "tagid": "2001", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, { + "id": "video-1-bid", + "impid": "video-1", + "price": 4.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "201" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "banner-1-bid", + "impid": "banner-1", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "banner" + }, { + "bid": { + "id": "video-1-bid", + "impid": "video-1", + "price": 4.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "201" + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/native.json b/adapters/pulsepoint/pulsepointtest/exemplary/native.json new file mode 100644 index 00000000000..72c8532d783 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/native.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "native-1", + "native": { + "ver": "1.0", + "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}" + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "native-1", + "tagid": "2001", + "native": { + "ver": "1.0", + "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}" + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "native-1-bid", + "impid": "native-1", + "price": 3.5, + "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}", + "adomain": [ + "advertiser.com" + ], + "crid": "20" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "native-1-bid", + "impid": "native-1", + "price": 3.5, + "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}", + "adomain": [ + "advertiser.com" + ], + "crid": "20" + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/video.json b/adapters/pulsepoint/pulsepointtest/exemplary/video.json new file mode 100644 index 00000000000..980f49f2d14 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/exemplary/video.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "video-1", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "some-request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "video-1", + "tagid": "2001", + "video": { + "w": 400, + "h": 300, + "mimes": ["video/x-flv"], + "minduration": 20 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 2001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "video-1-bid", + "impid": "video-1", + "price": 3.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "id": "video-1-bid", + "impid": "video-1", + "price": 3.5, + "adm": "Creative", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/params/race/banner.json b/adapters/pulsepoint/pulsepointtest/params/race/banner.json index 1f2e34df47c..bc3e9371a5f 100644 --- a/adapters/pulsepoint/pulsepointtest/params/race/banner.json +++ b/adapters/pulsepoint/pulsepointtest/params/race/banner.json @@ -1,5 +1,4 @@ { - "cf": "300X250", "cp": 512379, "ct": 486653 } diff --git a/adapters/pulsepoint/pulsepointtest/params/race/native.json b/adapters/pulsepoint/pulsepointtest/params/race/native.json new file mode 100644 index 00000000000..57867bc27c0 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "cp": 512379, + "ct": 486655 +} diff --git a/adapters/pulsepoint/pulsepointtest/params/race/video.json b/adapters/pulsepoint/pulsepointtest/params/race/video.json new file mode 100644 index 00000000000..d8ef37eb793 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "cp": 512379, + "ct": 486654 +} diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json new file mode 100644 index 00000000000..b5209ed4bbe --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-2", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": "300", + "h": "250" + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type uint64", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json new file mode 100644 index 00000000000..3fe1422bb00 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Bad user input: HTTP status 400", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json new file mode 100644 index 00000000000..2d0c74d39d7 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": "1234", + "ct": "1001" + } + } + }] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [{ + "value": "json: cannot unmarshal string into Go struct field ExtImpPulsePoint.cp of type int", + "comparison": "literal" + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json new file mode 100644 index 00000000000..7de9bd3c264 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": "" + }] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [{ + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/error.json b/adapters/pulsepoint/pulsepointtest/supplemental/error.json new file mode 100644 index 00000000000..c469e3bc2f9 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/error.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Bad server response: HTTP status 503", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json new file mode 100644 index 00000000000..30b57e1ef60 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "response-id", + "seatbid": [{ + "bid": [{ + "id": "banner-1-bid", + "impid": "banner-2", + "price": 3.5, + "adm": "
Creative
", + "adomain": [ + "advertiser.com" + ], + "crid": "20", + "w": 300, + "h": 250 + }], + "seat": "pulsepoint-seat" + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "bids": [] + }] +} \ No newline at end of file diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/passback.json b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json new file mode 100644 index 00000000000..434f5a7a141 --- /dev/null +++ b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "123456789", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://bidder.pulsepoint.com", + "body": { + "id": "request-id", + "site": { + "page": "http://publisher.com/index.html", + "publisher": { + "id": "1234", + "name": "publisher.com" + } + }, + "imp": [{ + "id": "banner-1", + "tagid": "1001", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "cp": 1234, + "ct": 1001 + } + } + }] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 31342a4c5ff..8da57d6ec59 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -65,6 +65,7 @@ import ( "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" + "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/revcontent" "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" @@ -166,6 +167,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, + openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 6e12ed00536..4d92fe9fe9c 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -8,7 +8,6 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/lifestreet" - "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -58,7 +57,7 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.Bidder } // Ignore Legacy Bidders - if bidderName == openrtb_ext.BidderLifestreet || bidderName == openrtb_ext.BidderPulsepoint { + if bidderName == openrtb_ext.BidderLifestreet { continue } @@ -99,12 +98,6 @@ func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos a bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter) } - // Pulsepoint - if infos[string(openrtb_ext.BidderPulsepoint)].Status == adapters.StatusActive { - adapter := pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderPulsepoint)].Endpoint) - bidders[openrtb_ext.BidderPulsepoint] = adaptLegacyAdapter(adapter) - } - return bidders } diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index d7059648877..20402e1e68c 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -10,7 +10,6 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/lifestreet" - "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -214,9 +213,9 @@ func TestBuildBidders(t *testing.T) { }, { description: "Success - Ignores Legacy", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}, "pulsepoint": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive, "pulsepoint": infoActive}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder, openrtb_ext.BidderPulsepoint: inconsequentialBuilder}, + adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}}, + bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive}, + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), }, @@ -267,7 +266,6 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) { cfg := config.Adapter{Endpoint: "anyEndpoint"} expectedLifestreet := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} - expectedPulsepoint := &adaptedAdapter{pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} testCases := []struct { description string @@ -276,29 +274,23 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) { expected map[openrtb_ext.BidderName]adaptedBidder }{ { - description: "All Active", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoActive}, - expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet, "pulsepoint": expectedPulsepoint}, + description: "Active", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive}, + expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, }, { - description: "All Disabled", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoDisabled, "pulsepoint": infoDisabled}, + description: "Disabled", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoDisabled}, expected: map[openrtb_ext.BidderName]adaptedBidder{}, }, { - description: "All Unknown", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoUnknown, "pulsepoint": infoUnknown}, + description: "Unknown", + adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, + bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoUnknown}, expected: map[openrtb_ext.BidderName]adaptedBidder{}, }, - { - description: "Mixed", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoUnknown}, - expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, - }, } for _, test := range testCases { diff --git a/openrtb_ext/imp_pulsepoint.go b/openrtb_ext/imp_pulsepoint.go new file mode 100644 index 00000000000..c168c80f1bb --- /dev/null +++ b/openrtb_ext/imp_pulsepoint.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.pulsepoint +// PubId/TagId are mandatory params + +type ExtImpPulsePoint struct { + PubID int `json:"cp"` + TagID int `json:"ct"` +} diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 716e453000e..056a0bf3d7c 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -4,6 +4,12 @@ capabilities: app: mediaTypes: - banner + - video + - audio + - native site: mediaTypes: - banner + - video + - audio + - native diff --git a/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json index c4704c3b42e..673fd2efd6f 100644 --- a/static/bidder-params/pulsepoint.json +++ b/static/bidder-params/pulsepoint.json @@ -11,12 +11,7 @@ "ct": { "type": "integer", "description": "An ID which identifies the ad slot being sold" - }, - "cf": { - "type": "string", - "pattern": "^[0-9]+[xX][0-9]+$", - "description": "The size of the ad slot being sold. This should be a string like 300X250" } }, - "required": ["cp", "ct", "cf"] + "required": ["cp", "ct"] }