From 07ae3c48193da053a900179c47fbea9ce3e695bd Mon Sep 17 00:00:00 2001 From: htang555 Date: Mon, 11 Nov 2019 10:52:47 -0500 Subject: [PATCH] Add Datablocks Adapter (#1095) * datablocks bid adapter * ttx * add test json * add coverage * redo ttx * formatted * better error handling * additional tests and recomended fixes --- adapters/datablocks/datablocks.go | 188 ++++++++++++++++++ adapters/datablocks/datablocks_test.go | 11 + .../exemplary/multi-request.json | 160 +++++++++++++++ .../datablockstest/exemplary/native.json | 123 ++++++++++++ .../exemplary/simple-banner.json | 133 +++++++++++++ .../exemplary/simple-video.json | 138 +++++++++++++ .../datablockstest/params/race/banner.json | 4 + .../datablockstest/params/race/native.json | 4 + .../datablockstest/params/race/video.json | 4 + .../datablockstest/supplemental/bad-host.json | 33 +++ .../supplemental/bad-response-body.json | 88 ++++++++ .../supplemental/bad-server-response.json | 88 ++++++++ .../supplemental/bad-sourceId.json | 35 ++++ .../supplemental/missing-ext.json | 27 +++ .../supplemental/missing-extparam.json | 30 +++ .../supplemental/no-content-response.json | 82 ++++++++ adapters/datablocks/usersync.go | 14 ++ adapters/datablocks/usersync_test.go | 18 ++ config/config.go | 2 + exchange/adapter_map.go | 2 + macros/macros.go | 1 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_datablocks.go | 7 + static/bidder-info/datablocks.yaml | 13 ++ static/bidder-params/datablocks.json | 19 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 27 files changed, 1229 insertions(+) create mode 100644 adapters/datablocks/datablocks.go create mode 100644 adapters/datablocks/datablocks_test.go create mode 100644 adapters/datablocks/datablockstest/exemplary/multi-request.json create mode 100644 adapters/datablocks/datablockstest/exemplary/native.json create mode 100644 adapters/datablocks/datablockstest/exemplary/simple-banner.json create mode 100644 adapters/datablocks/datablockstest/exemplary/simple-video.json create mode 100644 adapters/datablocks/datablockstest/params/race/banner.json create mode 100644 adapters/datablocks/datablockstest/params/race/native.json create mode 100644 adapters/datablocks/datablockstest/params/race/video.json create mode 100644 adapters/datablocks/datablockstest/supplemental/bad-host.json create mode 100644 adapters/datablocks/datablockstest/supplemental/bad-response-body.json create mode 100644 adapters/datablocks/datablockstest/supplemental/bad-server-response.json create mode 100644 adapters/datablocks/datablockstest/supplemental/bad-sourceId.json create mode 100644 adapters/datablocks/datablockstest/supplemental/missing-ext.json create mode 100644 adapters/datablocks/datablockstest/supplemental/missing-extparam.json create mode 100644 adapters/datablocks/datablockstest/supplemental/no-content-response.json create mode 100644 adapters/datablocks/usersync.go create mode 100644 adapters/datablocks/usersync_test.go create mode 100644 openrtb_ext/imp_datablocks.go create mode 100644 static/bidder-info/datablocks.yaml create mode 100644 static/bidder-params/datablocks.json diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go new file mode 100644 index 00000000000..ec4ab6ceb2d --- /dev/null +++ b/adapters/datablocks/datablocks.go @@ -0,0 +1,188 @@ +package datablocks + +import ( + "encoding/json" + "fmt" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" + "text/template" +) + +type DatablocksAdapter struct { + EndpointTemplate template.Template +} + +func (a *DatablocksAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + errs := make([]error, 0, len(request.Imp)) + headers := http.Header{ + "Content-Type": {"application/json"}, + "Accept": {"application/json"}, + } + + // Pull the host and source ID info from the bidder params. + reqImps, err := splitImpressions(request.Imp) + + if err != nil { + errs = append(errs, err) + } + + requests := []*adapters.RequestData{} + + for reqExt, reqImp := range reqImps { + request.Imp = reqImp + reqJson, err := json.Marshal(request) + + if err != nil { + errs = append(errs, err) + continue + } + + urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)} + url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams) + + if err != nil { + errs = append(errs, err) + continue + } + + request := adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJson, + Headers: headers} + + requests = append(requests, &request) + } + + return requests, errs +} + +/* +internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) +*/ +func (a *DatablocksAdapter) MakeBids( + internalRequest *openrtb.BidRequest, + externalRequest *adapters.RequestData, + response *adapters.ResponseData, +) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode), + }} + } + + var bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponse() + bidResponse.Currency = bidResp.Cur + + for _, seatBid := range bidResp.SeatBid { + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaType(bid.ImpID, internalRequest.Imp), + }) + } + } + + return bidResponse, nil +} + +func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp, error) { + + var m = make(map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp) + + for _, imp := range imps { + bidderParams, err := getBidderParams(&imp) + if err != nil { + return nil, err + } + + v, ok := m[*bidderParams] + if ok { + m[*bidderParams] = append(v, imp) + } else { + m[*bidderParams] = []openrtb.Imp{imp} + } + } + + return m, nil +} + +func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpDatablocks, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()), + } + } + var datablocksExt openrtb_ext.ExtImpDatablocks + if err := json.Unmarshal(bidderExt.Bidder, &datablocksExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()), + } + } + + if datablocksExt.SourceId < 1 { + return nil, &errortypes.BadInput{ + Message: "Invalid/Missing SourceId", + } + } + + if len(datablocksExt.Host) < 1 { + return nil, &errortypes.BadInput{ + Message: "Invalid/Missing Host", + } + } + + return &datablocksExt, nil +} + +func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + + bidType := openrtb_ext.BidTypeBanner + + for _, imp := range imps { + if imp.ID == impID { + if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + break + } else if imp.Native != nil { + bidType = openrtb_ext.BidTypeNative + break + } else { + bidType = openrtb_ext.BidTypeBanner + break + } + } + } + + return bidType +} + +func NewDatablocksBidder(endpoint string) *DatablocksAdapter { + template, err := template.New("endpointTemplate").Parse(endpoint) + if err != nil { + glog.Fatal("Unable to parse endpoint url template") + return nil + } + + return &DatablocksAdapter{EndpointTemplate: *template} +} diff --git a/adapters/datablocks/datablocks_test.go b/adapters/datablocks/datablocks_test.go new file mode 100644 index 00000000000..28c42016087 --- /dev/null +++ b/adapters/datablocks/datablocks_test.go @@ -0,0 +1,11 @@ +package datablocks + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "datablockstest", NewDatablocksBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}")) +} diff --git a/adapters/datablocks/datablockstest/exemplary/multi-request.json b/adapters/datablocks/datablockstest/exemplary/multi-request.json new file mode 100644 index 00000000000..286e058a72e --- /dev/null +++ b/adapters/datablocks/datablockstest/exemplary/multi-request.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + },{ + "id": "some-impression-id2", + "banner": + { + "format": [{ + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + },{ + "id": "some-impression-id2", + "banner": + { + "format": [ + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-5-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/exemplary/native.json b/adapters/datablocks/datablockstest/exemplary/native.json new file mode 100644 index 00000000000..9487079c734 --- /dev/null +++ b/adapters/datablocks/datablockstest/exemplary/native.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "native": + { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}", + "ver": "1.1" + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "user": + { + "buyeruid": "4610943261" + }, + "at": 1, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "native": + { + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}", + "ver": "1.1" + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "user": + { + "buyeruid": "4610943261" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status":200, + "body": { + "id": "some-request-id", + "bidid": "183975330-3-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314346", + "impid": "some-impression-id", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://track.nutella.datablocks.net/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://impression.nutella.datablocks.net/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "ext": {} + }] + }], + "cur": "USD", + "ext": {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "2181314346", + "impid": "some-impression-id", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://track.nutella.datablocks.net/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://impression.nutella.datablocks.net/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "ext": {} + }, + "type":"native" + } + ] + }] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/exemplary/simple-banner.json b/adapters/datablocks/datablockstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..289549ab588 --- /dev/null +++ b/adapters/datablocks/datablockstest/exemplary/simple-banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-5-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314349", + "impid": "some-impression-id", + "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net
\"\"", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906299", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/exemplary/simple-video.json b/adapters/datablocks/datablockstest/exemplary/simple-video.json new file mode 100644 index 00000000000..6f8073107f3 --- /dev/null +++ b/adapters/datablocks/datablockstest/exemplary/simple-video.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": + { + "mimes":[ + "video/x-flv" + ], + "w": 500, + "h": 400, + "minduration": 30 + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": + { + "mimes":[ + "video/x-flv" + ], + "w": 500, + "h": 400, + "minduration": 30 + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 906295 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body": + { + "id": "some-request-id", + "bidid": "183975330-4-29038-2", + "seatbid": [ + { + "seat": "906295", + "bid": [ + { + "id": "2181314347", + "impid": "some-impression-id", + "nurl": "https://impression.nutella.datablocks.net/wm/267237/?fcid=2181314347", + "price": 13.37, + "cid": "906293", + "adid": "906297", + "crid": "906729", + "w": 500, + "h": 400, + "ext": + { + "type": "CPM" + } + }] + }], + "cur": "USD", + "ext": + {} + } + } + }], + + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": + { + "id": "2181314347", + "impid": "some-impression-id", + "price": 13.37, + "nurl": "https://impression.nutella.datablocks.net/wm/267237/?fcid=2181314347", + "adid": "906297", + "cid": "906293", + "crid": "906729", + "w": 500, + "h": 400, + "ext": + { + "type": "CPM" + } + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/params/race/banner.json b/adapters/datablocks/datablockstest/params/race/banner.json new file mode 100644 index 00000000000..0433de8388d --- /dev/null +++ b/adapters/datablocks/datablockstest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "search.nutella.datablocks.net" +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/params/race/native.json b/adapters/datablocks/datablockstest/params/race/native.json new file mode 100644 index 00000000000..0433de8388d --- /dev/null +++ b/adapters/datablocks/datablockstest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "search.nutella.datablocks.net" +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/params/race/video.json b/adapters/datablocks/datablockstest/params/race/video.json new file mode 100644 index 00000000000..0433de8388d --- /dev/null +++ b/adapters/datablocks/datablockstest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "sourceId": 906295, + "host": "search.nutella.datablocks.net" +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/supplemental/bad-host.json b/adapters/datablocks/datablockstest/supplemental/bad-host.json new file mode 100644 index 00000000000..cee5efbe760 --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/bad-host.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "bad-host-test", + "imp": [ + { + "id": "bad-host-test-imp", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": + { + "bidder": + { + "host": "", + "sourceId": 123 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid/Missing Host", + "comparison": "literal" + } + ] +} diff --git a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json new file mode 100644 index 00000000000..c7ecf3c0eaa --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 200, + "body":"foobar" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/supplemental/bad-server-response.json b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..da2924f6f21 --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 500, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "ERR, response with status 500", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json new file mode 100644 index 00000000000..08f2fddf568 --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "bad-sourceId-test", + "imp": [ + { + "id": "bad-sourceId-test-imp", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 0 + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid/Missing SourceId", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/datablocks/datablockstest/supplemental/missing-ext.json b/adapters/datablocks/datablockstest/supplemental/missing-ext.json new file mode 100644 index 00000000000..68d29e880b9 --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/missing-ext.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "missing-extbid-test", + "imp": [ + { + "id": "missing-extbid-test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Missing bidder ext: unexpected end of JSON input", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/datablocks/datablockstest/supplemental/missing-extparam.json b/adapters/datablocks/datablockstest/supplemental/missing-extparam.json new file mode 100644 index 00000000000..d272cd5347c --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/missing-extparam.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "missing-extbid-test", + "imp": [ + { + "id": "missing-extbid-test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "sourceId":54326 + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Cannot Resolve host or sourceId: unexpected end of JSON input", + "comparison": "literal" + } + ] + + +} diff --git a/adapters/datablocks/datablockstest/supplemental/no-content-response.json b/adapters/datablocks/datablockstest/supplemental/no-content-response.json new file mode 100644 index 00000000000..7fc1b75c3ad --- /dev/null +++ b/adapters/datablocks/datablockstest/supplemental/no-content-response.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": + { + "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "body": + { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": + { + "format": [ + { + "w": 300, + "h": 250 + }] + }, + "ext": + { + "bidder": + { + "host": "search.nutella.datablocks.net", + "sourceId": 123 + } + } + }], + "site": + { + "page": "prebid.org" + }, + "device": + { + "ip": "8.8.8.10" + }, + "at": 1, + "tmax": 500 + } + }, + "mockResponse": + { + "status": 204 + } + }], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/datablocks/usersync.go b/adapters/datablocks/usersync.go new file mode 100644 index 00000000000..c8ec92aa857 --- /dev/null +++ b/adapters/datablocks/usersync.go @@ -0,0 +1,14 @@ +package datablocks + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +const datablocksGDPRVendorID = uint16(14) + +func NewDatablocksSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("datablocks", 14, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/datablocks/usersync_test.go b/adapters/datablocks/usersync_test.go new file mode 100644 index 00000000000..e31fff749fc --- /dev/null +++ b/adapters/datablocks/usersync_test.go @@ -0,0 +1,18 @@ +package datablocks + +import ( + "github.com/stretchr/testify/assert" + "testing" + "text/template" +) + +func TestDatablocksSyncer(t *testing.T) { + temp := template.Must(template.New("sync-template").Parse("https://search.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")) + syncer := NewDatablocksSyncer(temp) + syncInfo, err := syncer.GetUsersyncInfo("1", "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw") + assert.NoError(t, err) + assert.Equal(t, "https://search.v5prebid.datablocks.net/s2ssync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, datablocksGDPRVendorID, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 90b38c62510..e2a8170bb32 100644 --- a/config/config.go +++ b/config/config.go @@ -481,6 +481,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&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.BidderDatablocks, "https://search.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3F"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderFacebook doesn't have a good default. @@ -653,6 +654,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") 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.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24") v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 88f6c40f727..a1806ef7f32 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.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/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -69,6 +70,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.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint), openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint), openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), diff --git a/macros/macros.go b/macros/macros.go index 03da633b472..dd5832c35b9 100644 --- a/macros/macros.go +++ b/macros/macros.go @@ -10,6 +10,7 @@ type EndpointTemplateParams struct { Host string PublisherID string ZoneID string + SourceId string } // UserSyncTemplateParams specifies params for an user sync URL template diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index d5f125777b4..1e185895380 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -32,6 +32,7 @@ const ( BidderBrightroll BidderName = "brightroll" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" + BidderDatablocks BidderName = "datablocks" BidderEmxDigital BidderName = "emx_digital" BidderEPlanning BidderName = "eplanning" BidderFacebook BidderName = "audienceNetwork" @@ -80,6 +81,7 @@ var BidderMap = map[string]BidderName{ "brightroll": BidderBrightroll, "consumable": BidderConsumable, "conversant": BidderConversant, + "datablocks": BidderDatablocks, "emx_digital": BidderEmxDigital, "eplanning": BidderEPlanning, "gamma": BidderGamma, diff --git a/openrtb_ext/imp_datablocks.go b/openrtb_ext/imp_datablocks.go new file mode 100644 index 00000000000..fdb5dd398d4 --- /dev/null +++ b/openrtb_ext/imp_datablocks.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpDatablocks defines the contract for bidrequest.imp[i].ext.datablocks +type ExtImpDatablocks struct { + SourceId int `json:"sourceId"` + Host string `json:"host"` +} diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml new file mode 100644 index 00000000000..9bf7e780914 --- /dev/null +++ b/static/bidder-info/datablocks.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "henry@datablocks.net" +capabilities: + app: + mediaTypes: + - banner + - native + - video + site: + mediaTypes: + - banner + - native + - video diff --git a/static/bidder-params/datablocks.json b/static/bidder-params/datablocks.json new file mode 100644 index 00000000000..2abb986786b --- /dev/null +++ b/static/bidder-params/datablocks.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Datablocks Adapter Params", + "description": "A schema which validates params accepted by the Datablocks adapter", + + "type": "object", + "properties": { + "sourceId": { + "type": "integer", + "minimum": 1, + "description": "Website Source Id" + }, + "host": { + "type": "string", + "description": "Network Host to request from" + } + }, + "required": ["host", "sourceId"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 28ea08a1ceb..250f5a62dae 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -18,6 +18,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/datablocks" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -70,6 +71,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.BidderDatablocks, datablocks.NewDatablocksSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderFacebook, audienceNetwork.NewFacebookSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7a7e33648fd..f1452d3373b 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.BidderDatablocks): syncConfig, string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEPlanning): syncConfig, string(openrtb_ext.BidderGrid): syncConfig,