diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go new file mode 100644 index 00000000000..ebf428439ed --- /dev/null +++ b/adapters/onetag/onetag.go @@ -0,0 +1,152 @@ +package onetag + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpointTemplate template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpointTemplate: *template, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + pubID := "" + for idx, imp := range request.Imp { + onetagExt, err := getImpressionExt(imp) + if err != nil { + return nil, []error{err} + } + if onetagExt.PubId != "" { + if pubID == "" { + pubID = onetagExt.PubId + } else if pubID != onetagExt.PubId { + return nil, []error{&errortypes.BadInput{ + Message: "There must be only one publisher ID", + }} + } + } else { + return nil, []error{&errortypes.BadInput{ + Message: "The publisher ID must not be empty", + }} + } + request.Imp[idx].Ext = onetagExt.Ext + } + + url, err := a.buildEndpointURL(pubID) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func getImpressionExt(imp openrtb.Imp) (*openrtb_ext.ExtImpOnetag, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Bidder extension not provided or can't be unmarshalled", + } + } + + var onetagExt openrtb_ext.ExtImpOnetag + if err := json.Unmarshal(bidderExt.Bidder, &onetagExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + + return &onetagExt, nil +} + +func (a *adapter) buildEndpointURL(pubID string) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: pubID} + return macros.ResolveMacros(a.endpointTemplate, endpointParams) +} + +func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bidMediaType, err := getMediaTypeForBid(request.Imp, bid) + if err != nil { + return nil, []error{err} + } + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidMediaType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForBid(impressions []openrtb.Imp, bid openrtb.Bid) (openrtb_ext.BidType, error) { + for _, impression := range impressions { + if impression.ID == bid.ImpID { + if impression.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if impression.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if impression.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("The impression with ID %s is not present into the request", bid.ImpID), + } +} diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go new file mode 100644 index 00000000000..9f7c8e50115 --- /dev/null +++ b/adapters/onetag/onetag_test.go @@ -0,0 +1,26 @@ +package onetag + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ + Endpoint: "https://example.com/prebid-server/{{.PublisherID}}"}) + + assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr) + + adapterstest.RunJSONBidderTest(t, "onetagtest", bidder) +} diff --git a/adapters/onetag/onetagtest/exemplary/no-bid.json b/adapters/onetag/onetagtest/exemplary/no-bid.json new file mode 100644 index 00000000000..012834ca8a4 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/no-bid.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-banner.json b/adapters/onetag/onetagtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..7489a27dbff --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-banner.json @@ -0,0 +1,201 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId": "386276e072", + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05 + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "key1": "value1", + "key2": "value2" + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", + "seatbid": [ + { + "bid": [ + { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "121-dt1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428, + "attr": [] + } + ], + "seat": "42" + }, + { + "bid": [ + { + "adid": "527c9fdd55314ba06815f25e", + "adm": "", + "adomain": ["ads.com"], + "cid": "527c9fdd55314ba06815f25e", + "crid": "527c9fdd55314ba06815f25e_1383899102", + "id": "24195efda36066ee21f967bc1de14c82db841f08", + "impid": "121-dt2", + "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}", + "price": 0.04958, + "attr": [] + } + ], + "seat": "772" + } + ] + }, + "cur": "USD" + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "type": "banner", + "bid": { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "121-dt1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428 + } + }, + { + "type": "banner", + "bid": { + "adid": "527c9fdd55314ba06815f25e", + "adm": "", + "adomain": ["ads.com"], + "cid": "527c9fdd55314ba06815f25e", + "crid": "527c9fdd55314ba06815f25e_1383899102", + "id": "24195efda36066ee21f967bc1de14c82db841f08", + "impid": "121-dt2", + "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}", + "price": 0.04958 + } + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-native.json b/adapters/onetag/onetagtest/exemplary/simple-native.json new file mode 100644 index 00000000000..08fef34f7a7 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-native.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "pubId": "386276e072", + "ext": { + "key1": "value1" + } + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "key1": "value1" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "onetag" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids":[ + { + "type": "native", + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + } + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-video.json b/adapters/onetag/onetagtest/exemplary/simple-video.json new file mode 100644 index 00000000000..ea656a98fc8 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-video.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid" + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid" + }, + "type": "video" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/banner.json b/adapters/onetag/onetagtest/params/race/banner.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/native.json b/adapters/onetag/onetagtest/params/race/native.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/video.json b/adapters/onetag/onetagtest/params/race/video.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json new file mode 100644 index 00000000000..d5ca3b144bc --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId" : "1" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId" : "" + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "The publisher ID must not be empty", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/internal-server-error.json b/adapters/onetag/onetagtest/supplemental/internal-server-error.json new file mode 100644 index 00000000000..4fc069598c7 --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/internal-server-error.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/required-publisher-id.json b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json new file mode 100644 index 00000000000..6e8ccf0d3cc --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": {} + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "The publisher ID must not be empty", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json new file mode 100644 index 00000000000..fe2e3914fae --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId": "386276e072a", + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "There must be only one publisher ID", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json new file mode 100644 index 00000000000..bfdfcc5f3e3 --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05 + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", + "seatbid": [ + { + "bid": [ + { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428, + "attr": [] + } + ], + "seat": "42" + } + ] + }, + "cur": "USD" + } + } + ], + "expectedMakeBidsErrors": [{ + "value": "The impression with ID 1 is not present into the request", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/onetag/params_test.go b/adapters/onetag/params_test.go new file mode 100644 index 00000000000..4c7326ac9f0 --- /dev/null +++ b/adapters/onetag/params_test.go @@ -0,0 +1,54 @@ +package onetag + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderOneTag, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderOneTag, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ "pubId": "386276e072", + "ext": { + "key1": "value1", + "key2": "value2" + } + }`, + `{"pubId": "386276e072"}`, +} + +var invalidParams = []string{ + `{"ext": { + "key1": "value1", + "key2": "value2" + }`, + `{}`, + `{"pubId": ""}`, + `{"pubId": 123}`, +} diff --git a/adapters/onetag/usersync.go b/adapters/onetag/usersync.go new file mode 100644 index 00000000000..f36b82a853c --- /dev/null +++ b/adapters/onetag/usersync.go @@ -0,0 +1,12 @@ +package onetag + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSyncer(template *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("onetag", 241, template, adapters.SyncTypeIframe) +} diff --git a/adapters/onetag/usersync_test.go b/adapters/onetag/usersync_test.go new file mode 100644 index 00000000000..31892e6d9c8 --- /dev/null +++ b/adapters/onetag/usersync_test.go @@ -0,0 +1,24 @@ +package onetag + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestOneTagSyncer(t *testing.T) { + syncURL := "https://onetag-sys.com/usync/?redir=" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://onetag-sys.com/usync/?redir=", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.EqualValues(t, 241, syncer.GDPRVendorID()) +} diff --git a/config/config.go b/config/config.go index a35b3c2a7e6..379796013e8 100755 --- a/config/config.go +++ b/config/config.go @@ -612,6 +612,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") @@ -853,6 +854,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1") + v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 31342a4c5ff..12464405af6 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -61,6 +61,7 @@ import ( "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/nobid" + "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/orbidder" "github.com/prebid/prebid-server/adapters/pubmatic" @@ -162,6 +163,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c0839632c7e..b43c7ce9061 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -101,6 +101,7 @@ const ( BidderNanoInteractive BidderName = "nanointeractive" BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" + BidderOneTag BidderName = "onetag" BidderOpenx BidderName = "openx" BidderOrbidder BidderName = "orbidder" BidderPubmatic BidderName = "pubmatic" @@ -201,6 +202,7 @@ func CoreBidderNames() []BidderName { BidderNanoInteractive, BidderNinthDecimal, BidderNoBid, + BidderOneTag, BidderOpenx, BidderOrbidder, BidderPubmatic, diff --git a/openrtb_ext/imp_onetag.go b/openrtb_ext/imp_onetag.go new file mode 100644 index 00000000000..47d06ed4873 --- /dev/null +++ b/openrtb_ext/imp_onetag.go @@ -0,0 +1,10 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpOnetag struct { + PubId string `json:"pubId"` + Ext json.RawMessage `json:"ext"` +} diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml new file mode 100644 index 00000000000..fedaabf9949 --- /dev/null +++ b/static/bidder-info/onetag.yaml @@ -0,0 +1,13 @@ +maintainer: + email: devops@onetag.com +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/onetag.json b/static/bidder-params/onetag.json new file mode 100644 index 00000000000..ca2911ba4d1 --- /dev/null +++ b/static/bidder-params/onetag.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Onetag Adapter Params", + "description": "A schema which validates params accepted by the Onetag adapter", + "type": "object", + + "properties": { + "pubId": { + "type": "string", + "minLength": 1, + "description": "The publisher’s ID provided by OneTag" + }, + "ext": { + "type": "object", + "description": "A set of custom key-value pairs" + } + }, + + "required": ["pubId"] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index c41f7c6c746..6a6cfac8f9d 100755 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -55,6 +55,7 @@ import ( "github.com/prebid/prebid-server/adapters/nanointeractive" "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/nobid" + "github.com/prebid/prebid-server/adapters/onetag" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -142,6 +143,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNoBid, nobid.NewNoBidSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index bca8623d98b..c2455babe8b 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -64,6 +64,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderNanoInteractive): syncConfig, string(openrtb_ext.BidderNinthDecimal): syncConfig, string(openrtb_ext.BidderNoBid): syncConfig, + string(openrtb_ext.BidderOneTag): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig,