diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go new file mode 100644 index 00000000000..efa3fba1be9 --- /dev/null +++ b/adapters/telaria/params_test.go @@ -0,0 +1,50 @@ +package telaria + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +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.BidderTelaria, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Telaria params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Telaria 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.BidderTelaria, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"adCode": "string", "seatCode": "string", "originalPublisherid": "string"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "originalPublisherid": "string"}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, +} diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go new file mode 100644 index 00000000000..9edafa86a32 --- /dev/null +++ b/adapters/telaria/telaria.go @@ -0,0 +1,330 @@ +package telaria + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "strconv" +) + +const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" + +type TelariaAdapter struct { + URI string +} + +// This will be part of Imp[i].Ext when this adapter calls out the Telaria Ad Server +type ImpressionExtOut struct { + OriginalTagID string `json:"originalTagid"` + OriginalPublisherID string `json:"originalPublisherid"` +} + +// used for cookies and such +func (a *TelariaAdapter) Name() string { + return "telaria" +} + +func (a *TelariaAdapter) SkipNoCookies() bool { + return false +} + +// Endpoint for Telaria Ad server +func (a *TelariaAdapter) FetchEndpoint() string { + return a.URI +} + +// Checker method to ensure len(request.Imp) > 0 +func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { + if len(request.Imp) == 0 { + err := &errortypes.BadInput{ + Message: "Telaria: Missing Imp Object", + } + return err + } + return nil +} + +// Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist +func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error { + hasVideoObject := false + + for _, imp := range request.Imp { + if imp.Banner != nil { + return &errortypes.BadInput{ + Message: "Telaria: Banner not supported", + } + } + + hasVideoObject = hasVideoObject || imp.Video != nil + } + + if !hasVideoObject { + return &errortypes.BadInput{ + Message: "Telaria: Only Supports Video", + } + } + + return nil +} + +// Fetches the populated header object +func GetHeaders(request *openrtb.BidRequest) *http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + headers.Add("Accept-Encoding", "gzip") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + + if len(request.Device.Language) > 0 { + headers.Add("Accept-Language", request.Device.Language) + } + + if request.Device.DNT != nil { + headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT))) + } + } + + return &headers +} + +// Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format +func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpTelaria, error) { + var bidderExt adapters.ExtImpBidder + err := json.Unmarshal(imp.Ext, &bidderExt) + + if err != nil { + err = &errortypes.BadInput{ + Message: "Telaria: ext.bidder not provided", + } + + return nil, err + } + + var telariaExt openrtb_ext.ExtImpTelaria + err = json.Unmarshal(bidderExt.Bidder, &telariaExt) + + if err != nil { + return nil, err + } + + if telariaExt.SeatCode == "" { + return nil, &errortypes.BadInput{Message: "Telaria: Seat Code required"} + } + + return &telariaExt, nil +} + +// Method to fetch the original publisher ID. Note that this method must be called +// before we replace publisher.ID with seatCode +func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) string { + + if request.Site != nil && request.Site.Publisher != nil { + return request.Site.Publisher.ID + } else if request.App != nil && request.App.Publisher != nil { + return request.App.Publisher.ID + } + + return "" +} + +// Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID +func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb.Publisher) *openrtb.Publisher { + var pub = &openrtb.Publisher{ID: seatCode} + + if publisher != nil { + pub.Domain = publisher.Domain + pub.Name = publisher.Name + pub.Cat = publisher.Cat + pub.Ext = publisher.Ext + } + + return pub +} + +// This method changes .publisher.id to the seatCode +func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCode string) (*openrtb.Site, *openrtb.App) { + if request.Site != nil { + siteCopy := *request.Site + siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher) + return &siteCopy, nil + } else if request.App != nil { + appCopy := *request.App + appCopy.Publisher = a.MakePublisherObject(seatCode, request.App.Publisher) + return nil, &appCopy + } + return nil, nil +} + +func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + // make a copy of the incoming request + request := *requestIn + + // ensure that the request has Impressions + if noImps := a.CheckHasImps(&request); noImps != nil { + return nil, []error{noImps} + } + + // ensure that the request has a Video object + if noVideoObjectError := a.CheckHasVideoObject(&request); noVideoObjectError != nil { + return nil, []error{noVideoObjectError} + } + + var seatCode string + originalPublisherID := a.FetchOriginalPublisherID(&request) + + var errors []error + for i, imp := range request.Imp { + // fetch adCode & seatCode from Imp[i].Ext + telariaExt, err := a.FetchTelariaExtImpParams(&imp) + if err != nil { + errors = append(errors, err) + break + } + + seatCode = telariaExt.SeatCode + + // move the original tagId and the original publisher.id into the Imp[i].Ext object + request.Imp[i].Ext, err = json.Marshal(&ImpressionExtOut{request.Imp[i].TagID, originalPublisherID}) + if err != nil { + errors = append(errors, err) + break + } + + // Swap the tagID with adCode + request.Imp[i].TagID = telariaExt.AdCode + } + + if len(errors) > 0 { + return nil, errors + } + + // Add seatCode to .Publisher.ID + siteObject, appObject := a.PopulatePublisherId(&request, seatCode) + + request.Site = siteObject + request.App = appObject + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.FetchEndpoint(), + Body: reqJSON, + Headers: *GetHeaders(&request), + }}, nil +} + +// response isn't automatically decompressed. This method unzips the response if Content-Encoding is gzip +func GetResponseBody(response *adapters.ResponseData) ([]byte, error) { + + if "gzip" == response.Headers.Get("Content-Encoding") { + body := bytes.NewBuffer(response.Body) + r, readerErr := gzip.NewReader(body) + if readerErr != nil { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode), + } + } + var resB bytes.Buffer + var err error + _, err = resB.ReadFrom(r) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode), + } + } + + response.Headers.Del("Content-Encoding") + + return resB.Bytes(), nil + } else { + return response.Body, nil + } +} + +func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusNoContent { + return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"} + } + + if response.StatusCode == http.StatusBadRequest { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + return nil +} + +func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + httpStatusError := a.CheckResponseStatusCodes(response) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + responseBody, err := GetResponseBody(response) + + if err != nil { + return nil, []error{err} + } + + var bidResp openrtb.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Telaria: Bad Server Response", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + + for _, bid := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeVideo, + }) + } + return bidResponse, nil +} + +func NewTelariaBidder(endpoint string) *TelariaAdapter { + if endpoint == "" { + endpoint = Endpoint + } + + return &TelariaAdapter{ + URI: endpoint, + } +} diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go new file mode 100644 index 00000000000..7ad96b9307b --- /dev/null +++ b/adapters/telaria/telaria_test.go @@ -0,0 +1,34 @@ +package telaria + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +/** + * Verify adapter names are setup correctly. + */ +func TestTelariaAdapterNames(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyStringValue(adapter.Name(), "telaria", t) +} + +/** + * Verify adapter SkipNoCookie is correct. + */ +func TestTelariaAdapterSkipNoCookiesFlag(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) +} + +/** + * Verify bidder has the proper URL + */ +func TestTelariaAdapterEndpoint(t *testing.T) { + adapter := NewTelariaBidder("") + adapterstest.VerifyStringValue(adapter.URI, "https://ads.tremorhub.com/ad/rtb/prebid", t) +} + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "telariatest", NewTelariaBidder("")) +} diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json new file mode 100644 index 00000000000..09bcf998454 --- /dev/null +++ b/adapters/telaria/telariatest/exemplary/video-app.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "my-adcode", + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [{ + "bid": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }], + "seat": "telaria" + }], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } + } + } + }], + "expectedBids": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }] +} diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json new file mode 100644 index 00000000000..5dc26b0f018 --- /dev/null +++ b/adapters/telaria/telariatest/exemplary/video-web.json @@ -0,0 +1,145 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [{ + "bid": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }], + "seat": "telaria" + }], + "cur": "USD", + "ext": { + "responsetimemillis": { + "telaria": 154 + }, + "tmaxrequest": 1000 + } + } + } + }], + "expectedBids": [{ + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "asesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }] +} diff --git a/adapters/telaria/telariatest/params/race/video.json b/adapters/telaria/telariatest/params/race/video.json new file mode 100644 index 00000000000..e3b67ec8c20 --- /dev/null +++ b/adapters/telaria/telariatest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "adCode": "my-adcode", + "seatCode": "my-seatcode" +} diff --git a/adapters/telaria/telariatest/supplemental/banner-unsupported.json b/adapters/telaria/telariatest/supplemental/banner-unsupported.json new file mode 100644 index 00000000000..1e75371dd22 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/banner-unsupported.json @@ -0,0 +1,42 @@ + +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Banner not supported", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.com", + "publisher": { + "id": "someother-publisher-id" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + } +} diff --git a/adapters/telaria/telariatest/supplemental/invalid-response.json b/adapters/telaria/telariatest/supplemental/invalid-response.json new file mode 100644 index 00000000000..8e4f1ee8cb5 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/invalid-response.json @@ -0,0 +1,105 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"], + "Accept-Encoding": ["gzip"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"] + }, + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json b/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json new file mode 100644 index 00000000000..efdec8ad61b --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/invalid-telaria-ext-object.json @@ -0,0 +1,29 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: ext.bidder not provided", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "Awesome" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-imp-object.json b/adapters/telaria/telariatest/supplemental/requires-imp-object.json new file mode 100644 index 00000000000..5933dc4ee08 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-imp-object.json @@ -0,0 +1,16 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Missing Imp Object", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-seat-code.json b/adapters/telaria/telariatest/supplemental/requires-seat-code.json new file mode 100644 index 00000000000..5c05e529772 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-seat-code.json @@ -0,0 +1,30 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Seat Code required", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-1", + "video": { + "w": 640, + "h": 480, + "linearity": 1 + }, + "ext": { + "bidder": { + "adCode": "my-adcode" + } + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/requires-video-object.json b/adapters/telaria/telariatest/supplemental/requires-video-object.json new file mode 100644 index 00000000000..3f797c9e1de --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/requires-video-object.json @@ -0,0 +1,26 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Telaria: Only Supports Video", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-impression-1", + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-bad-request.json b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..0b5d8a85982 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json @@ -0,0 +1,80 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "" + } + } + ], + "app": { + "id": "123456789", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Unexpected status code: [ 400 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-no-content.json b/adapters/telaria/telariatest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..ffb183f4121 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-no-content.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Invalid Bid Request received by the server", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-other-error.json b/adapters/telaria/telariatest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..15e4b7f87d8 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-other-error.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Something went wrong, please contact your Account Manager. Status Code: [ 306 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..b92d4ea8ba1 --- /dev/null +++ b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,83 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "adCode": "my-adcode", + "seatCode": "my-seatcode" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ads.tremorhub.com/ad/rtb/prebid", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "originalTagid": "ogTAGID", + "originalPublisherid": "123456789" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "my-seatcode" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Telaria: Something went wrong, please contact your Account Manager. Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/telaria/usersync.go b/adapters/telaria/usersync.go new file mode 100644 index 00000000000..e3f76f6e9b4 --- /dev/null +++ b/adapters/telaria/usersync.go @@ -0,0 +1,12 @@ +package telaria + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTelariaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("telaria", 202, temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/telaria/usersync_test.go b/adapters/telaria/usersync_test.go new file mode 100644 index 00000000000..4896b253d2f --- /dev/null +++ b/adapters/telaria/usersync_test.go @@ -0,0 +1,33 @@ +package telaria + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTelariaSyncer(t *testing.T) { + + syncURL := "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTelariaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://pbs.publishers.tremorhub.com/pubsync?gdpr=0&gdpr_consent=", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.EqualValues(t, 202, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) + assert.Equal(t, "telaria", syncer.FamilyName()) + +} diff --git a/config/config.go b/config/config.go index 2cb5f8f2e66..652ad28cd87 100644 --- a/config/config.go +++ b/config/config.go @@ -535,6 +535,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") // openrtb_ext.BidderTappx doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") @@ -729,6 +730,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?supplier_id=20") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index f7b970c571b..8e779822cae 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -54,6 +54,7 @@ import ( "github.com/prebid/prebid-server/adapters/sovrn" "github.com/prebid/prebid-server/adapters/synacormedia" "github.com/prebid/prebid-server/adapters/tappx" + "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" @@ -127,6 +128,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderSovrn: sovrn.NewSovrnBidder(client, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), openrtb_ext.BidderSynacormedia: synacormedia.NewSynacorMediaBidder(cfg.Adapters[string(openrtb_ext.BidderSynacormedia)].Endpoint), openrtb_ext.BidderTappx: tappx.NewTappxBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTappx))].Endpoint), + openrtb_ext.BidderTelaria: telaria.NewTelariaBidder(cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTelaria))].Endpoint), openrtb_ext.BidderTriplelift: triplelift.NewTripleliftBidder(client, cfg.Adapters[string(openrtb_ext.BidderTriplelift)].Endpoint), openrtb_ext.BidderTripleliftNative: triplelift_native.NewTripleliftNativeBidder(client, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderTripleliftNative)].ExtraAdapterInfo), openrtb_ext.BidderUcfunnel: ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ec9745563ef..43d9b894ea8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -72,6 +72,7 @@ const ( BidderSovrn BidderName = "sovrn" BidderSynacormedia BidderName = "synacormedia" BidderTappx BidderName = "tappx" + BidderTelaria BidderName = "telaria" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" BidderUcfunnel BidderName = "ucfunnel" @@ -135,6 +136,7 @@ var BidderMap = map[string]BidderName{ "sovrn": BidderSovrn, "synacormedia": BidderSynacormedia, "tappx": BidderTappx, + "telaria": BidderTelaria, "triplelift": BidderTriplelift, "triplelift_native": BidderTripleliftNative, "ucfunnel": BidderUcfunnel, diff --git a/openrtb_ext/imp_telaria.go b/openrtb_ext/imp_telaria.go new file mode 100644 index 00000000000..8ea371a8ad0 --- /dev/null +++ b/openrtb_ext/imp_telaria.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpTelaria struct { + AdCode string `json:"adCode,omitempty"` + SeatCode string `json:"seatCode"` +} diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml new file mode 100644 index 00000000000..43e8707a17b --- /dev/null +++ b/static/bidder-info/telaria.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "github@telaria.com" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-params/telaria.json b/static/bidder-params/telaria.json new file mode 100644 index 00000000000..b4121967351 --- /dev/null +++ b/static/bidder-params/telaria.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Telaria Adapter Params", + "description": "A schema which validates params accepted by the Telaria adapter", + + "type": "object", + "properties": { + "adCode": { + "type": "string", + "description": "The Ad Unit Code." + }, + "seatCode": { + "type": "string", + "description": "Your Seat Code." + }, + "originalPublisherid": { + "type": "string", + "description": "publisher ID from the original request" + } + }, + "required": ["seatCode"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index be0392f2dbb..da235402bf0 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -47,6 +47,7 @@ import ( "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/telaria" "github.com/prebid/prebid-server/adapters/triplelift" "github.com/prebid/prebid-server/adapters/triplelift_native" "github.com/prebid/prebid-server/adapters/ucfunnel" @@ -110,6 +111,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 383e24d82cf..637f590e25f 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -56,6 +56,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSovrn): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, + string(openrtb_ext.BidderTelaria): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, string(openrtb_ext.BidderUcfunnel): syncConfig,