From cb262f7e579f14eb596c36a3b44cae2a902b2410 Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Wed, 3 Jun 2026 21:28:36 +0300 Subject: [PATCH 1/8] New Adapter: Floxis Adds the Floxis bidder adapter. Standard OpenRTB 2.x server-to-server; region param selects a fixed RTB host (us-e/eu/apac, default us-e); supports banner, video, native, and audio. --- adapters/floxis/floxis.go | 176 ++++++++++++++++ adapters/floxis/floxis_test.go | 194 ++++++++++++++++++ .../floxistest/exemplary/audio-web.json | 92 +++++++++ .../floxistest/exemplary/banner-app.json | 94 +++++++++ .../exemplary/banner-web-no-mtype.json | 92 +++++++++ .../floxistest/exemplary/banner-web.json | 98 +++++++++ .../floxistest/exemplary/native-app-apac.json | 94 +++++++++ .../floxistest/exemplary/video-web-eu.json | 98 +++++++++ .../supplemental/invalid-bidder-params.json | 20 ++ .../supplemental/invalid-imp-ext.json | 20 ++ .../supplemental/invalid-response-body.json | 45 ++++ .../multi-format-imp-uses-mtype.json | 60 ++++++ ...multi-format-imp-without-mtype-errors.json | 61 ++++++ .../multi-imp-first-imp-routes.json | 73 +++++++ .../supplemental/no-site-or-app-ok.json | 56 +++++ .../region-unknown-defaults-us-e.json | 58 ++++++ .../floxistest/supplemental/response-204.json | 36 ++++ .../floxistest/supplemental/response-400.json | 41 ++++ .../floxistest/supplemental/response-500.json | 41 ++++ .../supplemental/response-no-cur.json | 70 +++++++ .../supplemental/unmatched-bid-impid.json | 59 ++++++ .../supplemental/unsupported-bid-mtype.json | 59 ++++++ adapters/floxis/params_test.go | 53 +++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_floxis.go | 7 + static/bidder-info/floxis.yaml | 27 +++ static/bidder-params/floxis.json | 20 ++ 28 files changed, 1748 insertions(+) create mode 100644 adapters/floxis/floxis.go create mode 100644 adapters/floxis/floxis_test.go create mode 100644 adapters/floxis/floxistest/exemplary/audio-web.json create mode 100644 adapters/floxis/floxistest/exemplary/banner-app.json create mode 100644 adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json create mode 100644 adapters/floxis/floxistest/exemplary/banner-web.json create mode 100644 adapters/floxis/floxistest/exemplary/native-app-apac.json create mode 100644 adapters/floxis/floxistest/exemplary/video-web-eu.json create mode 100644 adapters/floxis/floxistest/supplemental/invalid-bidder-params.json create mode 100644 adapters/floxis/floxistest/supplemental/invalid-imp-ext.json create mode 100644 adapters/floxis/floxistest/supplemental/invalid-response-body.json create mode 100644 adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json create mode 100644 adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json create mode 100644 adapters/floxis/floxistest/supplemental/multi-imp-first-imp-routes.json create mode 100644 adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json create mode 100644 adapters/floxis/floxistest/supplemental/region-unknown-defaults-us-e.json create mode 100644 adapters/floxis/floxistest/supplemental/response-204.json create mode 100644 adapters/floxis/floxistest/supplemental/response-400.json create mode 100644 adapters/floxis/floxistest/supplemental/response-500.json create mode 100644 adapters/floxis/floxistest/supplemental/response-no-cur.json create mode 100644 adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json create mode 100644 adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json create mode 100644 adapters/floxis/params_test.go create mode 100644 openrtb_ext/imp_floxis.go create mode 100644 static/bidder-info/floxis.yaml create mode 100644 static/bidder-params/floxis.json diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go new file mode 100644 index 00000000000..c722dfa2ce7 --- /dev/null +++ b/adapters/floxis/floxis.go @@ -0,0 +1,176 @@ +package floxis + +import ( + "fmt" + "net/url" + + "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/openrtb_ext" + "github.com/prebid/prebid-server/v4/util/jsonutil" +) + +type adapter struct{} + +// regionHosts is a fixed allowlist mapping the bidder's region param to a Floxis RTB +// host. Routing is never derived from request-supplied hostnames; an unknown or empty +// region falls back to us-e. This satisfies PBS's "no fully dynamic hostnames" rule. +var regionHosts = map[string]string{ + "us-e": "rtb-us-e.floxis.tech", + "eu": "rtb-eu.floxis.tech", + "apac": "rtb-apac.floxis.tech", +} + +const defaultRegion = "us-e" + +// resolveHost returns the Floxis RTB host for the given region, defaulting to us-e for +// unknown or empty regions. +func resolveHost(region string) string { + if host, ok := regionHosts[region]; ok { + return host + } + return regionHosts[defaultRegion] +} + +// Builder builds a new instance of the Floxis adapter for the given bidder with the given +// config. The endpoint is resolved per-request from the bidder's region param via a fixed +// host allowlist, so config.Endpoint is intentionally unused. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{}, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{Message: "no impressions in the bid request"}} + } + + ext, err := parseImpExt(request.Imp[0]) + if err != nil { + return nil, []error{err} + } + + host := resolveHost(ext.Region) + uri := fmt.Sprintf("https://%s/pbs?seat=%s", host, url.QueryEscape(ext.Seat)) + + // The request body is forwarded unchanged; no caller-owned struct is mutated, so + // copy-on-write is satisfied by construction. + body, err := jsonutil.Marshal(request) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: "POST", + Uri: uri, + Body: body, + 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 +} + +// getMediaTypeForBid resolves the bid's media type. When bid.mtype (OpenRTB 2.6) is set it +// is treated as authoritative. When unset, a single-format imp's media type is used; +// multi-format imps without mtype cannot be disambiguated and return an error. +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 := 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 + } + 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), + } +} diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go new file mode 100644 index 00000000000..2df46274375 --- /dev/null +++ b/adapters/floxis/floxis_test.go @@ -0,0 +1,194 @@ +package floxis + +import ( + "encoding/json" + "testing" + + "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://rtb-us-e.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{} +} + +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 TestResolveHost(t *testing.T) { + cases := []struct { + region string + want string + }{ + {"us-e", "rtb-us-e.floxis.tech"}, + {"eu", "rtb-eu.floxis.tech"}, + {"apac", "rtb-apac.floxis.tech"}, + {"", "rtb-us-e.floxis.tech"}, + {"mars", "rtb-us-e.floxis.tech"}, + {"US-E", "rtb-us-e.floxis.tech"}, + } + for _, c := range cases { + assert.Equal(t, c.want, resolveHost(c.region), "region %q", c.region) + } +} + +func TestNoImpressions(t *testing.T) { + req := &openrtb2.BidRequest{ID: "req-1"} + reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Nil(t, reqData) + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "no impressions") +} + +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://rtb-eu.floxis.tech/pbs?seat=a+b%26c", reqData[0].Uri) +} + +func TestUnknownRegionFallsBackToUSE(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://rtb-us-e.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://rtb-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") +} + +// TestCallerRequestNotMutated asserts the adapter forwards the request body unchanged and +// does not mutate any caller-owned field (copy-on-write is satisfied by construction). +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..7eadf80e5e6 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/audio-web.json @@ -0,0 +1,92 @@ +{ + "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": { + "uri": "https://rtb-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..8b089d4a82f --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-app.json @@ -0,0 +1,94 @@ +{ + "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": { + "uri": "https://rtb-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..2d358edc49f --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json @@ -0,0 +1,92 @@ +{ + "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": { + "uri": "https://rtb-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.json b/adapters/floxis/floxistest/exemplary/banner-web.json new file mode 100644 index 00000000000..f2489dd1533 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web.json @@ -0,0 +1,98 @@ +{ + "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": { + "uri": "https://rtb-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..7bd681ba437 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/native-app-apac.json @@ -0,0 +1,94 @@ +{ + "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": { + "uri": "https://rtb-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/video-web-eu.json b/adapters/floxis/floxistest/exemplary/video-web-eu.json new file mode 100644 index 00000000000..46917442704 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/video-web-eu.json @@ -0,0 +1,98 @@ +{ + "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": { + "uri": "https://rtb-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..0137d6d6651 --- /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://rtb-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..5ed189e8818 --- /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://rtb-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..5d7a68469b8 --- /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://rtb-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-first-imp-routes.json b/adapters/floxis/floxistest/supplemental/multi-imp-first-imp-routes.json new file mode 100644 index 00000000000..a76edf376ce --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-first-imp-routes.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-other", "region": "apac" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb-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-other", "region": "apac" } } + } + ], + "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..2b2477ebb62 --- /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://rtb-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-unknown-defaults-us-e.json b/adapters/floxis/floxistest/supplemental/region-unknown-defaults-us-e.json new file mode 100644 index 00000000000..c137d2fac9b --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/region-unknown-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", "region": "mars" } } + } + ], + "site": { "id": "271", "domain": "example.com" } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://rtb-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": "mars" } } + } + ], + "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..32484208b3c --- /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://rtb-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..3afb1dc9041 --- /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://rtb-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..42f02c8faa8 --- /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://rtb-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..7f028baba5b --- /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://rtb-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..318a9f30462 --- /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://rtb-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..aced4d0e5f0 --- /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://rtb-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..b97047e55f2 --- /dev/null +++ b/adapters/floxis/params_test.go @@ -0,0 +1,53 @@ +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"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `{}`, + `{"region":"us-e"}`, + `{"seat":""}`, + `{"seat":123}`, + `{"seat":"abc","region":"mars"}`, + `{"seat":"abc","region":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..d0f994a9d7a --- /dev/null +++ b/openrtb_ext/imp_floxis.go @@ -0,0 +1,7 @@ +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"` +} diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml new file mode 100644 index 00000000000..9222aa34b70 --- /dev/null +++ b/static/bidder-info/floxis.yaml @@ -0,0 +1,27 @@ +# The live endpoint is resolved per-request from the bidder's region param via a fixed +# host allowlist in the adapter (us-e/eu/apac). This endpoint value is the us-e default +# used for config validation and as the host-config override anchor. +endpoint: "https://rtb-us-e.floxis.tech/pbs" +maintainer: + email: prebid@floxis.tech +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..6d6d2ecd640 --- /dev/null +++ b/static/bidder-params/floxis.json @@ -0,0 +1,20 @@ +{ + "$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", + "enum": ["us-e", "eu", "apac"], + "description": "The Floxis RTB region; defaults to us-e when omitted" + } + }, + "required": ["seat"] +} From f4f25d3aa5625e724c92780c92050ccf3822eb50 Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Fri, 5 Jun 2026 16:31:09 +0300 Subject: [PATCH 2/8] Floxis: set gvlVendorID to 1609 (IAB TCF registration approved) --- static/bidder-info/floxis.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml index 9222aa34b70..b42a4279d72 100644 --- a/static/bidder-info/floxis.yaml +++ b/static/bidder-info/floxis.yaml @@ -4,6 +4,7 @@ endpoint: "https://rtb-us-e.floxis.tech/pbs" maintainer: email: prebid@floxis.tech +gvlVendorID: 1609 modifyingVastXmlAllowed: false openrtb: version: 2.6 From 0f38f98f803308897d4ce1cf3a5f73434417341c Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Sat, 6 Jun 2026 12:25:45 +0300 Subject: [PATCH 3/8] Floxis: endpoint from config + multi-imp seat guard + privacy passthrough test --- adapters/floxis/floxis.go | 37 +++++- adapters/floxis/floxis_test.go | 5 +- .../exemplary/privacy-passthrough.json | 120 ++++++++++++++++++ .../multi-imp-different-seat-errors.json | 25 ++++ ...n => multi-imp-same-seat-routes-once.json} | 4 +- static/bidder-info/floxis.yaml | 8 +- 6 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 adapters/floxis/floxistest/exemplary/privacy-passthrough.json create mode 100644 adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json rename adapters/floxis/floxistest/supplemental/{multi-imp-first-imp-routes.json => multi-imp-same-seat-routes-once.json} (92%) diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index c722dfa2ce7..a3c9354a53f 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -3,16 +3,20 @@ package floxis import ( "fmt" "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{} +type adapter struct { + endpoint *template.Template +} // regionHosts is a fixed allowlist mapping the bidder's region param to a Floxis RTB // host. Routing is never derived from request-supplied hostnames; an unknown or empty @@ -35,10 +39,14 @@ func resolveHost(region string) string { } // Builder builds a new instance of the Floxis adapter for the given bidder with the given -// config. The endpoint is resolved per-request from the bidder's region param via a fixed -// host allowlist, so config.Endpoint is intentionally unused. +// config. config.Endpoint is the {{.Host}} template; the host is filled per-request from the +// bidder's region param via a fixed allowlist (never from request-supplied hostnames). func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - return &adapter{}, nil + 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) { @@ -51,8 +59,27 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte return nil, []error{err} } + // All imps in one call route under imp[0]'s seat/host. Reject a request whose imps carry + // differing floxis seats or regions rather than silently mis-routing imp[1..]. + for i := 1; i < len(request.Imp); i++ { + impExt, err := parseImpExt(request.Imp[i]) + if err != nil { + return nil, []error{err} + } + if impExt.Seat != ext.Seat || impExt.Region != ext.Region { + return nil, []error{&errortypes.BadInput{Message: fmt.Sprintf( + "imp %s seat/region (%q/%q) differs from imp %s (%q/%q); split into separate requests", + request.Imp[i].ID, impExt.Seat, impExt.Region, request.Imp[0].ID, ext.Seat, ext.Region)}} + } + } + host := resolveHost(ext.Region) - uri := fmt.Sprintf("https://%s/pbs?seat=%s", host, url.QueryEscape(ext.Seat)) + endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: host}) + if err != nil { + return nil, []error{err} + } + // seat is not a standard endpoint macro, so it is appended in code as a query param. + uri := fmt.Sprintf("%s?seat=%s", endpoint, url.QueryEscape(ext.Seat)) // The request body is forwarded unchanged; no caller-owned struct is mutated, so // copy-on-write is satisfied by construction. diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go index 2df46274375..410a486911b 100644 --- a/adapters/floxis/floxis_test.go +++ b/adapters/floxis/floxis_test.go @@ -3,6 +3,7 @@ package floxis import ( "encoding/json" "testing" + "text/template" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v4/adapters" @@ -14,7 +15,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderFloxis, config.Adapter{ - Endpoint: "https://rtb-us-e.floxis.tech/pbs"}, + Endpoint: "https://{{.Host}}/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", DataCenter: "2"}) if buildErr != nil { @@ -25,7 +26,7 @@ func TestJsonSamples(t *testing.T) { } func newAdapter() *adapter { - return &adapter{} + return &adapter{endpoint: template.Must(template.New("endpointTemplate").Parse("https://{{.Host}}/pbs"))} } func bannerImp(ext string) openrtb2.Imp { diff --git a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json new file mode 100644 index 00000000000..29d8bbf8b86 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json @@ -0,0 +1,120 @@ +{ + "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": { + "uri": "https://rtb-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/supplemental/multi-imp-different-seat-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json new file mode 100644 index 00000000000..75bda339eef --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json @@ -0,0 +1,25 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "imp imp-2 seat/region .* 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-first-imp-routes.json b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json similarity index 92% rename from adapters/floxis/floxistest/supplemental/multi-imp-first-imp-routes.json rename to adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json index a76edf376ce..7fdb3f1e3eb 100644 --- a/adapters/floxis/floxistest/supplemental/multi-imp-first-imp-routes.json +++ b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json @@ -10,7 +10,7 @@ { "id": "imp-2", "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, - "ext": { "bidder": { "seat": "seat-other", "region": "apac" } } + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } } ], "site": { "id": "271", "domain": "example.com" } @@ -30,7 +30,7 @@ { "id": "imp-2", "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, - "ext": { "bidder": { "seat": "seat-other", "region": "apac" } } + "ext": { "bidder": { "seat": "seat-eu", "region": "eu" } } } ], "site": { "id": "271", "domain": "example.com" } diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml index b42a4279d72..5bb5d1105d1 100644 --- a/static/bidder-info/floxis.yaml +++ b/static/bidder-info/floxis.yaml @@ -1,7 +1,7 @@ -# The live endpoint is resolved per-request from the bidder's region param via a fixed -# host allowlist in the adapter (us-e/eu/apac). This endpoint value is the us-e default -# used for config validation and as the host-config override anchor. -endpoint: "https://rtb-us-e.floxis.tech/pbs" +# The {{.Host}} macro is filled per-request from the bidder's region param via a fixed +# host allowlist in the adapter (us-e/eu/apac); routing is never derived from request- +# supplied hostnames. The seat query param is appended in code (not a standard macro). +endpoint: "https://{{.Host}}/pbs" maintainer: email: prebid@floxis.tech gvlVendorID: 1609 From 26ab0bfa29a944bd90a93ea922ab455b0cf8dff7 Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Sat, 6 Jun 2026 15:20:34 +0300 Subject: [PATCH 4/8] Floxis: mirror Prebid.js seat/region/partner host logic Co-Authored-By: Claude Opus 4.8 --- adapters/floxis/floxis.go | 61 +++++++---- adapters/floxis/floxis_test.go | 64 ++++++++--- .../floxistest/exemplary/audio-web.json | 2 +- .../floxistest/exemplary/banner-app.json | 2 +- .../exemplary/banner-web-no-mtype.json | 2 +- .../exemplary/banner-web-partner.json | 100 ++++++++++++++++++ .../floxistest/exemplary/banner-web.json | 2 +- .../floxistest/exemplary/native-app-apac.json | 2 +- .../exemplary/privacy-passthrough.json | 2 +- .../floxistest/exemplary/video-web-eu.json | 2 +- .../supplemental/invalid-partner-errors.json | 20 ++++ .../supplemental/invalid-response-body.json | 2 +- .../multi-format-imp-uses-mtype.json | 2 +- ...multi-format-imp-without-mtype-errors.json | 2 +- .../multi-imp-different-partner-errors.json | 25 +++++ .../multi-imp-different-seat-errors.json | 2 +- .../multi-imp-same-seat-routes-once.json | 2 +- .../supplemental/no-site-or-app-ok.json | 2 +- ...json => region-missing-defaults-us-e.json} | 6 +- .../floxistest/supplemental/response-204.json | 2 +- .../floxistest/supplemental/response-400.json | 2 +- .../floxistest/supplemental/response-500.json | 2 +- .../supplemental/response-no-cur.json | 2 +- .../supplemental/unmatched-bid-impid.json | 2 +- .../supplemental/unsupported-bid-mtype.json | 2 +- adapters/floxis/params_test.go | 7 +- openrtb_ext/imp_floxis.go | 5 +- static/bidder-params/floxis.json | 9 +- 28 files changed, 273 insertions(+), 62 deletions(-) create mode 100644 adapters/floxis/floxistest/exemplary/banner-web-partner.json create mode 100644 adapters/floxis/floxistest/supplemental/invalid-partner-errors.json create mode 100644 adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json rename adapters/floxis/floxistest/supplemental/{region-unknown-defaults-us-e.json => region-missing-defaults-us-e.json} (85%) diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index a3c9354a53f..cdda58eb137 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -3,6 +3,7 @@ package floxis import ( "fmt" "net/url" + "regexp" "text/template" "github.com/prebid/openrtb/v20/openrtb2" @@ -18,29 +19,43 @@ type adapter struct { endpoint *template.Template } -// regionHosts is a fixed allowlist mapping the bidder's region param to a Floxis RTB -// host. Routing is never derived from request-supplied hostnames; an unknown or empty -// region falls back to us-e. This satisfies PBS's "no fully dynamic hostnames" rule. -var regionHosts = map[string]string{ - "us-e": "rtb-us-e.floxis.tech", - "eu": "rtb-eu.floxis.tech", - "apac": "rtb-apac.floxis.tech", -} +const ( + defaultRegion = "us-e" + defaultPartner = "floxis" +) -const defaultRegion = "us-e" +// hostLabelRegex mirrors Prebid.js HOST_LABEL_REGEX: region/partner are interpolated into the +// request host, so they must be valid DNS labels — a value carrying URL delimiters could +// otherwise rewrite the request origin. Case-insensitive, single label, max 63 chars. +var hostLabelRegex = regexp.MustCompile(`(?i)^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$`) -// resolveHost returns the Floxis RTB host for the given region, defaulting to us-e for -// unknown or empty regions. -func resolveHost(region string) string { - if host, ok := regionHosts[region]; ok { - return host +func isValidHostLabel(s string) bool { + return hostLabelRegex.MatchString(s) +} + +// resolveBidHost mirrors Prebid.js getBidHost: empty region/partner default to us-e/floxis, +// each must be a valid host label, and floxis itself carries no partner prefix. Routing +// (which region maps to which datacenter) is handled at DNS/LB level, not here. +func resolveBidHost(region, partner string) (string, error) { + if region == "" { + region = defaultRegion + } + if partner == "" { + partner = defaultPartner } - return regionHosts[defaultRegion] + if !isValidHostLabel(region) || !isValidHostLabel(partner) { + return "", &errortypes.BadInput{Message: fmt.Sprintf( + "invalid region %q or partner %q; both must be valid host labels", region, partner)} + } + if partner == defaultPartner { + return region + ".floxis.tech", nil + } + return partner + "-" + region + ".floxis.tech", nil } // Builder builds a new instance of the Floxis adapter for the given bidder with the given // config. config.Endpoint is the {{.Host}} template; the host is filled per-request from the -// bidder's region param via a fixed allowlist (never from request-supplied hostnames). +// bidder's region/partner params (validated as host labels — never raw request-supplied hostnames). 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 { @@ -60,20 +75,24 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } // All imps in one call route under imp[0]'s seat/host. Reject a request whose imps carry - // differing floxis seats or regions rather than silently mis-routing imp[1..]. + // differing floxis seats, regions, or partners rather than silently mis-routing imp[1..]. for i := 1; i < len(request.Imp); i++ { impExt, err := parseImpExt(request.Imp[i]) if err != nil { return nil, []error{err} } - if impExt.Seat != ext.Seat || impExt.Region != ext.Region { + if impExt.Seat != ext.Seat || impExt.Region != ext.Region || impExt.Partner != ext.Partner { return nil, []error{&errortypes.BadInput{Message: fmt.Sprintf( - "imp %s seat/region (%q/%q) differs from imp %s (%q/%q); split into separate requests", - request.Imp[i].ID, impExt.Seat, impExt.Region, request.Imp[0].ID, ext.Seat, ext.Region)}} + "imp %s seat/region/partner (%q/%q/%q) differs from imp %s (%q/%q/%q); split into separate requests", + request.Imp[i].ID, impExt.Seat, impExt.Region, impExt.Partner, + request.Imp[0].ID, ext.Seat, ext.Region, ext.Partner)}} } } - host := resolveHost(ext.Region) + host, err := resolveBidHost(ext.Region, ext.Partner) + if err != nil { + return nil, []error{err} + } endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: host}) if err != nil { return nil, []error{err} diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go index 410a486911b..6bcb4122f28 100644 --- a/adapters/floxis/floxis_test.go +++ b/adapters/floxis/floxis_test.go @@ -37,20 +37,34 @@ func bannerImp(ext string) openrtb2.Imp { } } -func TestResolveHost(t *testing.T) { +func TestResolveBidHost(t *testing.T) { cases := []struct { - region string - want string + region string + partner string + want string + wantErr bool }{ - {"us-e", "rtb-us-e.floxis.tech"}, - {"eu", "rtb-eu.floxis.tech"}, - {"apac", "rtb-apac.floxis.tech"}, - {"", "rtb-us-e.floxis.tech"}, - {"mars", "rtb-us-e.floxis.tech"}, - {"US-E", "rtb-us-e.floxis.tech"}, + {"us-e", "floxis", "us-e.floxis.tech", false}, + {"eu", "floxis", "eu.floxis.tech", false}, + {"apac", "floxis", "apac.floxis.tech", false}, + {"", "", "us-e.floxis.tech", false}, // both default + {"", "floxis", "us-e.floxis.tech", false}, // empty region defaults to us-e + {"eu", "", "eu.floxis.tech", false}, // empty partner defaults to floxis + {"mars", "floxis", "mars.floxis.tech", false}, // any valid label passes through + {"us-e", "acme", "acme-us-e.floxis.tech", false}, + {"eu", "acme", "acme-eu.floxis.tech", false}, + {"a.b", "floxis", "", true}, // invalid region label + {"us-e", "bad_host!", "", true}, // invalid partner label + {"evil.com/x", "floxis", "", true}, } for _, c := range cases { - assert.Equal(t, c.want, resolveHost(c.region), "region %q", c.region) + got, err := resolveBidHost(c.region, c.partner) + if c.wantErr { + assert.Error(t, err, "region %q partner %q", c.region, c.partner) + continue + } + assert.NoError(t, err, "region %q partner %q", c.region, c.partner) + assert.Equal(t, c.want, got, "region %q partner %q", c.region, c.partner) } } @@ -71,10 +85,32 @@ func TestSeatIsURLEscaped(t *testing.T) { reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) assert.Empty(t, errs) assert.Len(t, reqData, 1) - assert.Equal(t, "https://rtb-eu.floxis.tech/pbs?seat=a+b%26c", reqData[0].Uri) + assert.Equal(t, "https://eu.floxis.tech/pbs?seat=a+b%26c", reqData[0].Uri) } -func TestUnknownRegionFallsBackToUSE(t *testing.T) { +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 TestInvalidPartnerRejected(t *testing.T) { + req := &openrtb2.BidRequest{ + ID: "req-1", + Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","partner":"bad_host!"}}`)}, + Site: &openrtb2.Site{ID: "271"}, + } + _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "valid host labels") +} + +func TestValidNonStandardRegionPassesThrough(t *testing.T) { req := &openrtb2.BidRequest{ ID: "req-1", Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","region":"mars"}}`)}, @@ -82,7 +118,7 @@ func TestUnknownRegionFallsBackToUSE(t *testing.T) { } reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) assert.Empty(t, errs) - assert.Equal(t, "https://rtb-us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) + assert.Equal(t, "https://mars.floxis.tech/pbs?seat=abc", reqData[0].Uri) } func TestMissingRegionDefaultsToUSE(t *testing.T) { @@ -93,7 +129,7 @@ func TestMissingRegionDefaultsToUSE(t *testing.T) { } reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) assert.Empty(t, errs) - assert.Equal(t, "https://rtb-us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) + assert.Equal(t, "https://us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) } func TestInvalidImpExt(t *testing.T) { diff --git a/adapters/floxis/floxistest/exemplary/audio-web.json b/adapters/floxis/floxistest/exemplary/audio-web.json index 7eadf80e5e6..f34f971b412 100644 --- a/adapters/floxis/floxistest/exemplary/audio-web.json +++ b/adapters/floxis/floxistest/exemplary/audio-web.json @@ -23,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/exemplary/banner-app.json b/adapters/floxis/floxistest/exemplary/banner-app.json index 8b089d4a82f..c2912f4378c 100644 --- a/adapters/floxis/floxistest/exemplary/banner-app.json +++ b/adapters/floxis/floxistest/exemplary/banner-app.json @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json index 2d358edc49f..b3c0363e844 100644 --- a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json +++ b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ 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..d13589c6685 --- /dev/null +++ b/adapters/floxis/floxistest/exemplary/banner-web-partner.json @@ -0,0 +1,100 @@ +{ + "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": { + "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 index f2489dd1533..6192dd0636b 100644 --- a/adapters/floxis/floxistest/exemplary/banner-web.json +++ b/adapters/floxis/floxistest/exemplary/banner-web.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/exemplary/native-app-apac.json b/adapters/floxis/floxistest/exemplary/native-app-apac.json index 7bd681ba437..98319ebc25f 100644 --- a/adapters/floxis/floxistest/exemplary/native-app-apac.json +++ b/adapters/floxis/floxistest/exemplary/native-app-apac.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-apac.floxis.tech/pbs?seat=pub-seat-apac", + "uri": "https://apac.floxis.tech/pbs?seat=pub-seat-apac", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json index 29d8bbf8b86..c9c2b2d38f5 100644 --- a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json +++ b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/exemplary/video-web-eu.json b/adapters/floxis/floxistest/exemplary/video-web-eu.json index 46917442704..c0ba492bc2d 100644 --- a/adapters/floxis/floxistest/exemplary/video-web-eu.json +++ b/adapters/floxis/floxistest/exemplary/video-web-eu.json @@ -26,7 +26,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-eu.floxis.tech/pbs?seat=pub-seat-eu", + "uri": "https://eu.floxis.tech/pbs?seat=pub-seat-eu", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json b/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json new file mode 100644 index 00000000000..f757582805f --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json @@ -0,0 +1,20 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "invalid region .* or partner .*; both must be valid host labels", + "comparison": "regex" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { "format": [{ "w": 300, "h": 250 }] }, + "ext": { "bidder": { "seat": "pub-seat-1", "partner": "bad_host!" } } + } + ], + "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 index 0137d6d6651..7516d99beb7 100644 --- a/adapters/floxis/floxistest/supplemental/invalid-response-body.json +++ b/adapters/floxis/floxistest/supplemental/invalid-response-body.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json index 5ed189e8818..add1f41d2e9 100644 --- a/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json +++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-uses-mtype.json @@ -14,7 +14,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ 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 index 5d7a68469b8..a1f5840a8c0 100644 --- a/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json +++ b/adapters/floxis/floxistest/supplemental/multi-format-imp-without-mtype-errors.json @@ -14,7 +14,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ 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..e79097bc24e --- /dev/null +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json @@ -0,0 +1,25 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "imp imp-2 seat/region/partner .* 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 index 75bda339eef..41d12761097 100644 --- a/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "imp imp-2 seat/region .* differs from imp imp-1 .*", + "value": "imp imp-2 seat/region/partner .* differs from imp imp-1 .*", "comparison": "regex" } ], 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 index 7fdb3f1e3eb..a7b8badf405 100644 --- a/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json +++ b/adapters/floxis/floxistest/supplemental/multi-imp-same-seat-routes-once.json @@ -18,7 +18,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-eu.floxis.tech/pbs?seat=seat-eu", + "uri": "https://eu.floxis.tech/pbs?seat=seat-eu", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json index 2b2477ebb62..a2038835c4a 100644 --- a/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json +++ b/adapters/floxis/floxistest/supplemental/no-site-or-app-ok.json @@ -12,7 +12,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/region-unknown-defaults-us-e.json b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json similarity index 85% rename from adapters/floxis/floxistest/supplemental/region-unknown-defaults-us-e.json rename to adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json index c137d2fac9b..e1b0f71ca6e 100644 --- a/adapters/floxis/floxistest/supplemental/region-unknown-defaults-us-e.json +++ b/adapters/floxis/floxistest/supplemental/region-missing-defaults-us-e.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "banner": { "format": [{ "w": 300, "h": 250 }] }, - "ext": { "bidder": { "seat": "pub-seat-1", "region": "mars" } } + "ext": { "bidder": { "seat": "pub-seat-1" } } } ], "site": { "id": "271", "domain": "example.com" } @@ -13,14 +13,14 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "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": "mars" } } + "ext": { "bidder": { "seat": "pub-seat-1" } } } ], "site": { "id": "271", "domain": "example.com" } diff --git a/adapters/floxis/floxistest/supplemental/response-204.json b/adapters/floxis/floxistest/supplemental/response-204.json index 32484208b3c..8b1cbfabd0a 100644 --- a/adapters/floxis/floxistest/supplemental/response-204.json +++ b/adapters/floxis/floxistest/supplemental/response-204.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/response-400.json b/adapters/floxis/floxistest/supplemental/response-400.json index 3afb1dc9041..06677c0151f 100644 --- a/adapters/floxis/floxistest/supplemental/response-400.json +++ b/adapters/floxis/floxistest/supplemental/response-400.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/response-500.json b/adapters/floxis/floxistest/supplemental/response-500.json index 42f02c8faa8..78995f1d0cb 100644 --- a/adapters/floxis/floxistest/supplemental/response-500.json +++ b/adapters/floxis/floxistest/supplemental/response-500.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/response-no-cur.json b/adapters/floxis/floxistest/supplemental/response-no-cur.json index 7f028baba5b..637a5231d36 100644 --- a/adapters/floxis/floxistest/supplemental/response-no-cur.json +++ b/adapters/floxis/floxistest/supplemental/response-no-cur.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json index 318a9f30462..de64bbbd56d 100644 --- a/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json +++ b/adapters/floxis/floxistest/supplemental/unmatched-bid-impid.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json index aced4d0e5f0..29547943e43 100644 --- a/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json +++ b/adapters/floxis/floxistest/supplemental/unsupported-bid-mtype.json @@ -13,7 +13,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://rtb-us-e.floxis.tech/pbs?seat=pub-seat-1", + "uri": "https://us-e.floxis.tech/pbs?seat=pub-seat-1", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/floxis/params_test.go b/adapters/floxis/params_test.go index b97047e55f2..b8cdcaafc3c 100644 --- a/adapters/floxis/params_test.go +++ b/adapters/floxis/params_test.go @@ -38,6 +38,9 @@ var validParams = []string{ `{"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{ @@ -47,7 +50,9 @@ var invalidParams = []string{ `{"region":"us-e"}`, `{"seat":""}`, `{"seat":123}`, - `{"seat":"abc","region":"mars"}`, + `{"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/openrtb_ext/imp_floxis.go b/openrtb_ext/imp_floxis.go index d0f994a9d7a..bbe97d392d2 100644 --- a/openrtb_ext/imp_floxis.go +++ b/openrtb_ext/imp_floxis.go @@ -2,6 +2,7 @@ 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"` + Seat string `json:"seat"` + Region string `json:"region,omitempty"` + Partner string `json:"partner,omitempty"` } diff --git a/static/bidder-params/floxis.json b/static/bidder-params/floxis.json index 6d6d2ecd640..3fa51592251 100644 --- a/static/bidder-params/floxis.json +++ b/static/bidder-params/floxis.json @@ -12,8 +12,13 @@ }, "region": { "type": "string", - "enum": ["us-e", "eu", "apac"], - "description": "The Floxis RTB region; defaults to us-e when omitted" + "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"] From 65d761c912550c64dbf85defff43d9d9bc1f234c Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Mon, 15 Jun 2026 21:05:50 +0300 Subject: [PATCH 5/8] Floxis adapter: send Content-Type/Accept headers; correct bidder-info host comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MakeRequests now sets Content-Type: application/json;charset=utf-8 and Accept: application/json on the outbound POST (the dominant convention across in-tree adapters), asserted in all exemplary test cases. - Reword the bidder-info comment: region/partner are validated as single DNS host-labels prefixed onto the fixed .floxis.tech domain — there is no fixed us-e/eu/apac allowlist (the prior comment misdescribed the SSRF control). --- adapters/floxis/floxis.go | 14 ++++++++++---- .../floxis/floxistest/exemplary/audio-web.json | 4 ++++ .../floxis/floxistest/exemplary/banner-app.json | 4 ++++ .../floxistest/exemplary/banner-web-no-mtype.json | 4 ++++ .../floxistest/exemplary/banner-web-partner.json | 4 ++++ .../floxis/floxistest/exemplary/banner-web.json | 4 ++++ .../floxistest/exemplary/native-app-apac.json | 4 ++++ .../floxistest/exemplary/privacy-passthrough.json | 4 ++++ .../floxis/floxistest/exemplary/video-web-eu.json | 4 ++++ static/bidder-info/floxis.yaml | 7 ++++--- 10 files changed, 46 insertions(+), 7 deletions(-) diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index cdda58eb137..a4934bf255d 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -2,6 +2,7 @@ package floxis import ( "fmt" + "net/http" "net/url" "regexp" "text/template" @@ -107,11 +108,16 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte 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, - ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + Method: "POST", + Uri: uri, + Body: body, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } diff --git a/adapters/floxis/floxistest/exemplary/audio-web.json b/adapters/floxis/floxistest/exemplary/audio-web.json index f34f971b412..bd59a0c655e 100644 --- a/adapters/floxis/floxistest/exemplary/audio-web.json +++ b/adapters/floxis/floxistest/exemplary/audio-web.json @@ -23,6 +23,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/banner-app.json b/adapters/floxis/floxistest/exemplary/banner-app.json index c2912f4378c..82d25e2f379 100644 --- a/adapters/floxis/floxistest/exemplary/banner-app.json +++ b/adapters/floxis/floxistest/exemplary/banner-app.json @@ -22,6 +22,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json index b3c0363e844..a47d5976d82 100644 --- a/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json +++ b/adapters/floxis/floxistest/exemplary/banner-web-no-mtype.json @@ -22,6 +22,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/banner-web-partner.json b/adapters/floxis/floxistest/exemplary/banner-web-partner.json index d13589c6685..024f0549ad7 100644 --- a/adapters/floxis/floxistest/exemplary/banner-web-partner.json +++ b/adapters/floxis/floxistest/exemplary/banner-web-partner.json @@ -25,6 +25,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/banner-web.json b/adapters/floxis/floxistest/exemplary/banner-web.json index 6192dd0636b..71748c4ed75 100644 --- a/adapters/floxis/floxistest/exemplary/banner-web.json +++ b/adapters/floxis/floxistest/exemplary/banner-web.json @@ -24,6 +24,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/native-app-apac.json b/adapters/floxis/floxistest/exemplary/native-app-apac.json index 98319ebc25f..3fa63e8e88a 100644 --- a/adapters/floxis/floxistest/exemplary/native-app-apac.json +++ b/adapters/floxis/floxistest/exemplary/native-app-apac.json @@ -24,6 +24,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json index c9c2b2d38f5..4dd3055379b 100644 --- a/adapters/floxis/floxistest/exemplary/privacy-passthrough.json +++ b/adapters/floxis/floxistest/exemplary/privacy-passthrough.json @@ -35,6 +35,10 @@ "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", diff --git a/adapters/floxis/floxistest/exemplary/video-web-eu.json b/adapters/floxis/floxistest/exemplary/video-web-eu.json index c0ba492bc2d..571ed32508d 100644 --- a/adapters/floxis/floxistest/exemplary/video-web-eu.json +++ b/adapters/floxis/floxistest/exemplary/video-web-eu.json @@ -26,6 +26,10 @@ "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", diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml index 5bb5d1105d1..e93bf034c42 100644 --- a/static/bidder-info/floxis.yaml +++ b/static/bidder-info/floxis.yaml @@ -1,6 +1,7 @@ -# The {{.Host}} macro is filled per-request from the bidder's region param via a fixed -# host allowlist in the adapter (us-e/eu/apac); routing is never derived from request- -# supplied hostnames. The seat query param is appended in code (not a standard macro). +# The {{.Host}} macro is filled per-request from the bidder's region (and optional partner) +# params — each validated as a single DNS host-label and interpolated as a subdomain of the +# fixed .floxis.tech base domain; request-supplied hostnames are never used. The seat query +# param is appended in code (not a standard macro). endpoint: "https://{{.Host}}/pbs" maintainer: email: prebid@floxis.tech From a141441d6a3fac1951c7e09178dac138d4b52e8f Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Mon, 15 Jun 2026 22:35:35 +0300 Subject: [PATCH 6/8] Floxis adapter: pin fixed .floxis.tech domain in endpoint (region/partner are subdomain labels) Per the dev-guide, a bidder endpoint domain must not be fully variable. Move the fixed .floxis.tech suffix into the bidder-info endpoint template and have resolveBidHost return just the validated region/partner subdomain label. Resolved URLs are unchanged (e.g. https://us-e.floxis.tech/pbs); this matches the fixed-suffix pattern of merged region-routed adapters (rubicon, clydo, mediago, algorix). --- adapters/floxis/floxis.go | 11 ++++++----- adapters/floxis/floxis_test.go | 22 +++++++++++----------- static/bidder-info/floxis.yaml | 10 +++++----- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index a4934bf255d..2909c7f0a45 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -34,9 +34,10 @@ func isValidHostLabel(s string) bool { return hostLabelRegex.MatchString(s) } -// resolveBidHost mirrors Prebid.js getBidHost: empty region/partner default to us-e/floxis, -// each must be a valid host label, and floxis itself carries no partner prefix. Routing -// (which region maps to which datacenter) is handled at DNS/LB level, not here. +// resolveBidHost returns the {{.Host}} subdomain label for the endpoint template (which pins +// the fixed .floxis.tech domain). Empty region/partner default to us-e/floxis, each must be a +// valid host label, and floxis itself carries no partner prefix. Which region maps to which +// datacenter is handled at DNS/LB level, not here. func resolveBidHost(region, partner string) (string, error) { if region == "" { region = defaultRegion @@ -49,9 +50,9 @@ func resolveBidHost(region, partner string) (string, error) { "invalid region %q or partner %q; both must be valid host labels", region, partner)} } if partner == defaultPartner { - return region + ".floxis.tech", nil + return region, nil } - return partner + "-" + region + ".floxis.tech", nil + return partner + "-" + region, nil } // Builder builds a new instance of the Floxis adapter for the given bidder with the given diff --git a/adapters/floxis/floxis_test.go b/adapters/floxis/floxis_test.go index 6bcb4122f28..dca3911915d 100644 --- a/adapters/floxis/floxis_test.go +++ b/adapters/floxis/floxis_test.go @@ -15,7 +15,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderFloxis, config.Adapter{ - Endpoint: "https://{{.Host}}/pbs"}, + Endpoint: "https://{{.Host}}.floxis.tech/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", DataCenter: "2"}) if buildErr != nil { @@ -26,7 +26,7 @@ func TestJsonSamples(t *testing.T) { } func newAdapter() *adapter { - return &adapter{endpoint: template.Must(template.New("endpointTemplate").Parse("https://{{.Host}}/pbs"))} + return &adapter{endpoint: template.Must(template.New("endpointTemplate").Parse("https://{{.Host}}.floxis.tech/pbs"))} } func bannerImp(ext string) openrtb2.Imp { @@ -44,15 +44,15 @@ func TestResolveBidHost(t *testing.T) { want string wantErr bool }{ - {"us-e", "floxis", "us-e.floxis.tech", false}, - {"eu", "floxis", "eu.floxis.tech", false}, - {"apac", "floxis", "apac.floxis.tech", false}, - {"", "", "us-e.floxis.tech", false}, // both default - {"", "floxis", "us-e.floxis.tech", false}, // empty region defaults to us-e - {"eu", "", "eu.floxis.tech", false}, // empty partner defaults to floxis - {"mars", "floxis", "mars.floxis.tech", false}, // any valid label passes through - {"us-e", "acme", "acme-us-e.floxis.tech", false}, - {"eu", "acme", "acme-eu.floxis.tech", false}, + {"us-e", "floxis", "us-e", false}, + {"eu", "floxis", "eu", false}, + {"apac", "floxis", "apac", false}, + {"", "", "us-e", false}, // both default + {"", "floxis", "us-e", false}, // empty region defaults to us-e + {"eu", "", "eu", false}, // empty partner defaults to floxis + {"mars", "floxis", "mars", false}, // any valid label passes through + {"us-e", "acme", "acme-us-e", false}, + {"eu", "acme", "acme-eu", false}, {"a.b", "floxis", "", true}, // invalid region label {"us-e", "bad_host!", "", true}, // invalid partner label {"evil.com/x", "floxis", "", true}, diff --git a/static/bidder-info/floxis.yaml b/static/bidder-info/floxis.yaml index e93bf034c42..90b4021cf20 100644 --- a/static/bidder-info/floxis.yaml +++ b/static/bidder-info/floxis.yaml @@ -1,8 +1,8 @@ -# The {{.Host}} macro is filled per-request from the bidder's region (and optional partner) -# params — each validated as a single DNS host-label and interpolated as a subdomain of the -# fixed .floxis.tech base domain; request-supplied hostnames are never used. The seat query -# param is appended in code (not a standard macro). -endpoint: "https://{{.Host}}/pbs" +# {{.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 From b9140f6946e072e75cf97d2ef6cab8af6ff7b196 Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Thu, 18 Jun 2026 12:13:42 +0300 Subject: [PATCH 7/8] Floxis adapter: drop redundant validation and comments, extract countFormats Remove the in-code host-label regex (the params JSON schema already enforces the same pattern before MakeRequests runs), the redundant empty-imp guard (the framework only invokes the adapter with at least one imp), and the explanatory comment blocks. Extract a countFormats helper; resolveBidHost no longer needs to return an error. Schema-level host-label rejection stays covered by params_test.go. --- adapters/floxis/floxis.go | 87 ++++++------------- adapters/floxis/floxis_test.go | 51 +++-------- .../supplemental/invalid-partner-errors.json | 20 ----- 3 files changed, 38 insertions(+), 120 deletions(-) delete mode 100644 adapters/floxis/floxistest/supplemental/invalid-partner-errors.json diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index 2909c7f0a45..00f2b46504a 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "net/url" - "regexp" "text/template" "github.com/prebid/openrtb/v20/openrtb2" @@ -25,39 +24,20 @@ const ( defaultPartner = "floxis" ) -// hostLabelRegex mirrors Prebid.js HOST_LABEL_REGEX: region/partner are interpolated into the -// request host, so they must be valid DNS labels — a value carrying URL delimiters could -// otherwise rewrite the request origin. Case-insensitive, single label, max 63 chars. -var hostLabelRegex = regexp.MustCompile(`(?i)^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$`) - -func isValidHostLabel(s string) bool { - return hostLabelRegex.MatchString(s) -} - -// resolveBidHost returns the {{.Host}} subdomain label for the endpoint template (which pins -// the fixed .floxis.tech domain). Empty region/partner default to us-e/floxis, each must be a -// valid host label, and floxis itself carries no partner prefix. Which region maps to which -// datacenter is handled at DNS/LB level, not here. -func resolveBidHost(region, partner string) (string, error) { +func resolveBidHost(region, partner string) string { if region == "" { region = defaultRegion } if partner == "" { partner = defaultPartner } - if !isValidHostLabel(region) || !isValidHostLabel(partner) { - return "", &errortypes.BadInput{Message: fmt.Sprintf( - "invalid region %q or partner %q; both must be valid host labels", region, partner)} - } if partner == defaultPartner { - return region, nil + return region } - return partner + "-" + region, nil + return partner + "-" + region } -// Builder builds a new instance of the Floxis adapter for the given bidder with the given -// config. config.Endpoint is the {{.Host}} template; the host is filled per-request from the -// bidder's region/partner params (validated as host labels — never raw request-supplied hostnames). +// 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 { @@ -67,17 +47,11 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - if len(request.Imp) == 0 { - return nil, []error{&errortypes.BadInput{Message: "no impressions in the bid request"}} - } - ext, err := parseImpExt(request.Imp[0]) if err != nil { return nil, []error{err} } - // All imps in one call route under imp[0]'s seat/host. Reject a request whose imps carry - // differing floxis seats, regions, or partners rather than silently mis-routing imp[1..]. for i := 1; i < len(request.Imp); i++ { impExt, err := parseImpExt(request.Imp[i]) if err != nil { @@ -91,19 +65,12 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } } - host, err := resolveBidHost(ext.Region, ext.Partner) + endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: resolveBidHost(ext.Region, ext.Partner)}) if err != nil { return nil, []error{err} } - endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: host}) - if err != nil { - return nil, []error{err} - } - // seat is not a standard endpoint macro, so it is appended in code as a query param. uri := fmt.Sprintf("%s?seat=%s", endpoint, url.QueryEscape(ext.Seat)) - // The request body is forwarded unchanged; no caller-owned struct is mutated, so - // copy-on-write is satisfied by construction. body, err := jsonutil.Marshal(request) if err != nil { return nil, []error{err} @@ -168,9 +135,6 @@ func parseImpExt(imp openrtb2.Imp) (openrtb_ext.ExtImpFloxis, error) { return floxisExt, nil } -// getMediaTypeForBid resolves the bid's media type. When bid.mtype (OpenRTB 2.6) is set it -// is treated as authoritative. When unset, a single-format imp's media type is used; -// multi-format imps without mtype cannot be disambiguated and return an error. func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.MType != 0 { switch bid.MType { @@ -192,24 +156,7 @@ func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidT if imp.ID != bid.ImpID { continue } - 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 - } + formats, resolved := countFormats(imp) switch { case formats == 1: return resolved, nil @@ -227,3 +174,25 @@ func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidT 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 index dca3911915d..37104b14bb7 100644 --- a/adapters/floxis/floxis_test.go +++ b/adapters/floxis/floxis_test.go @@ -42,40 +42,22 @@ func TestResolveBidHost(t *testing.T) { region string partner string want string - wantErr bool }{ - {"us-e", "floxis", "us-e", false}, - {"eu", "floxis", "eu", false}, - {"apac", "floxis", "apac", false}, - {"", "", "us-e", false}, // both default - {"", "floxis", "us-e", false}, // empty region defaults to us-e - {"eu", "", "eu", false}, // empty partner defaults to floxis - {"mars", "floxis", "mars", false}, // any valid label passes through - {"us-e", "acme", "acme-us-e", false}, - {"eu", "acme", "acme-eu", false}, - {"a.b", "floxis", "", true}, // invalid region label - {"us-e", "bad_host!", "", true}, // invalid partner label - {"evil.com/x", "floxis", "", true}, + {"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 { - got, err := resolveBidHost(c.region, c.partner) - if c.wantErr { - assert.Error(t, err, "region %q partner %q", c.region, c.partner) - continue - } - assert.NoError(t, err, "region %q partner %q", c.region, c.partner) - assert.Equal(t, c.want, got, "region %q partner %q", c.region, c.partner) + assert.Equal(t, c.want, resolveBidHost(c.region, c.partner), "region %q partner %q", c.region, c.partner) } } -func TestNoImpressions(t *testing.T) { - req := &openrtb2.BidRequest{ID: "req-1"} - reqData, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) - assert.Nil(t, reqData) - assert.Len(t, errs, 1) - assert.Contains(t, errs[0].Error(), "no impressions") -} - func TestSeatIsURLEscaped(t *testing.T) { req := &openrtb2.BidRequest{ ID: "req-1", @@ -99,17 +81,6 @@ func TestPartnerPrefixesHost(t *testing.T) { assert.Equal(t, "https://acme-us-e.floxis.tech/pbs?seat=abc", reqData[0].Uri) } -func TestInvalidPartnerRejected(t *testing.T) { - req := &openrtb2.BidRequest{ - ID: "req-1", - Imp: []openrtb2.Imp{bannerImp(`{"bidder":{"seat":"abc","partner":"bad_host!"}}`)}, - Site: &openrtb2.Site{ID: "271"}, - } - _, errs := newAdapter().MakeRequests(req, &adapters.ExtraRequestInfo{}) - assert.Len(t, errs, 1) - assert.Contains(t, errs[0].Error(), "valid host labels") -} - func TestValidNonStandardRegionPassesThrough(t *testing.T) { req := &openrtb2.BidRequest{ ID: "req-1", @@ -143,8 +114,6 @@ func TestInvalidImpExt(t *testing.T) { assert.Contains(t, errs[0].Error(), "imp.ext") } -// TestCallerRequestNotMutated asserts the adapter forwards the request body unchanged and -// does not mutate any caller-owned field (copy-on-write is satisfied by construction). func TestCallerRequestNotMutated(t *testing.T) { imp := openrtb2.Imp{ ID: "imp-1", diff --git a/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json b/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json deleted file mode 100644 index f757582805f..00000000000 --- a/adapters/floxis/floxistest/supplemental/invalid-partner-errors.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "invalid region .* or partner .*; both must be valid host labels", - "comparison": "regex" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { "format": [{ "w": 300, "h": 250 }] }, - "ext": { "bidder": { "seat": "pub-seat-1", "partner": "bad_host!" } } - } - ], - "site": { "id": "271", "domain": "example.com" } - }, - "httpCalls": [] -} From efa18819b04bda0d158a3beeeaf93cc46acc7144 Mon Sep 17 00:00:00 2001 From: floxis-admin Date: Thu, 2 Jul 2026 15:12:03 +0300 Subject: [PATCH 8/8] Floxis adapter: compare resolved hosts in multi-imp consistency check --- adapters/floxis/floxis.go | 12 +-- ...default-and-explicit-region-same-host.json | 73 +++++++++++++++++++ .../multi-imp-different-partner-errors.json | 2 +- .../multi-imp-different-seat-errors.json | 2 +- 4 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 adapters/floxis/floxistest/supplemental/multi-imp-default-and-explicit-region-same-host.json diff --git a/adapters/floxis/floxis.go b/adapters/floxis/floxis.go index 00f2b46504a..011ca4601f1 100644 --- a/adapters/floxis/floxis.go +++ b/adapters/floxis/floxis.go @@ -52,20 +52,22 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte 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} } - if impExt.Seat != ext.Seat || impExt.Region != ext.Region || impExt.Partner != ext.Partner { + impHost := resolveBidHost(impExt.Region, impExt.Partner) + if impExt.Seat != ext.Seat || impHost != resolvedHost { return nil, []error{&errortypes.BadInput{Message: fmt.Sprintf( - "imp %s seat/region/partner (%q/%q/%q) differs from imp %s (%q/%q/%q); split into separate requests", - request.Imp[i].ID, impExt.Seat, impExt.Region, impExt.Partner, - request.Imp[0].ID, ext.Seat, ext.Region, ext.Partner)}} + "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: resolveBidHost(ext.Region, ext.Partner)}) + endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: resolvedHost}) if err != nil { return nil, []error{err} } 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 index e79097bc24e..64a269c972a 100644 --- a/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-partner-errors.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "imp imp-2 seat/region/partner .* differs from imp imp-1 .*", + "value": "imp imp-2 seat/host .* differs from imp imp-1 .*", "comparison": "regex" } ], diff --git a/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json index 41d12761097..d91f949f491 100644 --- a/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json +++ b/adapters/floxis/floxistest/supplemental/multi-imp-different-seat-errors.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "imp imp-2 seat/region/partner .* differs from imp imp-1 .*", + "value": "imp imp-2 seat/host .* differs from imp imp-1 .*", "comparison": "regex" } ],