diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go new file mode 100644 index 00000000000..011ca4601f1 --- /dev/null +++ b/adapters/floxis/floxis.go @@ -0,0 +1,200 @@ +package floxis + +import ( + "fmt" + "net/http" + "net/url" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v4/adapters" + "github.com/prebid/prebid-server/v4/config" + "github.com/prebid/prebid-server/v4/errortypes" + "github.com/prebid/prebid-server/v4/macros" + "github.com/prebid/prebid-server/v4/openrtb_ext" + "github.com/prebid/prebid-server/v4/util/jsonutil" +) + +type adapter struct { + endpoint *template.Template +} + +const ( + defaultRegion = "us-e" + defaultPartner = "floxis" +) + +func resolveBidHost(region, partner string) string { + if region == "" { + region = defaultRegion + } + if partner == "" { + partner = defaultPartner + } + if partner == defaultPartner { + return region + } + return partner + "-" + region +} + +// Builder builds a new instance of the Floxis adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + tmpl, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + return &adapter{endpoint: tmpl}, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + ext, err := parseImpExt(request.Imp[0]) + if err != nil { + return nil, []error{err} + } + + resolvedHost := resolveBidHost(ext.Region, ext.Partner) + for i := 1; i < len(request.Imp); i++ { + impExt, err := parseImpExt(request.Imp[i]) + if err != nil { + return nil, []error{err} + } + impHost := resolveBidHost(impExt.Region, impExt.Partner) + if impExt.Seat != ext.Seat || impHost != resolvedHost { + return nil, []error{&errortypes.BadInput{Message: fmt.Sprintf( + "imp %s seat/host (%q/%q) differs from imp %s (%q/%q); split into separate requests", + request.Imp[i].ID, impExt.Seat, impHost, + request.Imp[0].ID, ext.Seat, resolvedHost)}} + } + } + + endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: resolvedHost}) + if err != nil { + return nil, []error{err} + } + uri := fmt.Sprintf("%s?seat=%s", endpoint, url.QueryEscape(ext.Seat)) + + body, err := jsonutil.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: uri, + Body: body, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if response.Cur != "" { + bidResponse.Currency = response.Cur + } + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, typeErr := getMediaTypeForBid(request.Imp, seatBid.Bid[i]) + if typeErr != nil { + errs = append(errs, typeErr) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + return bidResponse, errs +} + +func parseImpExt(imp openrtb2.Imp) (openrtb_ext.ExtImpFloxis, error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext for imp %s: %s", imp.ID, err)} + } + var floxisExt openrtb_ext.ExtImpFloxis + if err := jsonutil.Unmarshal(bidderExt.Bidder, &floxisExt); err != nil { + return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext.bidder for imp %s: %s", imp.ID, err)} + } + return floxisExt, nil +} + +func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.MType != 0 { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("unsupported bid.mtype %d for impression %s", bid.MType, bid.ImpID), + } + } + } + for _, imp := range imps { + if imp.ID != bid.ImpID { + continue + } + formats, resolved := countFormats(imp) + switch { + case formats == 1: + return resolved, nil + case formats > 1: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid for multi-format imp %s requires bid.mtype to disambiguate", bid.ImpID), + } + default: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("unable to resolve media type for impression %s", bid.ImpID), + } + } + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("unable to find impression %s for bid", bid.ImpID), + } +} + +func countFormats(imp openrtb2.Imp) (int, openrtb_ext.BidType) { + formats := 0 + var resolved openrtb_ext.BidType + if imp.Banner != nil { + formats++ + resolved = openrtb_ext.BidTypeBanner + } + if imp.Video != nil { + formats++ + resolved = openrtb_ext.BidTypeVideo + } + if imp.Audio != nil { + formats++ + resolved = openrtb_ext.BidTypeAudio + } + if imp.Native != nil { + formats++ + resolved = openrtb_ext.BidTypeNative + } + return formats, resolved +} diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go new file mode 100644 index 00000000000..37104b14bb7 --- /dev/null +++ b/adapters/floxis/floxis_test.go @@ -0,0 +1,200 @@ +package floxis + +import ( + "encoding/json" + "testing" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v4/adapters" + "github.com/prebid/prebid-server/v4/adapters/adapterstest" + "github.com/prebid/prebid-server/v4/config" + "github.com/prebid/prebid-server/v4/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderFloxis, config.Adapter{ + Endpoint: "https://{{.Host}}.floxis.tech/pbs"}, + config.Server{ExternalUrl: "http://hosturl.com", DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "floxistest", bidder) +} + +func newAdapter() *adapter { + return &adapter{endpoint: template.Must(template.New("endpointTemplate").Parse("https://{{.Host}}.floxis.tech/pbs"))} +} + +func bannerImp(ext string) openrtb2.Imp { + return openrtb2.Imp{ + ID: "imp-1", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, + Ext: json.RawMessage(ext), + } +} + +func TestResolveBidHost(t *testing.T) { + cases := []struct { + region string + partner string + want string + }{ + {"us-e", "floxis", "us-e"}, + {"eu", "floxis", "eu"}, + {"apac", "floxis", "apac"}, + {"", "", "us-e"}, + {"", "floxis", "us-e"}, + {"eu", "", "eu"}, + {"mars", "floxis", "mars"}, + {"us-e", "acme", "acme-us-e"}, + {"eu", "acme", "acme-eu"}, + } + for _, c := range cases { + assert.Equal(t, c.want, resolveBidHost(c.region, c.partner), "region %q partner %q", c.region, c.partner) + } +} + +func TestSeatIsURLEscaped(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"a b&c","region":"eu"}}`)}, + Site: &openrtb2.Site{ID: "271"}, + } + reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Empty(t, errs) + assert.Len(t, reqData, 1) + assert.Equal(t, "https://eu.floxis.tech/pbs?seat=a+b%26c", reqData[0].Uri) +} + +func TestPartnerPrefixesHost(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","region":"us-e","partner":"acme"}}`)}, + Site: &openrtb2.Site{ID: "271"}, + } + reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Empty(t, errs) + assert.Equal(t, "https://acme-us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) +} + +func TestValidNonStandardRegionPassesThrough(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","region":"mars"}}`)}, + Site: &openrtb2.Site{ID: "271"}, + } + reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Empty(t, errs) + assert.Equal(t, "https://mars.floxis.tech/pbs?seat=abc", reqData[0].Uri) +} + +func TestMissingRegionDefaultsToUSE(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc"}}`)}, + Site: &openrtb2.Site{ID: "271"}, + } + reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Empty(t, errs) + assert.Equal(t, "https://us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) +} + +func TestInvalidImpExt(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}, Ext: json.RawMessage(`"not-an-object"`)}}, + Site: &openrtb2.Site{ID: "271"}, + } + _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "imp.ext") +} + +func TestCallerRequestNotMutated(t *testing.T) { + imp := openrtb2.Imp{ + ID: "imp-1", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, + Ext: json.RawMessage(`{"bidder":{"seat":"abc","region":"eu"}}`), + } + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{imp}, + Site: &openrtb2.Site{ID: "271", Ext: json.RawMessage(`{"amp":0}`)}, + } + before, _ := json.Marshal(req) + + _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Empty(t, errs) + + after, _ := json.Marshal(req) + assert.JSONEq(t, string(before), string(after), "caller's request must not be mutated") + assert.Nil(t, req.Imp[0].Secure, "caller's imp[0].Secure must not be mutated") +} + +func TestGetMediaTypeForBidByMType(t *testing.T) { + cases := []struct { + mtype openrtb2.MarkupType + want openrtb_ext.BidType + }{ + {openrtb2.MarkupBanner, openrtb_ext.BidTypeBanner}, + {openrtb2.MarkupVideo, openrtb_ext.BidTypeVideo}, + {openrtb2.MarkupAudio, openrtb_ext.BidTypeAudio}, + {openrtb2.MarkupNative, openrtb_ext.BidTypeNative}, + } + for _, c := range cases { + bt, err := getMediaTypeForBid(nil, openrtb2.Bid{ImpID: "x", MType: c.mtype}) + assert.NoError(t, err) + assert.Equal(t, c.want, bt) + } +} + +func TestGetMediaTypeForBidUnsupportedMType(t *testing.T) { + _, err := getMediaTypeForBid(nil, openrtb2.Bid{ImpID: "x", MType: openrtb2.MarkupType(99)}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unsupported bid.mtype") +} + +func TestGetMediaTypeForBidSingleFormatFallback(t *testing.T) { + imps := []openrtb2.Imp{ + {ID: "b", Banner: &openrtb2.Banner{}}, + {ID: "v", Video: &openrtb2.Video{}}, + {ID: "a", Audio: &openrtb2.Audio{}}, + {ID: "n", Native: &openrtb2.Native{}}, + } + expected := map[string]openrtb_ext.BidType{ + "b": openrtb_ext.BidTypeBanner, + "v": openrtb_ext.BidTypeVideo, + "a": openrtb_ext.BidTypeAudio, + "n": openrtb_ext.BidTypeNative, + } + for impID, want := range expected { + bt, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: impID}) + assert.NoError(t, err, impID) + assert.Equal(t, want, bt, impID) + } +} + +func TestGetMediaTypeForBidMultiFormatNeedsMType(t *testing.T) { + imps := []openrtb2.Imp{{ID: "m", Banner: &openrtb2.Banner{}, Video: &openrtb2.Video{}}} + _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "m"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "requires bid.mtype to disambiguate") +} + +func TestGetMediaTypeForBidImpWithoutFormat(t *testing.T) { + imps := []openrtb2.Imp{{ID: "x"}} + _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "x"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unable to resolve media type") +} + +func TestGetMediaTypeForBidUnknownImp(t *testing.T) { + imps := []openrtb2.Imp{{ID: "x", Banner: &openrtb2.Banner{}}} + _, err := getMediaTypeForBid(imps, openrtb2.Bid{ImpID: "missing"}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unable to find impression") +} diff --git a/adapters/floxis/floxistest/exemplary/audio-web.json b/adapters/floxis/floxistest/exemplary/audio-web.json new file mode 100644 index 00000000000..bd59a0c655e --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/audio-web.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9, 10] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9, 10] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 1.1, + "adm": "", + "crid": "creative-1", + "mtype": 3 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 1.1, + "adm": "", + "crid": "creative-1", + "mtype": 3 + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/banner-app.json b/adapters/floxis/floxistest/exemplary/banner-app.json new file mode 100644 index 00000000000..82d25e2f379 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-app.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 320, "h": 50 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "app": { + "id": "app-1", + "bundle": "com.example.app" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 320, "h": 50 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "app": { + "id": "app-1", + "bundle": "com.example.app" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 0.9, + "adm": "
ad
", + "crid": "creative-1", + "w": 320, + "h": 50, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.9, + "adm": "
ad
", + "crid": "creative-1", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json new file mode 100644 index 00000000000..a47d5976d82 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 728, "h": 90 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 728, "h": 90 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 0.75, + "adm": "
ad
", + "crid": "creative-1", + "w": 728, + "h": 90 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.75, + "adm": "
ad
", + "crid": "creative-1", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/banner-web-partner.json b/adapters/floxis/floxistest/exemplary/banner-web-partner.json new file mode 100644 index 00000000000..024f0549ad7 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web-partner.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e", + "partner": "acme" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://acme-us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e", + "partner": "acme" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "acme", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/banner-web.json b/adapters/floxis/floxistest/exemplary/banner-web.json new file mode 100644 index 00000000000..71748c4ed75 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/native-app-apac.json b/adapters/floxis/floxistest/exemplary/native-app-apac.json new file mode 100644 index 00000000000..3fa63e8e88a --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/native-app-apac.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "seat": "pub-seat-apac", + "region": "apac" + } + } + } + ], + "app": { + "id": "app-1", + "bundle": "com.example.apac" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://apac.floxis.tech/pbs?seat=pub-seat-apac", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "seat": "pub-seat-apac", + "region": "apac" + } + } + } + ], + "app": { + "id": "app-1", + "bundle": "com.example.apac" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 2.0, + "adm": "{\"native\":{}}", + "crid": "creative-1", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 2.0, + "adm": "{\"native\":{}}", + "crid": "creative-1", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json new file mode 100644 index 00000000000..4dd3055379b --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + }, + "user": { + "ext": { + "consent": "CONSENT-STRING-XYZ" + } + }, + "regs": { + "gdpr": 1, + "gpp": "DBABMA~1YNN", + "gpp_sid": [2, 6], + "us_privacy": "1YNN" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{ "w": 300, "h": 250 }] + }, + "ext": { + "bidder": { + "seat": "pub-seat-1", + "region": "us-e" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.com", + "page": "https://example.com/" + }, + "user": { + "ext": { + "consent": "CONSENT-STRING-XYZ" + } + }, + "regs": { + "gdpr": 1, + "gpp": "DBABMA~1YNN", + "gpp_sid": [2, 6], + "us_privacy": "1YNN" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 1.25, + "adm": "
ad
", + "crid": "creative-1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/exemplary/video-web-eu.json b/adapters/floxis/floxistest/exemplary/video-web-eu.json new file mode 100644 index 00000000000..571ed32508d --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/video-web-eu.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "protocols": [2, 3, 5, 6] + }, + "ext": { + "bidder": { + "seat": "pub-seat-eu", + "region": "eu" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.eu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://eu.floxis.tech/pbs?seat=pub-seat-eu", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "protocols": [2, 3, 5, 6] + }, + "ext": { + "bidder": { + "seat": "pub-seat-eu", + "region": "eu" + } + } + } + ], + "site": { + "id": "271", + "domain": "example.eu" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "EUR", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 3.5, + "adm": "", + "crid": "creative-1", + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 3.5, + "adm": "", + "crid": "creative-1", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json b/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json new file mode 100644 index 00000000000..d755cf5b3c9 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/invalid-bidder-params.json @@ -0,0 +1,20 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "invalid imp.ext.bidder for imp test-imp-id:.*", + "comparison": "regex" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": "not-an-object" } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [] +} diff --git a/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json b/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..6d944746bea --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/invalid-imp-ext.json @@ -0,0 +1,20 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "invalid imp.ext for imp test-imp-id:.*", + "comparison": "regex" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": "not-an-object" + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [] +} diff --git a/adapters/floxis/floxistest/supplemental/invalid-response-body.json b/adapters/floxis/floxistest/supplemental/invalid-response-body.json new file mode 100644 index 00000000000..7516d99beb7 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/invalid-response-body.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": "this-should-be-an-array" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": ".*", + "comparison": "regex" + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json new file mode 100644 index 00000000000..add1f41d2e9 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "v", "impid": "test-imp-id", "price": 4.0, "adm": "", "crid": "c-1", "mtype": 2 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { "id": "v", "impid": "test-imp-id", "price": 4.0, "adm": "", "crid": "c-1", "mtype": 2 }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json b/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json new file mode 100644 index 00000000000..a1f5840a8c0 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "ambiguous", "impid": "test-imp-id", "price": 2.0, "adm": "
?
", "crid": "c-1" } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid for multi-format imp test-imp-id requires bid.mtype to disambiguate", + "comparison": "literal" + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json b/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json new file mode 100644 index 00000000000..0c731c7ee26 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-us" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-us", "region": "us-e", "partner": "floxis" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=seat-us", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-us" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-us", "region": "us-e", "partner": "floxis" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["imp-1", "imp-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + "type": "banner" + }, + { + "bid": { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json new file mode 100644 index 00000000000..64a269c972a --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json @@ -0,0 +1,25 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "imp imp-2 seat/host .* differs from imp imp-1 .*", + "comparison": "regex" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu", "partner": "acme" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu", "partner": "other" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json new file mode 100644 index 00000000000..d91f949f491 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json @@ -0,0 +1,25 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "imp imp-2 seat/host .* differs from imp imp-1 .*", + "comparison": "regex" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-other", "region": "apac" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [] +} diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json new file mode 100644 index 00000000000..a7b8badf405 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://eu.floxis.tech/pbs?seat=seat-eu", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } + }, + { + "id": "imp-2", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["imp-1", "imp-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "EUR", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { "id": "1", "impid": "imp-1", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + "type": "banner" + }, + { + "bid": { "id": "2", "impid": "imp-2", "price": 2.0, "adm": "", "crid": "c2", "mtype": 2 }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json new file mode 100644 index 00000000000..a2038835c4a --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json new file mode 100644 index 00000000000..e1b0f71ca6e --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { "id": "1", "impid": "test-imp-id", "price": 1.0, "adm": "
ad
", "crid": "c1", "mtype": 1 }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/response-204.json b/adapters/floxis/floxistest/supplemental/response-204.json new file mode 100644 index 00000000000..8b1cbfabd0a --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/response-204.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/floxis/floxistest/supplemental/response-400.json b/adapters/floxis/floxistest/supplemental/response-400.json new file mode 100644 index 00000000000..06677c0151f --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/response-400.json @@ -0,0 +1,41 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/response-500.json b/adapters/floxis/floxistest/supplemental/response-500.json new file mode 100644 index 00000000000..78995f1d0cb --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/response-500.json @@ -0,0 +1,41 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/response-no-cur.json b/adapters/floxis/floxistest/supplemental/response-no-cur.json new file mode 100644 index 00000000000..637a5231d36 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/response-no-cur.json @@ -0,0 +1,70 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 1.0, + "adm": "
ad
", + "crid": "creative-1", + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 1.0, + "adm": "
ad
", + "crid": "creative-1", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json new file mode 100644 index 00000000000..de64bbbd56d --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "no-such-imp", "price": 1.0, "crid": "c1" } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unable to find impression no-such-imp for bid", + "comparison": "literal" + } + ] +} diff --git a/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json new file mode 100644 index 00000000000..29547943e43 --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "floxis", + "bid": [ + { "id": "1", "impid": "test-imp-id", "price": 1.0, "crid": "c1", "mtype": 7 } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unsupported bid.mtype 7 for impression test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/floxis/params_test.go b/adapters/floxis/params_test.go new file mode 100644 index 00000000000..b8cdcaafc3c --- /dev/null +++ b/adapters/floxis/params_test.go @@ -0,0 +1,58 @@ +package floxis + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v4/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.BidderFloxis, 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.BidderFloxis, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"seat":"abc"}`, + `{"seat":"abc","region":"us-e"}`, + `{"seat":"abc","region":"eu"}`, + `{"seat":"abc","region":"apac"}`, + `{"seat":"abc","region":"mars"}`, + `{"seat":"x","partner":"acme"}`, + `{"seat":"x","region":"eu","partner":"acme"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `{}`, + `{"region":"us-e"}`, + `{"seat":""}`, + `{"seat":123}`, + `{"seat":"abc","region":"a.b"}`, + `{"seat":"abc","region":123}`, + `{"seat":"x","partner":"a.b/c"}`, + `{"seat":"x","partner":123}`, + `{"seat":"abc","unknownField":"foo"}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 2d6526a1900..1dc4bbb682d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -108,6 +108,7 @@ import ( "github.com/prebid/prebid-server/v4/adapters/feedad" "github.com/prebid/prebid-server/v4/adapters/flatads" "github.com/prebid/prebid-server/v4/adapters/flipp" + "github.com/prebid/prebid-server/v4/adapters/floxis" "github.com/prebid/prebid-server/v4/adapters/freewheelssp" "github.com/prebid/prebid-server/v4/adapters/frvradn" "github.com/prebid/prebid-server/v4/adapters/fwssp" @@ -379,6 +380,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderFeedAd: feedad.Builder, openrtb_ext.BidderFlatads: flatads.Builder, openrtb_ext.BidderFlipp: flipp.Builder, + openrtb_ext.BidderFloxis: floxis.Builder, openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, openrtb_ext.BidderFWSSP: fwssp.Builder, openrtb_ext.BidderFRVRAdNetwork: frvradn.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 9e9cfaa7610..e92305a0491 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -125,6 +125,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderFeedAd, BidderFlatads, BidderFlipp, + BidderFloxis, BidderFreewheelSSP, BidderFWSSP, BidderFRVRAdNetwork, @@ -502,6 +503,7 @@ const ( BidderFeedAd BidderName = "feedad" BidderFlatads BidderName = "flatads" BidderFlipp BidderName = "flipp" + BidderFloxis BidderName = "floxis" BidderFreewheelSSP BidderName = "freewheelssp" BidderFWSSP BidderName = "fwssp" BidderFRVRAdNetwork BidderName = "frvradn" diff --git a/openrtb_ext/imp_floxis.go b/openrtb_ext/imp_floxis.go new file mode 100644 index 00000000000..bbe97d392d2 --- /dev/null +++ b/openrtb_ext/imp_floxis.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpFloxis defines the contract for bidrequest.imp[i].ext.prebid.bidder.floxis. +type ExtImpFloxis struct { + Seat string `json:"seat"` + Region string `json:"region,omitempty"` + Partner string `json:"partner,omitempty"` +} diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml new file mode 100644 index 00000000000..90b4021cf20 --- /dev/null +++ b/static/bidder-info/floxis.yaml @@ -0,0 +1,29 @@ +# {{.Host}} is the region/partner subdomain label (e.g. us-e, or acme-us-e for a named partner), +# validated in the adapter as a single DNS host-label and prefixed onto the fixed .floxis.tech +# domain below; request-supplied hostnames are never used. The seat query param is appended in +# code (not a standard macro). +endpoint: "https://{{.Host}}.floxis.tech/pbs" +maintainer: + email: prebid@floxis.tech +gvlVendorID: 1609 +modifyingVastXmlAllowed: false +openrtb: + version: 2.6 + gpp-supported: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + - audio + site: + mediaTypes: + - banner + - video + - native + - audio +userSync: + redirect: + url: "https://px-us-e.floxis.tech/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&us_privacy={{.USPrivacy}}&dest={{.RedirectURL}}" + userMacro: "${USER_ID}" diff --git a/static/bidder-params/floxis.json b/static/bidder-params/floxis.json new file mode 100644 index 00000000000..3fa51592251 --- /dev/null +++ b/static/bidder-params/floxis.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Floxis Adapter Params", + "description": "A schema which validates params accepted by the Floxis adapter", + "type": "object", + "additionalProperties": false, + "properties": { + "seat": { + "type": "string", + "minLength": 1, + "description": "The Floxis seat ID this publisher buys through" + }, + "region": { + "type": "string", + "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$", + "description": "The Floxis RTB region as a host label (e.g. us-e, eu, apac); defaults to us-e when omitted" + }, + "partner": { + "type": "string", + "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$", + "description": "Partner identifier (default floxis)" + } + }, + "required": ["seat"] +}