Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapter Name Case Insensitive: Stored Bid Responses #3197

Merged
merged 10 commits into from
Oct 20, 2023
2 changes: 1 addition & 1 deletion endpoints/openrtb2/amp_auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
return
}

storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap)
storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, &openrtb_ext.RequestWrapper{BidRequest: req}, deps.storedRespFetcher)
if err != nil {
errs = []error{err}
return
Expand Down
37 changes: 20 additions & 17 deletions endpoints/openrtb2/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,6 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
return
}

//Stored auction responses should be processed after stored requests due to possible impression modification
storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap)
if len(errs) > 0 {
return nil, nil, nil, nil, nil, nil, errs
}

if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil {
errs = []error{err}
return
Expand All @@ -548,6 +542,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric

lmt.ModifyForIOS(req.BidRequest)

//Stored auction responses should be processed after stored requests due to possible impression modification
storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher)
if len(errs) > 0 {
return nil, nil, nil, nil, nil, nil, errs
}

hasStoredResponses := len(storedAuctionResponses) > 0
errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest)
if len(errL) > 0 {
Expand Down Expand Up @@ -1532,10 +1532,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma
return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)}
}

if len(storedBidResp) > 0 {
if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil {
return []error{err}
}
if err := deps.validateStoredBidRespAndImpExtBidders(prebid, storedBidResp, imp.ID); err != nil {
return []error{err}
}

errL := []error{}
Expand Down Expand Up @@ -2502,19 +2500,24 @@ func checkIfAppRequest(request []byte) (bool, error) {
return false, nil
}

func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error {
func (deps *endpointDeps) validateStoredBidRespAndImpExtBidders(prebid *openrtb_ext.ExtImpPrebid, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error {
if storedBidResp == nil && len(prebid.StoredBidResponse) == 0 {
return nil
}

if storedBidResp == nil {
return generateStoredBidResponseValidationError(impId)
}
if bidResponses, ok := storedBidResp[impId]; ok {
if len(bidResponses) != len(bidderExts) {
if len(bidResponses) != len(prebid.Bidder) {
return generateStoredBidResponseValidationError(impId)
}

for bidderName := range bidResponses {
bidder := bidderName
normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidder)
if ok {
bidder = normalizedCoreBidder.String()
if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk {
return fmt.Errorf(`unrecognized bidder "%v"`, bidderName)
}
if _, present := bidderExts[bidder]; !present {
if _, present := prebid.Bidder[bidderName]; !present {
return generateStoredBidResponseValidationError(impId)
}
}
Expand Down
43 changes: 30 additions & 13 deletions endpoints/openrtb2/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4991,8 +4991,8 @@ func TestParseRequestStoredResponses(t *testing.T) {
}

func TestParseRequestStoredBidResponses(t *testing.T) {
bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`)
bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`)
bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "telaria"}], "bidid": "123", "cur": "USD"}`)
bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "amx"}], "bidid": "124", "cur": "USD"}`)
bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`)
mockStoredBidResponses := map[string]json.RawMessage{
"bidResponseId1": bidRespId1,
Expand All @@ -5011,40 +5011,54 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
name: "req imp has valid stored bid response",
givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"),
expectedStoredBidResponses: map[string]map[string]json.RawMessage{
"imp-id1": {"testBidder1": bidRespId1},
"imp-id1": {"telaria": bidRespId1},
},
expectedErrorCount: 0,
},
{
name: "req imp has valid stored bid response with case insensitive bidder name",
givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-insensitive-bidder-name.json"),
name: "req imp has valid stored bid response with case not-matching bidder name",
givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-not-matching-bidder-name.json"),
expectedStoredBidResponses: map[string]map[string]json.RawMessage{
"imp-id3": {"APPNEXUS": bidRespId3},
"imp-id3": {"appnexus": bidRespId3},
},
expectedErrorCount: 0,
},
{
name: "req imp has valid stored bid response with case matching bidder name",
givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-matching-bidder-name.json"),
expectedStoredBidResponses: map[string]map[string]json.RawMessage{
"imp-id3": {"appnexus": bidRespId3},
},
expectedErrorCount: 0,
},
{
name: "req has two imps with valid stored bid responses",
givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"),
expectedStoredBidResponses: map[string]map[string]json.RawMessage{
"imp-id1": {"testBidder1": bidRespId1},
"imp-id2": {"testBidder2": bidRespId2},
"imp-id1": {"telaria": bidRespId1},
"imp-id2": {"amx": bidRespId2},
},
expectedErrorCount: 0,
},
{
name: "req has two imps one with valid stored bid responses and another one without stored bid responses",
givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"),
expectedStoredBidResponses: map[string]map[string]json.RawMessage{
"imp-id2": {"testBidder2": bidRespId2},
"imp-id2": {"amx": bidRespId2},
},
expectedErrorCount: 0,
},
{
name: "req has two imps with missing stored bid responses",
givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"),
expectedStoredBidResponses: nil,
expectedErrorCount: 2,
expectedErrorCount: 1,
},
{
name: "req imp has valid stored bid response with non existing bidder name",
givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-non-existing-bidder-name.json"),
expectedStoredBidResponses: nil,
expectedErrorCount: 1,
},
}
for _, test := range tests {
Expand All @@ -5063,7 +5077,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
map[string]string{},
false,
[]byte{},
map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2", "appnexus": "appnexus"},
map[string]openrtb_ext.BidderName{"telaria": "telaria", "amx": "amx", "appnexus": "appnexus"},
nil,
nil,
hardcodedResponseIPValidator{response: true},
Expand All @@ -5078,6 +5092,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
if test.expectedErrorCount == 0 {
assert.Empty(t, errL)
assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match")
} else {
assert.Contains(t, errL[0].Error(), test.expectedError, "error should match")
Expand Down Expand Up @@ -5653,8 +5668,10 @@ func TestValidateStoredResp(t *testing.T) {
}

for _, test := range testCases {
errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false)
assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
t.Run(test.description, func(t *testing.T) {
errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false)
assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
})
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"description": "request with impression with stored bid response with case matching bidder name",
"mockBidRequest": {
"id": "request-with-stored-resp",
"site": {
"page": "test.somepage.com"
},
"imp": [
{
"id": "imp-id3",
"banner": {
"format": [
{
"w": 300,
"h": 600
}
]
},
"ext": {
"appnexus": {
"placementId": 12883451
},
"prebid": {
"storedbidresponse": [
{
"bidder": "appnexus",
"id": "bidResponseId3"
}
]
}
}
}
],
"user": {
"yob": 1989
}
},
"expectedReturnCode": 200
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "request with impression with stored bid response with sensitive bidder name",
"description": "request with impression with stored bid response with case not matching bidder name",
"mockBidRequest": {
"id": "request-with-stored-resp",
"site": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"description": "request with impression with stored bid response",
"mockBidRequest": {
"id": "request-with-stored-resp",
"site": {
"page": "test.somepage.com"
},
"imp": [
{
"id": "imp-id1",
"banner": {
"format": [
{
"w": 300,
"h": 600
}
]
},
"ext": {
"bidderABC": {},
"prebid": {
"storedbidresponse": [
{"bidder":"bidderABC", "id": "bidResponseId1"}
]
}
}
}
],
"user": {
"yob": 1989
}
},
"expectedReturnCode": 200
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
]
},
"ext": {
"appnexus": {
"placementId": 12883451
},
"telaria": {},
"prebid": {
"storedbidresponse": [
{"bidder":"testBidder1", "id": "bidResponseId1"}
{"bidder":"telaria", "id": "bidResponseId1"}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
]
},
"ext": {
"appnexus": {
"telaria": {
"placementId": 12883451
},
"prebid": {
"storedbidresponse": [
{"bidder":"testBidder1", "id": "bidResponseId1"}
{"bidder":"telaria", "id": "bidResponseId1"}
]
}
}
Expand All @@ -38,12 +38,10 @@
]
},
"ext": {
"appnexus": {
"placementId": 12883451
},
"amx": {},
"prebid": {
"storedbidresponse": [
{"bidder":"testBidder2", "id": "bidResponseId2"}
{"bidder":"amx", "id": "bidResponseId2"}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
]
},
"ext": {
"appnexus": {
"amx": {
"placementId": 12883451
},
"prebid": {
"storedbidresponse": [
{"bidder":"testBidder2", "id": "bidResponseId2"}
{"bidder":"amx", "id": "bidResponseId2"}
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,7 @@ func buildBidResponseRequest(req *openrtb2.BidRequest,
BidderCoreName: resolvedBidder,
BidderName: bidderName,
BidderStoredResponses: impResps,
ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)],
ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)],
IsRequestAlias: isRequestAlias,
BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder},
}
Expand Down
23 changes: 23 additions & 0 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/prebid/prebid-server/stored_responses"
"sort"
"testing"

Expand Down Expand Up @@ -4754,3 +4755,25 @@ func TestApplyBidAdjustmentToFloor(t *testing.T) {
})
}
}

func TestBuildBidResponseRequestBidderName(t *testing.T) {
bidderImpResponses := stored_responses.BidderImpsWithBidResponses{
openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)},
openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)},
}

bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{
"appnexus": {"impId1": true, "impId2": false},
"appneXUS": {"impId3": true, "impId4": false},
}
result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID)

resultAppnexus := result["appnexus"]
assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus"))
assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false})

resultAppneXUS := result["appneXUS"]
assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS"))
assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false})

}
Loading
Loading