From bd43afbca80114e76d1b14a2843b71f8a5f62894 Mon Sep 17 00:00:00 2001 From: CPMStar Date: Thu, 16 Jan 2020 08:14:14 -0800 Subject: [PATCH] Added new adapter for CPMStar ad network banners and video (#1159) --- adapters/cpmstar/cpmstar.go | 161 ++++++++++++++++++ adapters/cpmstar/cpmstar_test.go | 11 ++ .../exemplary/banner-and-video.json | 154 +++++++++++++++++ .../cpmstar/cpmstartest/exemplary/banner.json | 100 +++++++++++ .../cpmstar/cpmstartest/exemplary/video.json | 55 ++++++ .../cpmstartest/supplemental/audio.json | 25 +++ .../supplemental/explicit-dimensions.json | 58 +++++++ .../invalid-response-no-bids.json | 50 ++++++ .../invalid-response-unmarshall-error.json | 66 +++++++ .../cpmstartest/supplemental/native.json | 25 +++ .../supplemental/no-imps-in-request.json | 18 ++ .../supplemental/server-error-code.json | 53 ++++++ .../supplemental/server-no-content.json | 45 +++++ .../supplemental/wrong-impression-ext.json | 26 +++ .../wrong-impression-mapping.json | 77 +++++++++ adapters/cpmstar/params_test.go | 54 ++++++ adapters/cpmstar/usersync.go | 13 ++ adapters/cpmstar/usersync_test.go | 25 +++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_cpmstar.go | 6 + static/bidder-info/cpmstar.yaml | 11 ++ static/bidder-params/cpmstar.json | 19 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 26 files changed, 1061 insertions(+) create mode 100644 adapters/cpmstar/cpmstar.go create mode 100644 adapters/cpmstar/cpmstar_test.go create mode 100644 adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json create mode 100644 adapters/cpmstar/cpmstartest/exemplary/banner.json create mode 100644 adapters/cpmstar/cpmstartest/exemplary/video.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/audio.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/native.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/server-error-code.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/server-no-content.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json create mode 100644 adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json create mode 100644 adapters/cpmstar/params_test.go create mode 100644 adapters/cpmstar/usersync.go create mode 100644 adapters/cpmstar/usersync_test.go create mode 100644 openrtb_ext/imp_cpmstar.go create mode 100644 static/bidder-info/cpmstar.yaml create mode 100644 static/bidder-params/cpmstar.json diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go new file mode 100644 index 00000000000..ef6abe70cb7 --- /dev/null +++ b/adapters/cpmstar/cpmstar.go @@ -0,0 +1,161 @@ +package cpmstar + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type Adapter struct { + endpoint string +} + +func (a *Adapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var adapterRequests []*adapters.RequestData + + if err := preprocess(request); err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterReq, err := a.makeRequest(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + adapterRequests = append(adapterRequests, adapterReq) + + return adapterRequests, errs +} + +func (a *Adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { + var err error + + jsonBody, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: jsonBody, + Headers: headers, + }, nil +} + +func preprocess(request *openrtb.BidRequest) error { + if len(request.Imp) == 0 { + return &errortypes.BadInput{ + Message: "No Imps in Bid Request", + } + } + for i := 0; i < len(request.Imp); i++ { + var imp = &request.Imp[i] + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + if err := validateImp(imp); err != nil { + return err + } + + var extImp openrtb_ext.ExtImpCpmstar + if err := json.Unmarshal(bidderExt.Bidder, &extImp); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + imp.Ext = bidderExt.Bidder + } + + return nil +} + +func validateImp(imp *openrtb.Imp) error { + if imp.Banner == nil && imp.Video == nil { + return &errortypes.BadInput{ + Message: "Only Banner and Video bid-types are supported at this time", + } + } + return nil +} + +// MakeBids based on cpmstar server response +func (a *Adapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected HTTP status code: %d. Run with request.debug = 1 for more info", responseData.StatusCode), + }} + } + + var bidResponse openrtb.BidResponse + + if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + if len(bidResponse.SeatBid) == 0 { + return nil, nil + } + + rv := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid)) + var errors []error + + for _, seatbid := range bidResponse.SeatBid { + for _, bid := range seatbid.Bid { + foundMatchingBid := false + bidType := openrtb_ext.BidTypeBanner + for _, imp := range bidRequest.Imp { + if imp.ID == bid.ImpID { + foundMatchingBid = true + if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + } + break + } + } + + if foundMatchingBid { + rv.Bids = append(rv.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } else { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid id='%s' could not find valid impid='%s'", bid.ID, bid.ImpID), + }) + } + } + } + return rv, errors +} + +func NewCpmstarBidder(endpoint string) *Adapter { + return &Adapter{ + endpoint: endpoint, + } +} diff --git a/adapters/cpmstar/cpmstar_test.go b/adapters/cpmstar/cpmstar_test.go new file mode 100644 index 00000000000..0a7f43f5ee7 --- /dev/null +++ b/adapters/cpmstar/cpmstar_test.go @@ -0,0 +1,11 @@ +package cpmstar + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "cpmstartest", NewCpmstarBidder("//host")) +} diff --git a/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json b/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..d4dcb4b8677 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/banner-and-video.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + }, + { + "id": "test-video-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 654 + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + }, + { + "id": "test-video-imp-id", + "video": { + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 640 + }, + "ext": { + "placementId": 154, + "subpoolId": 654 + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "cpmstar", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-video-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "banner" + }, + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29484110", + "adomain": [ + "sample.com" + ], + "cid": "958", + "crid": "29484110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/exemplary/banner.json b/adapters/cpmstar/cpmstartest/exemplary/banner.json new file mode 100644 index 00000000000..0bbe3060a63 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/banner.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "cpmstar", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-banner-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/exemplary/video.json b/adapters/cpmstar/cpmstartest/exemplary/video.json new file mode 100644 index 00000000000..a0213cbdac1 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/exemplary/video.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-video-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/audio.json b/adapters/cpmstar/cpmstartest/supplemental/audio.json new file mode 100644 index 00000000000..18ff171c7f5 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/audio.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-audio-request", + "imp": [ + { + "id": "unsupported-audio-imp", + "audio": { + "mimes": ["video/mp4"] + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Only Banner and Video bid-types are supported at this time", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json b/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json new file mode 100644 index 00000000000..b8aad87514d --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/explicit-dimensions.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "bidder": { + "placementId": 154, + "subpoolId": 123 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 400 + }, + "ext": { + "placementId": 154, + "subpoolId": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json new file mode 100644 index 00000000000..a3cb9114caa --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-no-bids.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + ], + "cur": "USD" + } + } + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json new file mode 100644 index 00000000000..e20acefe2c3 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/invalid-response-unmarshall-error.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "w": 90, + "h": 728 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "some_test_ad", + "banner": { + "h": 728, + "w": 90 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [ + { + "bid": [ + { + "id": "uuid", + "impid": "some_test_ad", + "w": "728", + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/native.json b/adapters/cpmstar/cpmstartest/supplemental/native.json new file mode 100644 index 00000000000..a02db78db0f --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/native.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "native": { + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Only Banner and Video bid-types are supported at this time", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json b/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json new file mode 100644 index 00000000000..274a34227cf --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/no-imps-in-request.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site?with=some¶meters=here" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "No Imps in Bid Request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json b/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json new file mode 100644 index 00000000000..21e697d13f1 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/server-error-code.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 600, + "h": 300 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected HTTP status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } diff --git a/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json b/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json new file mode 100644 index 00000000000..e56db95f8e8 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/server-no-content.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": 154 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "some_test_auction", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "placementId": 154 + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] + } diff --git a/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json new file mode 100644 index 00000000000..1e8de0acc66 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-ext.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "rqid", + "imp": [ + { + "id": "impid", + "video": { + "w": 100, + "h": 200 + }, + "ext": { + "bidder": { + "placementId": "BOGUS" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field ExtImpCpmstar.placementId of type int", + "comparison": "literal" + } + ] +} diff --git a/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..6ab02db0ca7 --- /dev/null +++ b/adapters/cpmstar/cpmstartest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "poolid": 154, + "subpoolid": 123 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "//host", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "poolid": 154, + "subpoolid": 123 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "BOGUS-IMPID", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid id='test-bid-id' could not find valid impid='BOGUS-IMPID'", + "comparison": "regex" + } +] +} \ No newline at end of file diff --git a/adapters/cpmstar/params_test.go b/adapters/cpmstar/params_test.go new file mode 100644 index 00000000000..cee471a8322 --- /dev/null +++ b/adapters/cpmstar/params_test.go @@ -0,0 +1,54 @@ +package cpmstar + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/cpmstar.json +// These also validate the format of the external API: request.imp[i].ext.cpmstar +// TestValidParams makes sure that the Cpmstar schema accepts all imp.ext fields which we intend to support. + +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.BidderCpmstar, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Cpmstar params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Cpmstar 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.BidderCpmstar, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": 154}`, + `{"placementId": 154, "subpoolId": 123}`, +} + +var invalidParams = []string{ + `{}`, + `null`, + `true`, + `154`, + `{"placementId": "154"}`, // placementId should be numeric + `{"placementId": 154, "subpoolId": "123"}`, // placementId and subpoolId should both be numeric + `{"invalid_param": 123}`, +} diff --git a/adapters/cpmstar/usersync.go b/adapters/cpmstar/usersync.go new file mode 100644 index 00000000000..9c864e24ce3 --- /dev/null +++ b/adapters/cpmstar/usersync.go @@ -0,0 +1,13 @@ +package cpmstar + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +//NewCpmstarSyncer : +func NewCpmstarSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("cpmstar", 0, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/cpmstar/usersync_test.go b/adapters/cpmstar/usersync_test.go new file mode 100644 index 00000000000..dae55e6302e --- /dev/null +++ b/adapters/cpmstar/usersync_test.go @@ -0,0 +1,25 @@ +package cpmstar + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestCpmstarSyncer(t *testing.T) { + syncURL := "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewCpmstarSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://server.cpmstar.com/usersync.aspx?gdpr=&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D%26gdpr_consent%3D%26us_privacy%3D%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.False(t, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 8dc0bb14526..d9b6ee6e55d 100644 --- a/config/config.go +++ b/config/config.go @@ -496,6 +496,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") @@ -678,6 +679,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24") + v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 5795ad2c197..95f5b7f5882 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -22,6 +22,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" + "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" @@ -79,6 +80,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(), openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint), + openrtb_ext.BidderCpmstar: cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint), openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f02a16b4350..7a3f24eb07f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -33,6 +33,7 @@ const ( BidderBrightroll BidderName = "brightroll" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" @@ -87,6 +88,7 @@ var BidderMap = map[string]BidderName{ "brightroll": BidderBrightroll, "consumable": BidderConsumable, "conversant": BidderConversant, + "cpmstar": BidderCpmstar, "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "engagebdr": BidderEngageBDR, diff --git a/openrtb_ext/imp_cpmstar.go b/openrtb_ext/imp_cpmstar.go new file mode 100644 index 00000000000..0b74f4d437d --- /dev/null +++ b/openrtb_ext/imp_cpmstar.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpCpmstar struct { + PoolId int `json:"placementId"` + SubPoolId int `json:"subpoolId,omitempty"` +} diff --git a/static/bidder-info/cpmstar.yaml b/static/bidder-info/cpmstar.yaml new file mode 100644 index 00000000000..097dfddd5b0 --- /dev/null +++ b/static/bidder-info/cpmstar.yaml @@ -0,0 +1,11 @@ +maintainer: + email: "prebid@cpmstar.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/cpmstar.json b/static/bidder-params/cpmstar.json new file mode 100644 index 00000000000..576b503e793 --- /dev/null +++ b/static/bidder-params/cpmstar.json @@ -0,0 +1,19 @@ + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Cpmstar Adapter Params", + "description": "Schema to validate params accepted by the Cpmstar adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "Cpmstar-specific ID for ad pool" + }, + "subpoolId": { + "type": "integer", + "description": "Cpmstar-specific ID for ad subpool" + } + }, + "required": ["placementId"] + } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 2bfa5514063..5447cd28800 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -19,6 +19,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/consumable" "github.com/prebid/prebid-server/adapters/conversant" + "github.com/prebid/prebid-server/adapters/cpmstar" "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" @@ -75,6 +76,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 3fb1d4d7fa4..ded8fd2bd78 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBrightroll): syncConfig, string(openrtb_ext.BidderConsumable): syncConfig, string(openrtb_ext.BidderConversant): syncConfig, + string(openrtb_ext.BidderCpmstar): syncConfig, string(openrtb_ext.BidderDatablocks): syncConfig, string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig,