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
13 changes: 6 additions & 7 deletions endpoints/openrtb2/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma
}

if len(storedBidResp) > 0 {
if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil {
if err := deps.validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil {
return []error{err}
}
}
Expand Down Expand Up @@ -2492,19 +2492,18 @@ 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(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error {
if bidResponses, ok := storedBidResp[impId]; ok {
if len(bidResponses) != len(bidderExts) {
return generateStoredBidResponseValidationError(impId)
}

for bidderName := range bidResponses {
bidder := bidderName
normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidder)
if ok {
bidder = normalizedCoreBidder.String()
_, bidderNameOk := deps.normalizeBidderName(bidderName)
if !bidderNameOk {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: bidderNameOk is only used in the if statement. Can we merge the two lines?

2501         for bidderName := range bidResponses {
2502 -           _, bidderNameOk := deps.normalizeBidderName(bidderName)
2503 -           if !bidderNameOk {
     +           if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk {
2504                 return fmt.Errorf(`unrecognized bidder "%v"`, bidderName)
endpoints/openrtb2/auction.go

return fmt.Errorf(`unrecognized bidder "%v"`, bidderName)
}
if _, present := bidderExts[bidder]; !present {
if _, present := bidderExts[bidderName]; !present {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly for my education. Why are we not using normalised bidder name to lookup on bidderExts.
Here bidderExts is prepared from core bidder names. So should use normalised bidder name

Copy link
Contributor Author

@VeronikaSolovei9 VeronikaSolovei9 Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a description with explanations how it works. We need to use bidder name as it is specified in imp, not normalized bidder name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2023-10-11 at 3 41 07 PM

return generateStoredBidResponseValidationError(impId)
}
}
Expand Down
33 changes: 23 additions & 10 deletions endpoints/openrtb2/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4990,8 +4990,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 @@ -5010,32 +5010,38 @@ 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: nil,
expectedErrorCount: 1,
},
{
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},
"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,
},
Expand All @@ -5045,6 +5051,12 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
expectedStoredBidResponses: nil,
expectedErrorCount: 2,
},
{
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 {
t.Run(test.name, func(t *testing.T) {
Expand All @@ -5062,7 +5074,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 @@ -5077,6 +5089,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
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 matching bidder name",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Typo "case not matching"

"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 @@ -930,7 +930,7 @@ func buildBidResponseRequest(req *openrtb2.BidRequest,
BidderCoreName: resolvedBidder,
BidderName: bidderName,
BidderStoredResponses: impResps,
ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)],
ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)],
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 @@ -4679,3 +4680,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})

}
11 changes: 0 additions & 11 deletions stored_responses/stored_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,6 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData,
if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 {
return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index)
}
bidderName := bidderResp.Bidder
normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderResp.Bidder)
if ok {
bidderName = normalizedCoreBidder.String()
}
//check if bidder is valid/exists
if _, isValid := bidderMap[bidderName]; !isValid {
return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder)
}
Comment on lines -106 to -109
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this logic was removed. Isn't this logic checking that the stored response bidder name is the name of a bidder defined in the imp? Do we need this? Does this error detection exist elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this function is only about parsing request. This check is now in validateStoredBidRespAndImpExtBidders:

if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk {
				return fmt.Errorf(`unrecognized bidder "%v"`, bidderName)
			}

// bidder is unique per one bid stored response
// if more than one bidder specified the last defined bidder id will take precedence
bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly for my education. Why was the code block from line 100 to 110 removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a fail-fast logic, that is not needed in extractStoredResponsesIds, we just want to extract ids.
This logic was sort of duplicated in validateStoredBidRespAndImpExtBidders. Now it's only one place with validation in validation function.

impBiddersWithBidResponseIDs[impId] = bidderStoredRespId

Expand Down
21 changes: 4 additions & 17 deletions stored_responses/stored_responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,6 @@ func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) {
]}`),
expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")},
},
{
description: "Invalid stored bid response: storedbidresponse.bidder not found",
requestJson: []byte(`{"imp": [
{
"id": "imp-id1",
"ext": {
"prebid": {
"storedbidresponse": [
{ "bidder": "testBidder123", "id": "123abc"}]
}
}
}
]}`),
expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext.prebid.bidder contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")},
},
{
description: "Invalid stored auction response format: empty stored Auction Response Id in second imp",
requestJson: []byte(`{"imp": [
Expand Down Expand Up @@ -285,8 +270,10 @@ func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) {
}

for _, test := range testCases {
_, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap)
assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
t.Run(test.description, func(t *testing.T) {
_, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap)
assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
})
}

}
Expand Down
Loading