diff --git a/adapters/epom_as/epom_as.go b/adapters/epom_as/epom_as.go
new file mode 100644
index 00000000000..fb90f0429b0
--- /dev/null
+++ b/adapters/epom_as/epom_as.go
@@ -0,0 +1,325 @@
+package epom_as
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+ "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"
+ "github.com/prebid/prebid-server/v4/util/urlutil"
+)
+
+// adapter talks to the Epom Ad Server, the sell side of the Epom platform.
+// It is a different product from the `epom` adapter, which is the Epom DSP:
+// the DSP buys impressions, this one sells a publisher's own inventory.
+//
+// Epom is white-label, so every network serves from its own domain and the
+// host arrives per impression in imp.ext.bidder.host rather than from config.
+type adapter struct {
+ endpointTemplate *template.Template
+}
+
+func Builder(bidderName openrtb_ext.BidderName, cfg config.Adapter, server config.Server) (adapters.Bidder, error) {
+ endpointTemplate, err := template.New("endpointTemplate").Parse(cfg.Endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+ }
+ return &adapter{endpointTemplate: endpointTemplate}, nil
+}
+
+// MakeRequests emits one request per host, carrying every impression addressed
+// to that host.
+//
+// Keeping a host's impressions together is a requirement, not an optimisation:
+// the ad server decides a page as a unit, so its roadblock and
+// one-campaign-per-page rules only hold when every slot is resolved in the same
+// auction. Splitting per impression would make those rules race each other.
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var errs []error
+
+ // Preserve the order hosts were first seen so the emitted requests are
+ // deterministic — Go map iteration is not.
+ hostOrder := make([]string, 0, len(request.Imp))
+ impsByHost := make(map[string][]openrtb2.Imp, len(request.Imp))
+
+ for _, imp := range request.Imp {
+ impExt, err := parseImpExt(&imp)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ // The placement travels as imp.tagid so the wire format is identical to
+ // the one the Prebid.js adapter sends, and the ad server has a single
+ // place to read it from.
+ imp.TagID = impExt.PlacementKey
+
+ applyBidFloor(&imp, impExt)
+
+ if err := enrichImpExt(&imp, impExt); err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ if _, seen := impsByHost[impExt.Host]; !seen {
+ hostOrder = append(hostOrder, impExt.Host)
+ }
+ impsByHost[impExt.Host] = append(impsByHost[impExt.Host], imp)
+ }
+
+ if len(impsByHost) == 0 {
+ return nil, errs
+ }
+
+ headers := http.Header{
+ "Content-Type": {"application/json"},
+ "Accept": {"application/json"},
+ }
+ // The ad server resolves geo, IP targeting and its consent country fallback from the address it
+ // sees, and a server-to-server call's socket peer is this host, never the reader. Forwarding the
+ // address the request already carries is what makes those decisions about the right person.
+ if request.Device != nil {
+ addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IPv6)
+ addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP)
+ }
+
+ requests := make([]*adapters.RequestData, 0, len(impsByHost))
+ for _, host := range hostOrder {
+ imps := impsByHost[host]
+
+ url, err := macros.ResolveMacros(a.endpointTemplate, macros.EndpointTemplateParams{Host: host})
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ hostRequest := *request
+ hostRequest.Imp = imps
+
+ body, err := jsonutil.Marshal(hostRequest)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ requests = append(requests, &adapters.RequestData{
+ Method: http.MethodPost,
+ Uri: url,
+ Body: body,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(imps),
+ })
+ }
+
+ return requests, errs
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if adapters.IsResponseStatusCodeNoContent(response) {
+ return nil, nil
+ }
+ if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
+ return nil, []error{err}
+ }
+
+ var bidResponse openrtb2.BidResponse
+ if err := jsonutil.Unmarshal(response.Body, &bidResponse); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{Message: err.Error()}}
+ }
+
+ impsByID := make(map[string]*openrtb2.Imp, len(request.Imp))
+ for i := range request.Imp {
+ impsByID[request.Imp[i].ID] = &request.Imp[i]
+ }
+
+ result := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ if bidResponse.Cur != "" {
+ result.Currency = bidResponse.Cur
+ }
+
+ var errs []error
+ for _, seatBid := range bidResponse.SeatBid {
+ for i := range seatBid.Bid {
+ bidType, err := getMediaTypeForBid(seatBid.Bid[i], impsByID)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ result.Bids = append(result.Bids, &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+
+ return result, errs
+}
+
+func addHeaderIfNonEmpty(headers http.Header, name, value string) {
+ if value != "" {
+ headers.Add(name, value)
+ }
+}
+
+// applyBidFloor fills the floor from the bidder params only when the request
+// carries none of its own, so a floor already resolved by the Price Floors
+// module — or set by the publisher on the impression — always wins.
+func applyBidFloor(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpEpomAs) {
+ if imp.BidFloor != 0 || impExt.BidFloor <= 0 {
+ return
+ }
+ imp.BidFloor = impExt.BidFloor
+ if impExt.BidFloorCur != "" {
+ imp.BidFloorCur = impExt.BidFloorCur
+ } else {
+ imp.BidFloorCur = "USD"
+ }
+}
+
+// enrichImpExt moves the Epom-specific params out of imp.ext.bidder and into the
+// shape the ad server reads: channel under our own namespace, custom parameters
+// merged into imp.ext.data, the standard first-party-data home, so that data
+// contributed by RTD modules lands in the same object.
+func enrichImpExt(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpEpomAs) error {
+ if impExt.Channel == "" && len(impExt.CustomParams) == 0 {
+ return nil
+ }
+
+ ext := map[string]json.RawMessage{}
+ if len(imp.Ext) > 0 {
+ if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil {
+ return &errortypes.BadInput{Message: fmt.Sprintf("imp %s: malformed ext: %s", imp.ID, err.Error())}
+ }
+ }
+
+ if impExt.Channel != "" {
+ namespace, err := jsonutil.Marshal(map[string]string{"channel": impExt.Channel})
+ if err != nil {
+ return err
+ }
+ ext[string(openrtb_ext.BidderEpomAs)] = namespace
+ }
+
+ if merged := mergeCustomParams(ext["data"], impExt.CustomParams); merged != nil {
+ ext["data"] = merged
+ }
+
+ encoded, err := jsonutil.Marshal(ext)
+ if err != nil {
+ return err
+ }
+ imp.Ext = encoded
+ return nil
+}
+
+// mergeCustomParams folds the custom params into any existing imp.ext.data.
+// Existing keys win — data already on the imp came from the publisher's own
+// first-party configuration. Values are stringified because the ad server reads
+// custom targeting as text; the schema already restricts them to scalars, so a
+// value this cannot stringify only reaches here through a host that skipped
+// param validation, and is skipped rather than written as a Go rendering of a
+// map. Nothing is dropped for size, which is what keeps the marshalled imp.ext
+// independent of Go's randomised map iteration order.
+func mergeCustomParams(existing json.RawMessage, params map[string]interface{}) json.RawMessage {
+ out := map[string]interface{}{}
+ for key, value := range params {
+ if asString, ok := scalarToString(value); ok {
+ out[key] = asString
+ }
+ }
+ if len(out) == 0 {
+ return nil
+ }
+
+ if len(existing) > 0 {
+ current := map[string]interface{}{}
+ if err := jsonutil.Unmarshal(existing, ¤t); err == nil {
+ for key, value := range current {
+ out[key] = value
+ }
+ }
+ }
+
+ encoded, err := jsonutil.Marshal(out)
+ if err != nil {
+ return nil
+ }
+ return encoded
+}
+
+func scalarToString(value interface{}) (string, bool) {
+ switch v := value.(type) {
+ case string:
+ return v, true
+ case bool:
+ return strconv.FormatBool(v), true
+ case float64:
+ return strconv.FormatFloat(v, 'f', -1, 64), true
+ case json.Number:
+ return v.String(), true
+ default:
+ return "", false
+ }
+}
+
+func parseImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpEpomAs, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("imp %s: missing bidder ext: %s", imp.ID, err.Error()),
+ }
+ }
+
+ var impExt openrtb_ext.ExtImpEpomAs
+ if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("imp %s: cannot resolve host or placementKey: %s", imp.ID, err.Error()),
+ }
+ }
+
+ // The host is the only publisher-controlled part of the outbound URL, so it
+ // must be a bare hostname; anything carrying a path, query or userinfo could
+ // redirect the bid request to an unintended destination.
+ if !urlutil.IsSafeHost(impExt.Host) {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("imp %s: invalid host", imp.ID),
+ }
+ }
+ if impExt.PlacementKey == "" {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("imp %s: missing placementKey", imp.ID),
+ }
+ }
+
+ return &impExt, nil
+}
+
+// getMediaTypeForBid resolves the bid's media type from mtype, falling back to
+// the impression the bid answers when the ad server omits it. Nothing is
+// assumed: a bid that matches no banner impression is a defect on the wire, and
+// rendering it as a banner would hide that.
+func getMediaTypeForBid(bid openrtb2.Bid, impsByID map[string]*openrtb2.Imp) (openrtb_ext.BidType, error) {
+ switch bid.MType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case 0:
+ if imp, ok := impsByID[bid.ImpID]; ok && imp.Banner != nil {
+ return openrtb_ext.BidTypeBanner, nil
+ }
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unresolved mtype for bid %s: no banner imp %s", bid.ID, bid.ImpID),
+ }
+ default:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unsupported mtype %d for bid %s", bid.MType, bid.ID),
+ }
+ }
+}
diff --git a/adapters/epom_as/epom_as_test.go b/adapters/epom_as/epom_as_test.go
new file mode 100644
index 00000000000..a85c46be5c4
--- /dev/null
+++ b/adapters/epom_as/epom_as_test.go
@@ -0,0 +1,273 @@
+package epom_as
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "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/errortypes"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const testEndpoint = "https://{{.Host}}/hb/bid"
+
+// End-to-end MakeRequests/MakeBids behaviour is exercised by the JSON fixtures
+// under epom_astest/exemplary and epom_astest/supplemental. The Go tests below
+// cover only what fixtures cannot: the error-TYPE contract (BadInput vs
+// BadServerResponse, which fixtures compare by message only), the promise that
+// the caller's request is never mutated, and the determinism of the custom-param
+// merge, which a single fixture run cannot distinguish from luck.
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(
+ openrtb_ext.BidderEpomAs,
+ config.Adapter{Endpoint: testEndpoint},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 849, DataCenter: "2"},
+ )
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "epom_astest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(
+ openrtb_ext.BidderEpomAs,
+ config.Adapter{Endpoint: "{{Malformed}}"},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 849, DataCenter: "2"},
+ )
+
+ assert.Error(t, buildErr)
+}
+
+func TestMakeRequestsErrorsAreBadInput(t *testing.T) {
+ testCases := []struct {
+ name string
+ impExt json.RawMessage
+ message string
+ }{
+ {
+ name: "imp.ext is not an object",
+ impExt: json.RawMessage(`"not-an-object"`),
+ message: "imp test-imp-id: missing bidder ext",
+ },
+ {
+ name: "imp.ext.bidder is not an object",
+ impExt: json.RawMessage(`{"bidder":"not-an-object"}`),
+ message: "imp test-imp-id: cannot resolve host or placementKey",
+ },
+ {
+ name: "host would rewrite the outbound url",
+ impExt: json.RawMessage(`{"bidder":{"host":"ads.example.com/collect","placementKey":"a4f21c9e7b"}}`),
+ message: "imp test-imp-id: invalid host",
+ },
+ {
+ name: "host is absent",
+ impExt: json.RawMessage(`{"bidder":{"placementKey":"a4f21c9e7b"}}`),
+ message: "imp test-imp-id: invalid host",
+ },
+ {
+ name: "placementKey is absent",
+ impExt: json.RawMessage(`{"bidder":{"host":"ads.example.com"}}`),
+ message: "imp test-imp-id: missing placementKey",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ requests, errs := newAdapter(t).MakeRequests(givenRequest(givenImp("test-imp-id", tc.impExt)), &adapters.ExtraRequestInfo{})
+
+ assert.Empty(t, requests)
+ require.Len(t, errs, 1)
+ assert.IsType(t, &errortypes.BadInput{}, errs[0])
+ assert.Contains(t, errs[0].Error(), tc.message)
+ })
+ }
+}
+
+func TestMakeBidsErrorTypes(t *testing.T) {
+ request := givenRequest(givenImp("test-imp-id", json.RawMessage(`{"bidder":{"host":"ads.example.com","placementKey":"a4f21c9e7b"}}`)))
+
+ testCases := []struct {
+ name string
+ status int
+ body string
+ wantType error
+ }{
+ {
+ name: "4xx is the publisher's problem",
+ status: http.StatusBadRequest,
+ body: `{}`,
+ wantType: &errortypes.BadInput{},
+ },
+ {
+ name: "5xx is the exchange's problem",
+ status: http.StatusInternalServerError,
+ body: `{}`,
+ wantType: &errortypes.BadServerResponse{},
+ },
+ {
+ name: "unparseable body",
+ status: http.StatusOK,
+ body: `not json at all`,
+ wantType: &errortypes.BadServerResponse{},
+ },
+ {
+ name: "unsupported mtype",
+ status: http.StatusOK,
+ body: `{"id":"test-request-id","cur":"USD","seatbid":[{"bid":[{"id":"b1","impid":"test-imp-id","price":1,"mtype":2}]}]}`,
+ wantType: &errortypes.BadServerResponse{},
+ },
+ {
+ name: "no mtype and no matching imp",
+ status: http.StatusOK,
+ body: `{"id":"test-request-id","cur":"USD","seatbid":[{"bid":[{"id":"b1","impid":"no-such-imp","price":1}]}]}`,
+ wantType: &errortypes.BadServerResponse{},
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ response := &adapters.ResponseData{StatusCode: tc.status, Body: []byte(tc.body)}
+
+ _, errs := newAdapter(t).MakeBids(request, &adapters.RequestData{}, response)
+
+ require.Len(t, errs, 1)
+ assert.IsType(t, tc.wantType, errs[0])
+ })
+ }
+}
+
+// TestCallerRequestNotMutated pins the copy-on-write surface: MakeRequests
+// rewrites tagid, the floor and imp.ext, and the exchange reuses the same
+// request object for every bidder in the auction.
+func TestCallerRequestNotMutated(t *testing.T) {
+ impExt := json.RawMessage(`{"bidder":{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":"sports-uk","customParams":{"section":"sport"},"bidFloor":2.5,"bidFloorCur":"EUR"}}`)
+ request := givenRequest(givenImp("test-imp-id", impExt))
+
+ requests, errs := newAdapter(t).MakeRequests(request, &adapters.ExtraRequestInfo{})
+ assert.Empty(t, errs)
+ require.Len(t, requests, 1)
+
+ assert.Empty(t, request.Imp[0].TagID, "caller's imp[0].TagID must not be mutated")
+ assert.EqualValues(t, 0, request.Imp[0].BidFloor, "caller's imp[0].BidFloor must not be mutated")
+ assert.Empty(t, request.Imp[0].BidFloorCur, "caller's imp[0].BidFloorCur must not be mutated")
+ assert.JSONEq(t, string(impExt), string(request.Imp[0].Ext), "caller's imp[0].Ext must not be mutated")
+ assert.Len(t, request.Imp, 1, "caller's imp slice must not be re-sliced")
+}
+
+func TestGetMediaTypeForBid(t *testing.T) {
+ bannerImp := &openrtb2.Imp{ID: "banner-imp", Banner: &openrtb2.Banner{}}
+ videoImp := &openrtb2.Imp{ID: "video-imp", Video: &openrtb2.Video{}}
+ imps := map[string]*openrtb2.Imp{bannerImp.ID: bannerImp, videoImp.ID: videoImp}
+
+ testCases := []struct {
+ name string
+ bid openrtb2.Bid
+ wantType openrtb_ext.BidType
+ wantErr string
+ }{
+ {
+ name: "mtype banner",
+ bid: openrtb2.Bid{ID: "b1", ImpID: "banner-imp", MType: openrtb2.MarkupBanner},
+ wantType: openrtb_ext.BidTypeBanner,
+ },
+ {
+ name: "no mtype resolves from a banner imp",
+ bid: openrtb2.Bid{ID: "b2", ImpID: "banner-imp"},
+ wantType: openrtb_ext.BidTypeBanner,
+ },
+ {
+ name: "no mtype and the imp is not a banner",
+ bid: openrtb2.Bid{ID: "b3", ImpID: "video-imp"},
+ wantErr: "unresolved mtype for bid b3: no banner imp video-imp",
+ },
+ {
+ name: "no mtype and no matching imp",
+ bid: openrtb2.Bid{ID: "b4", ImpID: "no-such-imp"},
+ wantErr: "unresolved mtype for bid b4: no banner imp no-such-imp",
+ },
+ {
+ name: "video mtype on a banner-only adapter",
+ bid: openrtb2.Bid{ID: "b5", ImpID: "banner-imp", MType: openrtb2.MarkupVideo},
+ wantErr: "unsupported mtype 2 for bid b5",
+ },
+ {
+ name: "native mtype on a banner-only adapter",
+ bid: openrtb2.Bid{ID: "b6", ImpID: "banner-imp", MType: openrtb2.MarkupNative},
+ wantErr: "unsupported mtype 4 for bid b6",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ bidType, err := getMediaTypeForBid(tc.bid, imps)
+
+ if tc.wantErr != "" {
+ require.Error(t, err)
+ assert.IsType(t, &errortypes.BadServerResponse{}, err)
+ assert.Equal(t, tc.wantErr, err.Error())
+ return
+ }
+ assert.NoError(t, err)
+ assert.Equal(t, tc.wantType, bidType)
+ })
+ }
+}
+
+// TestCustomParamsMergeIsDeterministic guards the reason the adapter caps
+// nothing: with no entry dropped, Go's randomised map iteration order cannot
+// change which keys reach the wire, so the marshalled body is stable across
+// runs. A single fixture pass would not tell a stable result from a lucky one.
+func TestCustomParamsMergeIsDeterministic(t *testing.T) {
+ params := map[string]interface{}{}
+ for i := 0; i < 40; i++ {
+ params[fmt.Sprintf("key%02d", i)] = fmt.Sprintf("value-%02d", i)
+ }
+
+ first := mergeCustomParams(nil, params)
+ require.NotNil(t, first)
+
+ for i := 0; i < 50; i++ {
+ assert.Equal(t, string(first), string(mergeCustomParams(nil, params)))
+ }
+
+ var decoded map[string]string
+ require.NoError(t, json.Unmarshal(first, &decoded))
+ assert.Len(t, decoded, 40, "no custom param may be dropped")
+}
+
+func newAdapter(t *testing.T) adapters.Bidder {
+ t.Helper()
+ bidder, err := Builder(
+ openrtb_ext.BidderEpomAs,
+ config.Adapter{Endpoint: testEndpoint},
+ config.Server{ExternalUrl: "http://hosturl.com", GvlID: 849, DataCenter: "2"},
+ )
+ require.NoError(t, err)
+ return bidder
+}
+
+func givenImp(id string, ext json.RawMessage) openrtb2.Imp {
+ return openrtb2.Imp{
+ ID: id,
+ Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
+ Ext: ext,
+ }
+}
+
+func givenRequest(imps ...openrtb2.Imp) *openrtb2.BidRequest {
+ return &openrtb2.BidRequest{
+ ID: "test-request-id",
+ Imp: imps,
+ Site: &openrtb2.Site{Page: "https://publisher.example.com/article"},
+ }
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/bid-floor-params.json b/adapters/epom_as/epom_astest/exemplary/bid-floor-params.json
new file mode 100644
index 00000000000..4a66cbdda88
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/bid-floor-params.json
@@ -0,0 +1,143 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-floor-with-currency",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "bidFloor": 1.75,
+ "bidFloorCur": "EUR"
+ }
+ }
+ },
+ {
+ "id": "imp-floor-defaults-to-usd",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "6d0e83b415",
+ "bidFloor": 0.5
+ }
+ }
+ },
+ {
+ "id": "imp-zero-floor-is-no-floor",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "ff0011aa22",
+ "bidFloor": 0,
+ "bidFloorCur": "EUR"
+ }
+ }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-floor-with-currency",
+ "tagid": "a4f21c9e7b",
+ "bidfloor": 1.75,
+ "bidfloorcur": "EUR",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "bidFloor": 1.75,
+ "bidFloorCur": "EUR"
+ }
+ }
+ },
+ {
+ "id": "imp-floor-defaults-to-usd",
+ "tagid": "6d0e83b415",
+ "bidfloor": 0.5,
+ "bidfloorcur": "USD",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "6d0e83b415",
+ "bidFloor": 0.5
+ }
+ }
+ },
+ {
+ "id": "imp-zero-floor-is-no-floor",
+ "tagid": "ff0011aa22",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "ff0011aa22",
+ "bidFloor": 0,
+ "bidFloorCur": "EUR"
+ }
+ }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "impIDs": ["imp-floor-with-currency", "imp-floor-defaults-to-usd", "imp-zero-floor-is-no-floor"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "EUR",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-floor-with-currency",
+ "price": 2.1,
+ "adm": "
creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-floor-with-currency",
+ "price": 2.1,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/channel-and-custom-params.json b/adapters/epom_as/epom_astest/exemplary/channel-and-custom-params.json
new file mode 100644
index 00000000000..cbf8e58a73a
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/channel-and-custom-params.json
@@ -0,0 +1,92 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": "sports-uk",
+ "customParams": { "section": "sport", "tier": 2, "premium": true }
+ }
+ }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": "sports-uk",
+ "customParams": { "section": "sport", "tier": 2, "premium": true }
+ },
+ "epom_as": { "channel": "sports-uk" },
+ "data": { "section": "sport", "tier": "2", "premium": "true" }
+ }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 2.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 2.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/device-address-forwarded.json b/adapters/epom_as/epom_astest/exemplary/device-address-forwarded.json
new file mode 100644
index 00000000000..80ae6c3f8d4
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/device-address-forwarded.json
@@ -0,0 +1,143 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ },
+ "tmax": 500,
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
+ "ip": "203.0.113.44",
+ "ipv6": "2001:db8::8a2e:370:7334",
+ "language": "en"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "2001:db8::8a2e:370:7334",
+ "203.0.113.44"
+ ]
+ },
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ },
+ "tmax": 500,
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
+ "ip": "203.0.113.44",
+ "ipv6": "2001:db8::8a2e:370:7334",
+ "language": "en"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 2.75,
+ "adm": "creative
",
+ "crid": "creative-99",
+ "dealid": "epom-direct",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 2.75,
+ "adm": "creative
",
+ "crid": "creative-99",
+ "dealid": "epom-direct",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/multi-host.json b/adapters/epom_as/epom_astest/exemplary/multi-host.json
new file mode 100644
index 00000000000..e9c1d585118
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/multi-host.json
@@ -0,0 +1,142 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-network-one",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": { "bidder": { "host": "ads.network-one.com", "placementKey": "a4f21c9e7b" } }
+ },
+ {
+ "id": "imp-network-two",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.network-two.com", "placementKey": "6d0e83b415" } }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.network-one.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-network-one",
+ "tagid": "a4f21c9e7b",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": { "bidder": { "host": "ads.network-one.com", "placementKey": "a4f21c9e7b" } }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "impIDs": ["imp-network-one"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-network-one",
+ "price": 1.5,
+ "adm": "a
",
+ "crid": "c1",
+ "w": 728,
+ "h": 90,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "https://ads.network-two.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-network-two",
+ "tagid": "6d0e83b415",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.network-two.com", "placementKey": "6d0e83b415" } }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "impIDs": ["imp-network-two"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-2",
+ "impid": "imp-network-two",
+ "price": 2.25,
+ "adm": "b
",
+ "crid": "c2",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-network-one",
+ "price": 1.5,
+ "adm": "a
",
+ "crid": "c1",
+ "w": 728,
+ "h": 90,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-2",
+ "impid": "imp-network-two",
+ "price": 2.25,
+ "adm": "b
",
+ "crid": "c2",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/multi-imp-one-request.json b/adapters/epom_as/epom_astest/exemplary/multi-imp-one-request.json
new file mode 100644
index 00000000000..b1f0611090b
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/multi-imp-one-request.json
@@ -0,0 +1,126 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-leaderboard",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ },
+ {
+ "id": "imp-sidebar",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "6d0e83b415" } }
+ },
+ {
+ "id": "imp-footer",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "ff0011aa22" } }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-leaderboard",
+ "tagid": "a4f21c9e7b",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ },
+ {
+ "id": "imp-sidebar",
+ "tagid": "6d0e83b415",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "6d0e83b415" } }
+ },
+ {
+ "id": "imp-footer",
+ "tagid": "ff0011aa22",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "ff0011aa22" } }
+ }
+ ],
+ "site": { "page": "https://publisher.example.com/article" }
+ },
+ "impIDs": ["imp-leaderboard", "imp-sidebar", "imp-footer"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-leaderboard",
+ "price": 1.5,
+ "adm": "a
",
+ "crid": "c1",
+ "dealid": "epom-direct",
+ "w": 728,
+ "h": 90,
+ "mtype": 1
+ },
+ {
+ "id": "bid-2",
+ "impid": "imp-footer",
+ "price": 4.0,
+ "adm": "b
",
+ "crid": "c2",
+ "dealid": "epom-direct",
+ "w": 970,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-leaderboard",
+ "price": 1.5,
+ "adm": "a
",
+ "crid": "c1",
+ "dealid": "epom-direct",
+ "w": 728,
+ "h": 90,
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-2",
+ "impid": "imp-footer",
+ "price": 4.0,
+ "adm": "b
",
+ "crid": "c2",
+ "dealid": "epom-direct",
+ "w": 970,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/simple-banner-app.json b/adapters/epom_as/epom_astest/exemplary/simple-banner-app.json
new file mode 100644
index 00000000000..dcfb71f68dd
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/simple-banner-app.json
@@ -0,0 +1,89 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 320, "h": 50 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ }
+ ],
+ "app": {
+ "bundle": "com.publisher.reader",
+ "name": "Publisher Reader",
+ "publisher": { "id": "pub-1" }
+ },
+ "device": { "ua": "test-ua", "ifa": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0" }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": { "format": [{ "w": 320, "h": 50 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ }
+ ],
+ "app": {
+ "bundle": "com.publisher.reader",
+ "name": "Publisher Reader",
+ "publisher": { "id": "pub-1" }
+ },
+ "device": { "ua": "test-ua", "ifa": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0" }
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 0.9,
+ "adm": "creative
",
+ "crid": "c1",
+ "adomain": ["advertiser.example"],
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 0.9,
+ "adm": "creative
",
+ "crid": "c1",
+ "adomain": ["advertiser.example"],
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/exemplary/simple-banner.json b/adapters/epom_as/epom_astest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..060684dbed3
--- /dev/null
+++ b/adapters/epom_as/epom_astest/exemplary/simple-banner.json
@@ -0,0 +1,105 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ { "w": 300, "h": 250 },
+ { "w": 300, "h": 600 }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ },
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ { "w": 300, "h": 250 },
+ { "w": 300, "h": 600 }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ },
+ "tmax": 500
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 2.75,
+ "adm": "creative
",
+ "crid": "creative-99",
+ "dealid": "epom-direct",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 2.75,
+ "adm": "creative
",
+ "crid": "creative-99",
+ "dealid": "epom-direct",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/custom-params-do-not-clobber-fpd.json b/adapters/epom_as/epom_astest/supplemental/custom-params-do-not-clobber-fpd.json
new file mode 100644
index 00000000000..ea42a24788a
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/custom-params-do-not-clobber-fpd.json
@@ -0,0 +1,129 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": "sports-uk",
+ "customParams": {
+ "section": "from-param",
+ "tier": 2
+ }
+ },
+ "data": {
+ "section": "from-first-party",
+ "keywords": "news"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": "sports-uk",
+ "customParams": {
+ "section": "from-param",
+ "tier": 2
+ }
+ },
+ "data": {
+ "section": "from-first-party",
+ "keywords": "news",
+ "tier": "2"
+ },
+ "epom_as": {
+ "channel": "sports-uk"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/custom-params-forty-keys.json b/adapters/epom_as/epom_astest/supplemental/custom-params-forty-keys.json
new file mode 100644
index 00000000000..0481681b4bf
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/custom-params-forty-keys.json
@@ -0,0 +1,233 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "customParams": {
+ "key01": 1,
+ "key02": true,
+ "key03": "value-03",
+ "key04": 4,
+ "key05": false,
+ "key06": "value-06",
+ "key07": 7,
+ "key08": true,
+ "key09": "value-09",
+ "key10": 10,
+ "key11": false,
+ "key12": "value-12",
+ "key13": 13,
+ "key14": true,
+ "key15": "value-15",
+ "key16": 16,
+ "key17": false,
+ "key18": "value-18",
+ "key19": 19,
+ "key20": true,
+ "key21": "value-21",
+ "key22": 22,
+ "key23": false,
+ "key24": "value-24",
+ "key25": 25,
+ "key26": true,
+ "key27": "value-27",
+ "key28": 28,
+ "key29": false,
+ "key30": "value-30",
+ "key31": 31,
+ "key32": true,
+ "key33": "value-33",
+ "key34": 34,
+ "key35": false,
+ "key36": "value-36",
+ "key37": 37,
+ "key38": true,
+ "key39": "value-39",
+ "key40": 40
+ }
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "customParams": {
+ "key01": 1,
+ "key02": true,
+ "key03": "value-03",
+ "key04": 4,
+ "key05": false,
+ "key06": "value-06",
+ "key07": 7,
+ "key08": true,
+ "key09": "value-09",
+ "key10": 10,
+ "key11": false,
+ "key12": "value-12",
+ "key13": 13,
+ "key14": true,
+ "key15": "value-15",
+ "key16": 16,
+ "key17": false,
+ "key18": "value-18",
+ "key19": 19,
+ "key20": true,
+ "key21": "value-21",
+ "key22": 22,
+ "key23": false,
+ "key24": "value-24",
+ "key25": 25,
+ "key26": true,
+ "key27": "value-27",
+ "key28": 28,
+ "key29": false,
+ "key30": "value-30",
+ "key31": 31,
+ "key32": true,
+ "key33": "value-33",
+ "key34": 34,
+ "key35": false,
+ "key36": "value-36",
+ "key37": 37,
+ "key38": true,
+ "key39": "value-39",
+ "key40": 40
+ }
+ },
+ "data": {
+ "key01": "1",
+ "key02": "true",
+ "key03": "value-03",
+ "key04": "4",
+ "key05": "false",
+ "key06": "value-06",
+ "key07": "7",
+ "key08": "true",
+ "key09": "value-09",
+ "key10": "10",
+ "key11": "false",
+ "key12": "value-12",
+ "key13": "13",
+ "key14": "true",
+ "key15": "value-15",
+ "key16": "16",
+ "key17": "false",
+ "key18": "value-18",
+ "key19": "19",
+ "key20": "true",
+ "key21": "value-21",
+ "key22": "22",
+ "key23": "false",
+ "key24": "value-24",
+ "key25": "25",
+ "key26": "true",
+ "key27": "value-27",
+ "key28": "28",
+ "key29": "false",
+ "key30": "value-30",
+ "key31": "31",
+ "key32": "true",
+ "key33": "value-33",
+ "key34": "34",
+ "key35": "false",
+ "key36": "value-36",
+ "key37": "37",
+ "key38": "true",
+ "key39": "value-39",
+ "key40": "40"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/empty-channel-ignored.json b/adapters/epom_as/epom_astest/supplemental/empty-channel-ignored.json
new file mode 100644
index 00000000000..7582b2de2b2
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/empty-channel-ignored.json
@@ -0,0 +1,109 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": ""
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "channel": ""
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/empty-seatbid.json b/adapters/epom_as/epom_astest/supplemental/empty-seatbid.json
new file mode 100644
index 00000000000..b9201e76df3
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/empty-seatbid.json
@@ -0,0 +1,77 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": []
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/invalid-host.json b/adapters/epom_as/epom_astest/supplemental/invalid-host.json
new file mode 100644
index 00000000000..317dcee8409
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/invalid-host.json
@@ -0,0 +1,15 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com/collect", "placementKey": "a4f21c9e7b" } }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ { "value": "imp test-imp-id: invalid host", "comparison": "literal" }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext-bidder.json b/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext-bidder.json
new file mode 100644
index 00000000000..c9e73372c5c
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext-bidder.json
@@ -0,0 +1,31 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": "not-an-object"
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "imp test-imp-id: cannot resolve host or placementKey:.*",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext.json b/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext.json
new file mode 100644
index 00000000000..e8a2a99c202
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/invalid-imp-ext.json
@@ -0,0 +1,29 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": "not-an-object"
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "imp test-imp-id: missing bidder ext:.*",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/missing-placement-key.json b/adapters/epom_as/epom_astest/supplemental/missing-placement-key.json
new file mode 100644
index 00000000000..ff96e066366
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/missing-placement-key.json
@@ -0,0 +1,15 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com" } }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ { "value": "imp test-imp-id: missing placementKey", "comparison": "literal" }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/multi-imp-partial-failure.json b/adapters/epom_as/epom_astest/supplemental/multi-imp-partial-failure.json
new file mode 100644
index 00000000000..a35a3d80df3
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/multi-imp-partial-failure.json
@@ -0,0 +1,166 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-good-first",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ },
+ {
+ "id": "imp-bad-host",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com/collect",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ },
+ {
+ "id": "imp-good-second",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "6d0e83b415"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "imp-good-first",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ },
+ {
+ "id": "imp-good-second",
+ "tagid": "6d0e83b415",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "6d0e83b415"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "imp-good-first",
+ "imp-good-second"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-good-first",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "imp imp-bad-host: invalid host",
+ "comparison": "literal"
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-good-first",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/no-content-response.json b/adapters/epom_as/epom_astest/supplemental/no-content-response.json
new file mode 100644
index 00000000000..0478544c675
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/no-content-response.json
@@ -0,0 +1,33 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "ext": { "bidder": { "host": "ads.example.com", "placementKey": "a4f21c9e7b" } }
+ }
+ ]
+ },
+ "impIDs": ["test-imp-id"]
+ },
+ "mockResponse": { "status": 204, "body": {} }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/no-mtype-resolved-from-imp.json b/adapters/epom_as/epom_astest/supplemental/no-mtype-resolved-from-imp.json
new file mode 100644
index 00000000000..0ba5977d76b
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/no-mtype-resolved-from-imp.json
@@ -0,0 +1,105 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-no-mtype",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-no-mtype",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/request-floor-wins-over-param.json b/adapters/epom_as/epom_astest/supplemental/request-floor-wins-over-param.json
new file mode 100644
index 00000000000..d3f4c58810b
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/request-floor-wins-over-param.json
@@ -0,0 +1,115 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "bidfloor": 1.25,
+ "bidfloorcur": "GBP",
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "bidFloor": 3.5,
+ "bidFloorCur": "EUR"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "bidfloor": 1.25,
+ "bidfloorcur": "GBP",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b",
+ "bidFloor": 3.5,
+ "bidFloorCur": "EUR"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/status-400.json b/adapters/epom_as/epom_astest/supplemental/status-400.json
new file mode 100644
index 00000000000..974aa9af2ea
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/status-400.json
@@ -0,0 +1,74 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/status-500.json b/adapters/epom_as/epom_astest/supplemental/status-500.json
new file mode 100644
index 00000000000..6eb89fec919
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/status-500.json
@@ -0,0 +1,74 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 500,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/unmatched-bid-impid.json b/adapters/epom_as/epom_astest/supplemental/unmatched-bid-impid.json
new file mode 100644
index 00000000000..9a0558b5d7d
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/unmatched-bid-impid.json
@@ -0,0 +1,98 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-orphan",
+ "impid": "no-such-imp",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unresolved mtype for bid bid-orphan: no banner imp no-such-imp",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/unparseable-response-body.json b/adapters/epom_as/epom_astest/supplemental/unparseable-response-body.json
new file mode 100644
index 00000000000..2a8227570de
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/unparseable-response-body.json
@@ -0,0 +1,74 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "not json at all"
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "expect",
+ "comparison": "regex"
+ }
+ ]
+}
diff --git a/adapters/epom_as/epom_astest/supplemental/unsupported-bid-media-type.json b/adapters/epom_as/epom_astest/supplemental/unsupported-bid-media-type.json
new file mode 100644
index 00000000000..e559c4d1bb8
--- /dev/null
+++ b/adapters/epom_as/epom_astest/supplemental/unsupported-bid-media-type.json
@@ -0,0 +1,123 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://ads.example.com/hb/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "a4f21c9e7b",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "ads.example.com",
+ "placementKey": "a4f21c9e7b"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://publisher.example.com/article"
+ }
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "epom",
+ "bid": [
+ {
+ "id": "bid-banner",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ {
+ "id": "bid-video",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-banner",
+ "impid": "test-imp-id",
+ "price": 1.5,
+ "adm": "creative
",
+ "crid": "c1",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unsupported mtype 2 for bid bid-video",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/epom_as/params_test.go b/adapters/epom_as/params_test.go
new file mode 100644
index 00000000000..1c9a3b2341b
--- /dev/null
+++ b/adapters/epom_as/params_test.go
@@ -0,0 +1,127 @@
+package epom_as
+
+import (
+ "encoding/json"
+ "strings"
+ "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.BidderEpomAs, 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.BidderEpomAs, json.RawMessage(p)); err == nil {
+ t.Errorf("Schema allowed invalid params: %s", p)
+ }
+ }
+}
+
+var validParams = []string{
+ // host — the pattern is byte-identical to util/urlutil.IsSafeHost, which the
+ // adapter gates on, so everything it accepts must validate here too.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com:8080","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com:65535","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads-eu.example.co.uk","placementKey":"a4f21c9e7b"}`,
+ // A single-label host is a real deployment shape (an internal name, or
+ // localhost in a staging rig), not a malformed one.
+ `{"host":"localhost","placementKey":"a4f21c9e7b"}`,
+ `{"host":"api-us","placementKey":"a4f21c9e7b"}`,
+
+ // placementKey — minLength 1, so a single character is the boundary.
+ `{"host":"ads.example.com","placementKey":"a"}`,
+
+ // channel — free-form, and deliberately uncapped: the ad server applies its
+ // own ingest limits rather than the adapter rejecting the impression.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":"sports-uk"}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":""}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":"` + strings.Repeat("c", 300) + `"}`,
+
+ // customParams — an object of scalars, in every scalar flavour.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{"section":"sport","tier":2,"premium":true}}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{"ratio":1.75,"empty":""}}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{}}`,
+
+ // bidFloor — minimum 0, so 0 is the boundary and means "no floor".
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":0}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":0.01}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":1.75,"bidFloorCur":"EUR"}`,
+
+ // bidFloorCur — a plain string; the schema declares no pattern, so it must
+ // not reject a currency it merely does not recognise.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloorCur":"USD"}`,
+
+ // Everything at once.
+ `{"host":"ads.example.com:8443","placementKey":"a4f21c9e7b","channel":"sports-uk","customParams":{"section":"sport"},"bidFloor":2.5,"bidFloorCur":"GBP"}`,
+}
+
+var invalidParams = []string{
+ // Non-object roots.
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `[]`,
+ `"{}"`,
+
+ // Required params.
+ `{}`,
+ `{"host":"ads.example.com"}`,
+ `{"placementKey":"a4f21c9e7b"}`,
+
+ // host — wrong type, and the empty string, which the pattern rejects
+ // because it demands at least one label character.
+ `{"host":42,"placementKey":"a4f21c9e7b"}`,
+ `{"host":"","placementKey":"a4f21c9e7b"}`,
+ // A host must not be able to rewrite the outbound URL.
+ `{"host":"https://ads.example.com","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com/collect","placementKey":"a4f21c9e7b"}`,
+ `{"host":"user@ads.example.com","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com?x=1","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com#frag","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads.example.com:80a","placementKey":"a4f21c9e7b"}`,
+ `{"host":"ads example.com","placementKey":"a4f21c9e7b"}`,
+
+ // placementKey — wrong type, and the empty string just under minLength 1.
+ `{"host":"ads.example.com","placementKey":42}`,
+ `{"host":"ads.example.com","placementKey":""}`,
+
+ // channel — wrong type.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":42}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","channel":["sports-uk"]}`,
+
+ // customParams — must be an object of scalars. A nested object or array
+ // would be stringified into targeting as a Go rendering of a map, so the
+ // schema rejects the impression instead.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":"not-an-object"}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":[]}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{"nested":{"a":1}}}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{"list":[1,2]}}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","customParams":{"nothing":null}}`,
+
+ // bidFloor — wrong type, and one step under the minimum of 0.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":-1}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":-0.01}`,
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloor":"1.75"}`,
+
+ // bidFloorCur — wrong type.
+ `{"host":"ads.example.com","placementKey":"a4f21c9e7b","bidFloorCur":978}`,
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index c8da847f761..31dbc09ff9d 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -103,6 +103,7 @@ import (
"github.com/prebid/prebid-server/v4/adapters/emtv"
"github.com/prebid/prebid-server/v4/adapters/eplanning"
"github.com/prebid/prebid-server/v4/adapters/epom"
+ epom_as "github.com/prebid/prebid-server/v4/adapters/epom_as"
"github.com/prebid/prebid-server/v4/adapters/escalax"
"github.com/prebid/prebid-server/v4/adapters/eskimi"
"github.com/prebid/prebid-server/v4/adapters/exco"
@@ -381,6 +382,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder,
openrtb_ext.BidderEPlanning: eplanning.Builder,
openrtb_ext.BidderEpom: epom.Builder,
+ openrtb_ext.BidderEpomAs: epom_as.Builder,
openrtb_ext.BidderEscalax: escalax.Builder,
openrtb_ext.BidderEskimi: eskimi.Builder,
openrtb_ext.BidderExco: exco.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 4858f69ef8f..4cfa74c2af8 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -119,6 +119,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderEmxDigital,
BidderEPlanning,
BidderEpom,
+ BidderEpomAs,
BidderEscalax,
BidderEskimi,
BidderEVolution,
@@ -504,6 +505,7 @@ const (
BidderEmxDigital BidderName = "emx_digital"
BidderEPlanning BidderName = "eplanning"
BidderEpom BidderName = "epom"
+ BidderEpomAs BidderName = "epom_as"
BidderEscalax BidderName = "escalax"
BidderEskimi BidderName = "eskimi"
BidderExco BidderName = "exco"
diff --git a/openrtb_ext/imp_epom_as.go b/openrtb_ext/imp_epom_as.go
new file mode 100644
index 00000000000..8d5a4b80c01
--- /dev/null
+++ b/openrtb_ext/imp_epom_as.go
@@ -0,0 +1,18 @@
+package openrtb_ext
+
+// ExtImpEpomAs defines the contract for bidrequest.imp[i].ext.prebid.bidder.epom_as
+type ExtImpEpomAs struct {
+ // Host is the serving host of the publisher's Epom Ad Server deployment.
+ Host string `json:"host"`
+ // PlacementKey identifies the placement within that deployment.
+ PlacementKey string `json:"placementKey"`
+ // Channel is a traffic-slice label used for targeting and reporting.
+ Channel string `json:"channel,omitempty"`
+ // CustomParams feed custom targeting and creative macros.
+ CustomParams map[string]interface{} `json:"customParams,omitempty"`
+ // BidFloor is a CPM floor applied only when the request carries no floor of
+ // its own, so a Price Floors module result always wins.
+ BidFloor float64 `json:"bidFloor,omitempty"`
+ // BidFloorCur is the currency of BidFloor, defaulting to USD.
+ BidFloorCur string `json:"bidFloorCur,omitempty"`
+}
diff --git a/static/bidder-info/epom_as.yaml b/static/bidder-info/epom_as.yaml
new file mode 100644
index 00000000000..9f6257650b6
--- /dev/null
+++ b/static/bidder-info/epom_as.yaml
@@ -0,0 +1,25 @@
+# Epom is white-label: every publisher network serves from its own domain, so the
+# host arrives per impression in imp.ext.bidder.host and is templated into the
+# endpoint rather than configured here.
+endpoint: "https://{{.Host}}/hb/bid"
+maintainer:
+ email: "support@epom.com"
+gvlVendorID: 849
+geoscope:
+ - global
+openrtb:
+ version: 2.6
+modifyingVastXmlAllowed: false
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ site:
+ mediaTypes:
+ - banner
+userSync:
+ # epom_as supports user syncing, but the sync endpoint lives on the publisher's own
+ # Epom deployment, so it requires configuration by the host. Contact this bidder
+ # directly at the email address in this file to ask about enabling user sync.
+ supports:
+ - iframe
diff --git a/static/bidder-params/epom_as.json b/static/bidder-params/epom_as.json
new file mode 100644
index 00000000000..ad129e91cfc
--- /dev/null
+++ b/static/bidder-params/epom_as.json
@@ -0,0 +1,46 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Epom Ad Server Adapter Params",
+ "description": "A schema which validates params accepted by the Epom Ad Server adapter",
+ "type": "object",
+ "properties": {
+ "host": {
+ "type": "string",
+ "description": "Serving host of the publisher's Epom Ad Server deployment, as a bare hostname with an optional port",
+ "pattern": "^[a-zA-Z0-9.-]+(:[0-9]+)?$"
+ },
+ "placementKey": {
+ "type": "string",
+ "description": "Placement identifier, from the placement's invocation-code tab in the Epom UI",
+ "minLength": 1
+ },
+ "channel": {
+ "type": "string",
+ "description": "Epom channel: publisher traffic-slice label used for channel targeting, reporting and tracking-URL propagation. Sent as imp.ext.epom_as.channel. An empty value is ignored."
+ },
+ "customParams": {
+ "type": "object",
+ "description": "Epom custom parameters for custom targeting and creative macros. Merged into imp.ext.data, where keys already on the impression win.",
+ "additionalProperties": {
+ "type": [
+ "string",
+ "number",
+ "boolean"
+ ]
+ }
+ },
+ "bidFloor": {
+ "type": "number",
+ "description": "CPM floor for this impression, applied only when no floor has already been resolved on imp.bidfloor. 0 means no floor.",
+ "minimum": 0
+ },
+ "bidFloorCur": {
+ "type": "string",
+ "description": "Currency of bidFloor as an ISO-4217 code. Defaults to USD."
+ }
+ },
+ "required": [
+ "host",
+ "placementKey"
+ ]
+}