From 3b753adb20068302acd49728fd2577b7b06da700 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 20 Sep 2023 13:40:38 +0530 Subject: [PATCH 1/5] make bidder names as case insensitive during request comparison --- endpoints/openrtb2/auction.go | 6 +-- endpoints/openrtb2/auction_test.go | 22 +++++----- .../valid-whole/exemplary/simple.json | 42 ++++++++++--------- exchange/exchange_test.go | 6 +-- exchange/utils.go | 3 +- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index aeb5c20e64e..3056011544b 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1510,12 +1510,12 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma errL := []error{} for bidder, ext := range prebid.Bidder { - coreBidder := bidder + coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) if tmp, isAlias := aliases[bidder]; isAlias { - coreBidder = tmp + coreBidder = openrtb_ext.BidderName(tmp) } - if coreBidderNormalized, isValid := deps.bidderMap[coreBidder]; isValid { + if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid { if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 84e5cf4baf5..56d443abbc7 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -2833,19 +2833,21 @@ func TestValidateImpExt(t *testing.T) { for _, group := range testGroups { for _, test := range group.testCases { - imp := &openrtb2.Imp{Ext: test.impExt} - impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} + t.Run(test.description, func(t *testing.T) { + imp := &openrtb2.Imp{Ext: test.impExt} + impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} - errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) + errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) - assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") + assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") - if len(test.expectedImpExt) > 0 { - assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) - } else { - assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) - } - assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) + } else { + assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) + } + assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + }) } } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json index ba9079d4675..c6de6d7e0a6 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json @@ -2,7 +2,11 @@ "description": "Simple request", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } ] }, "mockBidRequest": { @@ -26,7 +30,7 @@ ] }, "ext": { - "appnexus": { + "Appnexus": { "placementId": 12883451 } } @@ -36,22 +40,22 @@ "ext": {} }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0 - } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 8ca96880781..c4b85ebeb1f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -769,7 +769,7 @@ func TestAdapterCurrency(t *testing.T) { categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""), + openrtb_ext.BidderName("appnexus"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("appnexus"), nil, ""), }, } e.requestSplitter = requestSplitter{ @@ -783,7 +783,7 @@ func TestAdapterCurrency(t *testing.T) { Imp: []openrtb2.Imp{{ ID: "some-impression-id", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"prebid":{"bidder":{"foo":{"placementId":1}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{ Page: "prebid.org", @@ -805,7 +805,7 @@ func TestAdapterCurrency(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "some-request-id", response.ID, "Response ID") assert.Empty(t, response.SeatBid, "Response Bids") - assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") + assert.Contains(t, string(response.Ext), `"errors":{"appnexus":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") // Test Currency Converter Properly Passed To Adapter if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { diff --git a/exchange/utils.go b/exchange/utils.go index 0becca21f6c..54a99131598 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -761,10 +761,11 @@ func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessag // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) if coreBidder, ok := aliases[bidder]; ok { return openrtb_ext.BidderName(coreBidder) } - return openrtb_ext.BidderName(bidder) + return normalisedBidderName } // parseAliases parses the aliases from the BidRequest From c5b6e39c11ff19502e7e1b38627ea2bacc6ae302 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 20 Sep 2023 13:42:08 +0530 Subject: [PATCH 2/5] make aliases names comparison in request as case insensitive --- endpoints/openrtb2/auction.go | 15 ++-- .../valid-whole/exemplary/all-ext.json | 88 ++++++++++--------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 3056011544b..58d38b5af75 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1575,18 +1575,21 @@ func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { - for alias, coreBidder := range aliases { - if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder) + for alias, bidderName := range aliases { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidderName) + coreBidderName := normalisedBidderName.String() + if _, isCoreBidderDisabled := deps.disabledBidders[coreBidderName]; isCoreBidderDisabled { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, bidderName) } - if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) + if _, isCoreBidder := deps.bidderMap[coreBidderName]; !isCoreBidder { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, bidderName) } - if alias == coreBidder { + if alias == coreBidderName { return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) } + aliases[alias] = coreBidderName } return nil } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 8f64fd2a5fd..8b1b750edc3 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -2,8 +2,16 @@ "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 1.00}, - {"bidderName": "rubicon", "currency": "USD", "price": 1.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } ] }, "mockBidRequest": { @@ -61,7 +69,7 @@ "ext": { "prebid": { "aliases": { - "districtm": "appnexus" + "districtm": "Appnexus" }, "bidadjustmentfactors": { "appnexus": 1.01, @@ -91,42 +99,42 @@ } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 1.01 - } - ], - "seat": "appnexus" - }, - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0.98 - } - ], - "seat": "districtm" - }, - { - "bid": [ - { - "id": "rubicon-bid", - "impid": "some-impression-id", - "price": 0.99 - } - ], - "seat": "rubicon" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file From 6e3ba92f8e2c7b93e7aab77969183a268529bc42 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Wed, 20 Sep 2023 15:03:11 +0530 Subject: [PATCH 3/5] separate test files for case insensitive comparison --- .../exemplary/all-ext-case-insensitive.json | 140 ++++++++++++++++++ .../valid-whole/exemplary/all-ext.json | 2 +- .../exemplary/simple-case-insensitive.json | 61 ++++++++ .../valid-whole/exemplary/simple.json | 4 +- 4 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json new file mode 100644 index 00000000000..20997076af2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json @@ -0,0 +1,140 @@ +{ + "description": "This demonstrates all extension in case insensitive.", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string", + "prebid": { + "buyeruids": { + "appnexus": "override-appnexus-id-in-cookie" + } + } + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "districtm": { + "placementId": 105 + }, + "rubicon": { + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "Appnexus" + }, + "bidadjustmentfactors": { + "appnexus": 1.01, + "districtm": 0.98, + "rubicon": 0.99 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 8b1b750edc3..02dc6160d49 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -69,7 +69,7 @@ "ext": { "prebid": { "aliases": { - "districtm": "Appnexus" + "districtm": "appnexus" }, "bidadjustmentfactors": { "appnexus": 1.01, diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json new file mode 100644 index 00000000000..9b66a59d0a5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json @@ -0,0 +1,61 @@ +{ + "description": "Simple request - bidder case insensitive", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json index c6de6d7e0a6..5a7dbd20747 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json @@ -30,7 +30,7 @@ ] }, "ext": { - "Appnexus": { + "appnexus": { "placementId": 12883451 } } @@ -50,7 +50,7 @@ "price": 0 } ], - "seat": "Appnexus" + "seat": "appnexus" } ], "bidid": "test bid id", From 98fd465a5d83eae85dcb34deb07df1509549eb59 Mon Sep 17 00:00:00 2001 From: Ashish Garg Date: Mon, 25 Sep 2023 11:48:08 +0530 Subject: [PATCH 4/5] add test case for validateAliases method --- endpoints/openrtb2/auction_test.go | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 56d443abbc7..cb27178abc5 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -5773,3 +5773,59 @@ func TestSetSeatNonBidRaw(t *testing.T) { }) } } + +func TestValidateAliases(t *testing.T) { + deps := &endpointDeps{ + disabledBidders: map[string]string{"rubicon": "rubicon"}, + bidderMap: map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderName("appnexus")}, + } + + testCases := []struct { + description string + aliases map[string]string + expectedAliases map[string]string + expectedError error + }{ + { + description: "valid case", + aliases: map[string]string{"test": "appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "valid case - case insensitive", + aliases: map[string]string{"test": "Appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "disabled bidder", + aliases: map[string]string{"test": "rubicon"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to disabled bidder: rubicon"), + }, + { + description: "coreBidderName not found", + aliases: map[string]string{"test": "anyBidder"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to unknown bidder: anyBidder"), + }, + { + description: "alias name is coreBidder name", + aliases: map[string]string{"appnexus": "appnexus"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.appnexus defines a no-op alias. Choose a different alias, or remove this entry."), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + err := deps.validateAliases(testCase.aliases) + if err != nil { + assert.Equal(t, testCase.expectedError, err) + } else { + assert.ObjectsAreEqualValues(testCase.expectedAliases, map[string]string{"test": "appnexus"}) + } + }) + } +} From 8cb962282a8f1b3e443671490957cae529187c0a Mon Sep 17 00:00:00 2001 From: Onkar Hanumante Date: Mon, 25 Sep 2023 17:13:21 +0530 Subject: [PATCH 5/5] Support case insensitive bidder name in ext.prebid.data.eidpermissions.bidders --- endpoints/openrtb2/auction.go | 7 +- ...idpermissions-insensitive-bidder-name.json | 109 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 58d38b5af75..413249b1a3b 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1003,7 +1003,12 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde return errors.New(`bidder wildcard "*" mixed with specific bidders`) } } else { - _, isCoreBidder := knownBidders[bidder] + bidderName := bidder + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderName) + if ok { + bidderName = normalizedCoreBidder.String() + } + _, isCoreBidder := knownBidders[bidderName] _, isAlias := knownAliases[bidder] if !isCoreBidder && !isAlias { return fmt.Errorf(`unrecognized bidder "%v"`, bidder) diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json new file mode 100644 index 00000000000..b06b2593e26 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/eidpermissions-insensitive-bidder-name.json @@ -0,0 +1,109 @@ +{ + "description": "This demonstrates ext.prebid.data.eidpermissions.bidders with case insensitive bidder names", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "APPNEXUS": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "APPNEXUS" + ] + } + ] + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1 + } + ], + "seat": "APPNEXUS" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file