diff --git a/adapters/flatads/flatads.go b/adapters/flatads/flatads.go new file mode 100644 index 00000000000..0bf0b979e7a --- /dev/null +++ b/adapters/flatads/flatads.go @@ -0,0 +1,157 @@ +package flatads + +import ( + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint template: %v", err) + } + + bidder := &adapter{ + endpoint: endpointTemplate, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + for _, imp := range request.Imp { + requestCopy := *request + requestCopy.Imp = []openrtb2.Imp{imp} + + endpoint, err := a.buildEndpointFromRequest(&imp) + if err != nil { + errs = append(errs, err) + continue + } + + requestJSON, err := jsonutil.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + r := &adapters.RequestData{ + Method: http.MethodPost, + Body: requestJSON, + Uri: endpoint, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), + } + + requests = append(requests, r) + } + + return requests, errs +} + +func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { + var impExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &impExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), + } + } + + var flatadsExt openrtb_ext.ImpExtFlatads + if err := jsonutil.Unmarshal(impExt.Bidder, &flatadsExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize Flatads extension: %v", err), + } + } + + endpointParams := macros.EndpointTemplateParams{ + TokenID: flatadsExt.Token, + PublisherID: flatadsExt.PublisherId, + } + + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + err := adapters.CheckResponseStatusCodeForErrors(responseData) + if err != nil { + return nil, []error{err} + } + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) != 0 { + bidResponse.Currency = response.Cur + } + + var errors []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidMediaType, err := getMediaTypeForBid(seatBid.Bid[i].ImpID, request.Imp) + if err != nil { + errors = append(errors, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidMediaType, + }) + } + } + return bidResponse, errors +} + +func getMediaTypeForBid(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("The impression with ID %s is not present into the request", impID), + } +} diff --git a/adapters/flatads/flatads_test.go b/adapters/flatads/flatads_test.go new file mode 100644 index 00000000000..b3b3cf33f7c --- /dev/null +++ b/adapters/flatads/flatads_test.go @@ -0,0 +1,29 @@ +package flatads + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderFlatads, config.Adapter{ + Endpoint: "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id={{.PublisherID}}&x-net-token={{.TokenID}}"}, + config.Server{}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "flatadstest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderFlatads, config.Adapter{ + Endpoint: "x-net-id={{PublisherID}}&x-net-token={{TokenID}}"}, config.Server{}) + + assert.Error(t, buildErr) +} diff --git a/adapters/flatads/flatadstest/exemplary/banner.json b/adapters/flatads/flatadstest/exemplary/banner.json new file mode 100644 index 00000000000..5cdf66ac0da --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/banner.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/exemplary/headers_ipv4.json b/adapters/flatads/flatadstest/exemplary/headers_ipv4.json new file mode 100644 index 00000000000..ac997f96579 --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/headers_ipv4.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": [ + "test-imp-id-banner" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/exemplary/headers_ipv6.json b/adapters/flatads/flatadstest/exemplary/headers_ipv6.json new file mode 100644 index 00000000000..2d12280c73e --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/headers_ipv6.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/flatads/flatadstest/exemplary/multiple-imps.json b/adapters/flatads/flatadstest/exemplary/multiple-imps.json new file mode 100644 index 00000000000..282037b8441 --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/multiple-imps.json @@ -0,0 +1,213 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + }, + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "22223333", + "publisherId": "2222" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=2222&x-net-token=22223333", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "22223333", + "publisherId": "2222" + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-banner2", + "price": 0.5, + "adm": "some-test-ad-banner2", + "crid": "crid_11", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-banner2", + "price": 0.5, + "adm": "some-test-ad-banner2", + "crid": "crid_11", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/exemplary/native.json b/adapters/flatads/flatadstest/exemplary/native.json new file mode 100644 index 00000000000..c4b67687335 --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/native.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "id": "test-request-id-native", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-native", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-native", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-native", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": [ + "test-imp-id-native" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-native", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-native", + "price": 0.5, + "adm": "some-test-ad-native", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-native", + "price": 0.5, + "adm": "some-test-ad-native", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/exemplary/optional-params.json b/adapters/flatads/flatadstest/exemplary/optional-params.json new file mode 100644 index 00000000000..20441dadf44 --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/optional-params.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111", + "bidFloor": 0.1, + "isTest": false + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111", + "bidFloor": 0.1, + "isTest": false + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/exemplary/video.json b/adapters/flatads/flatadstest/exemplary/video.json new file mode 100644 index 00000000000..dc2f1bf5322 --- /dev/null +++ b/adapters/flatads/flatadstest/exemplary/video.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-video-id", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-video-id", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-video-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-test-video-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [ + { + "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd", + "impid": "test-video-id", + "price": 0.1529, + "nurl": "test-win", + "adm": "test-video", + "adid": "92-288", + "adomain": ["advertiserdomain.com"], + "crid": "288", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd", + "impid": "test-video-id", + "price": 0.1529, + "nurl": "test-win", + "adm": "test-video", + "adid": "92-288", + "adomain": ["advertiserdomain.com"], + "crid": "288", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/supplemental/bad-request.json b/adapters/flatads/flatadstest/supplemental/bad-request.json new file mode 100644 index 00000000000..25be9ed2466 --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/bad-request.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 400, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/flatads/flatadstest/supplemental/invalid-bid-type.json b/adapters/flatads/flatadstest/supplemental/invalid-bid-type.json new file mode 100644 index 00000000000..87331a1f720 --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/invalid-bid-type.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": [ + "test-imp-id-banner" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-invalid", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "The impression with ID test-imp-id-invalid is not present into the request", + "comparison": "literal" + } + ] +} diff --git a/adapters/flatads/flatadstest/supplemental/invalid-imp-ext-bidder.json b/adapters/flatads/flatadstest/supplemental/invalid-imp-ext-bidder.json new file mode 100644 index 00000000000..3d210a34527 --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/invalid-imp-ext-bidder.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": "" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize Flatads extension: expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/flatads/flatadstest/supplemental/invalid-imp-ext.json b/adapters/flatads/flatadstest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..4790fc34d13 --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/invalid-imp-ext.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": "" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize bidder impression extension: expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/flatads/flatadstest/supplemental/multiple-imps-with-error.json b/adapters/flatads/flatadstest/supplemental/multiple-imps-with-error.json new file mode 100644 index 00000000000..908c06df7cc --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/multiple-imps-with-error.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + }, + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": {}, + "publisherId": "1111" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id-banner", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "flatads", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize Flatads extension: cannot unmarshal openrtb_ext.ImpExtFlatads.Token: expects \" or n, but found {", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/flatads/flatadstest/supplemental/response-200-without-body.json b/adapters/flatads/flatadstest/supplemental/response-200-without-body.json new file mode 100644 index 00000000000..2ee9b421d1e --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/response-200-without-body.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "expect { or n, but found", + "comparison": "startswith" + } + ] +} \ No newline at end of file diff --git a/adapters/flatads/flatadstest/supplemental/response-204.json b/adapters/flatads/flatadstest/supplemental/response-204.json new file mode 100644 index 00000000000..f93bdeedc6e --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/response-204.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/flatads/flatadstest/supplemental/server-error.json b/adapters/flatads/flatadstest/supplemental/server-error.json new file mode 100644 index 00000000000..0d9d3cebb01 --- /dev/null +++ b/adapters/flatads/flatadstest/supplemental/server-error.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://test.endpoint.com/api/rtbs/adx/rtb?x-net-id=1111&x-net-token=66668888", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "token": "66668888", + "publisherId": "1111" + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 500, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/flatads/params_test.go b/adapters/flatads/params_test.go new file mode 100644 index 00000000000..c58546399b9 --- /dev/null +++ b/adapters/flatads/params_test.go @@ -0,0 +1,51 @@ +package flatads + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/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.BidderFlatads, 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.BidderFlatads, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ "token": "66668888", + "publisherId": "1111" + }`, + `{"token": "66668888", "publisherId": "1111"}`, +} + +var invalidParams = []string{ + `{}`, + `{"token": 66668888, "networkId":1111}`, + `{"token": "66668888"", "networkId":1111}`, + `{"token": 66668888, "networkId":"1111""}`, + `{"token": "", "publisherId": "1111"}`, + `{"token": "66668888", "publisherId": ""}`, + `{"token": "", "publisherId": ""}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 7647de71786..70cd54a41f8 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -100,6 +100,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/epom" "github.com/prebid/prebid-server/v3/adapters/escalax" "github.com/prebid/prebid-server/v3/adapters/feedad" + "github.com/prebid/prebid-server/v3/adapters/flatads" "github.com/prebid/prebid-server/v3/adapters/flipp" "github.com/prebid/prebid-server/v3/adapters/freewheelssp" "github.com/prebid/prebid-server/v3/adapters/frvradn" @@ -348,6 +349,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEscalax: escalax.Builder, openrtb_ext.BidderEVolution: evolution.Builder, openrtb_ext.BidderFeedAd: feedad.Builder, + openrtb_ext.BidderFlatads: flatads.Builder, openrtb_ext.BidderFlipp: flipp.Builder, openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, openrtb_ext.BidderFRVRAdNetwork: frvradn.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 10755dfd7fd..dded00194c1 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -117,6 +117,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderEscalax, BidderEVolution, BidderFeedAd, + BidderFlatads, BidderFlipp, BidderFreewheelSSP, BidderFRVRAdNetwork, @@ -471,6 +472,7 @@ const ( BidderEscalax BidderName = "escalax" BidderEVolution BidderName = "e_volution" BidderFeedAd BidderName = "feedad" + BidderFlatads BidderName = "flatads" BidderFlipp BidderName = "flipp" BidderFreewheelSSP BidderName = "freewheelssp" BidderFRVRAdNetwork BidderName = "frvradn" diff --git a/openrtb_ext/imp_flatads.go b/openrtb_ext/imp_flatads.go new file mode 100644 index 00000000000..331190a78cd --- /dev/null +++ b/openrtb_ext/imp_flatads.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtFlatads struct { + Token string `json:"token"` + PublisherId string `json:"publisherId"` +} diff --git a/static/bidder-info/flatads.yaml b/static/bidder-info/flatads.yaml new file mode 100644 index 00000000000..5e1e7c47cc7 --- /dev/null +++ b/static/bidder-info/flatads.yaml @@ -0,0 +1,17 @@ +endpoint: "https://bid.rtbshark.com/api/rtbs/adx/rtb?x-net-id={{.PublisherID}}&x-net-token={{.TokenID}}" +endpointCompression: gzip +maintainer: + email: "adxbusiness@flat-ads.com" +openrtb: + version: 2.6 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/flatads.json b/static/bidder-params/flatads.json new file mode 100644 index 00000000000..5f3eb8738a5 --- /dev/null +++ b/static/bidder-params/flatads.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Flatads Adapter Params", + "description": "A schema which validates params accepted by the Flatads adapter", + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Token of the publisher", + "minLength": 1 + }, + "publisherId": { + "type": "string", + "description": "Flatads Publisher Id", + "minLength": 1 + } + }, + "required": [ + "token", + "publisherId" + ] +} \ No newline at end of file