Skip to content

Commit

Permalink
Added adpod_id to request extension (#1444)
Browse files Browse the repository at this point in the history
* Added adpod_id to request -> ext -> appnexus and modified requests splitting based on pod

* Unit test fix

* Unit test fix

* Minor unit test fixes

* Code refactoring

* Minor code and unit tests refactoring

* Unit tests refactoring

Co-authored-by: Veronika Solovei <veronika.solovei@xandr.com>
  • Loading branch information
VeronikaSolovei9 and Veronika Solovei authored Aug 24, 2020
1 parent 80d557e commit d663380
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 140 deletions.
62 changes: 54 additions & 8 deletions adapters/appnexus/appnexus.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -95,10 +96,11 @@ type appnexusBidExt struct {
}

type appnexusReqExtAppnexus struct {
IncludeBrandCategory *bool `json:"include_brand_category,omitempty"`
BrandCategoryUniqueness *bool `json:"brand_category_uniqueness,omitempty"`
IsAMP int `json:"is_amp,omitempty"`
HeaderBiddingSource int `json:"hb_source,omitempty"`
IncludeBrandCategory *bool `json:"include_brand_category,omitempty"`
BrandCategoryUniqueness *bool `json:"brand_category_uniqueness,omitempty"`
IsAMP int `json:"is_amp,omitempty"`
HeaderBiddingSource int `json:"hb_source,omitempty"`
AdPodId string `json:"adpod_id,omitempty"`
}

// Full request extension including appnexus extension object
Expand Down Expand Up @@ -354,14 +356,56 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada
}
reqExt.Appnexus.IsAMP = isAMP
reqExt.Appnexus.HeaderBiddingSource = a.hbSource + isVIDEO

imps := request.Imp

// For long form requests adpod_id must be sent downstream.
// Adpod id is a unique identifier for pod
// All impressions in the same pod must have the same pod id in request extension
// For this all impressions in request should belong to the same pod
// If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same
if isVIDEO == 1 {
podImps := groupByPods(imps)

requests := make([]*adapters.RequestData, 0, len(podImps))
for _, podImps := range podImps {
reqExt.Appnexus.AdPodId = generatePodId()

reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs)
requests = append(requests, reqs...)
errs = append(errs, errors...)
}
return requests, errs
}

return splitRequests(imps, request, reqExt, thisURI, errs)
}

func generatePodId() string {
val := rand.Int63()
return fmt.Sprint(val)
}

func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) {
// find number of pods in response
podImps := make(map[string][]openrtb.Imp)
for _, imp := range imps {
pod := strings.Split(imp.ID, "_")[0]
podImps[pod] = append(podImps[pod], imp)
}
return podImps
}

func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appnexusReqExt, errs []error) {
var err error
request.Ext, err = json.Marshal(reqExt)
request.Ext, err = json.Marshal(requestExtension)
if err != nil {
errs = append(errs, err)
return nil, errs
}
}

func splitRequests(imps []openrtb.Imp, request *openrtb.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) {

imps := request.Imp
// Initial capacity for future array of requests, memory optimization.
// Let's say there are 35 impressions and limit impressions per request equals to 10.
// In this case we need to create 4 requests with 10, 10, 10 and 5 impressions.
Expand All @@ -375,6 +419,8 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")

marshalAndSetRequestExt(request, requestExtension, errs)

for impsLeft {

endInd := startInd + maxImpsPerReq
Expand All @@ -393,7 +439,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada

resArr = append(resArr, &adapters.RequestData{
Method: "POST",
Uri: thisURI,
Uri: uri,
Body: reqJSON,
Headers: headers,
})
Expand Down
229 changes: 229 additions & 0 deletions adapters/appnexus/appnexus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"time"

Expand Down Expand Up @@ -38,6 +40,233 @@ func TestMemberQueryParam(t *testing.T) {
}
}

func TestVideoSinglePod(t *testing.T) {
var a AppNexusAdapter
a.URI = "http://test.com/openrtb2"
a.hbSource = 5

var reqInfo adapters.ExtraRequestInfo
reqInfo.PbsEntryPoint = "video"

var req openrtb.BidRequest
req.ID = "test_id"

reqExt := `{"prebid":{}}`
impExt := `{"bidder":{"placementId":123}}`
req.Ext = []byte(reqExt)

req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)})

result, err := a.MakeRequests(&req, &reqInfo)

assert.Empty(t, err, "Errors array should be empty")
assert.Len(t, result, 1, "Only one request should be returned")

var error error
var reqData *openrtb.BidRequest
error = json.Unmarshal(result[0].Body, &reqData)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt *appnexusReqExt
error = json.Unmarshal(reqData.Ext, &reqDataExt)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

regMatch, matchErr := regexp.Match(`[0-9]19`, []byte(reqDataExt.Appnexus.AdPodId))
assert.NoError(t, matchErr, "Regex match error should be nil")
assert.True(t, regMatch, "AdPod id doesn't present in Appnexus extension or has incorrect format")
}

func TestVideoSinglePodManyImps(t *testing.T) {
var a AppNexusAdapter
a.URI = "http://test.com/openrtb2"
a.hbSource = 5

var reqInfo adapters.ExtraRequestInfo
reqInfo.PbsEntryPoint = "video"

var req openrtb.BidRequest
req.ID = "test_id"

reqExt := `{"prebid":{}}`
impExt := `{"bidder":{"placementId":123}}`
req.Ext = []byte(reqExt)

req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_3", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_4", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_5", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_6", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_7", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_8", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_9", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_10", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_11", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_12", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_13", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_14", Ext: []byte(impExt)})

res, err := a.MakeRequests(&req, &reqInfo)

assert.Empty(t, err, "Errors array should be empty")
assert.Len(t, res, 2, "Two requests should be returned")

var error error
var reqData1 *openrtb.BidRequest
error = json.Unmarshal(res[0].Body, &reqData1)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt1 *appnexusReqExt
error = json.Unmarshal(reqData1.Ext, &reqDataExt1)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

adPodId1 := reqDataExt1.Appnexus.AdPodId

var reqData2 *openrtb.BidRequest
error = json.Unmarshal(res[1].Body, &reqData2)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt2 *appnexusReqExt
error = json.Unmarshal(reqData2.Ext, &reqDataExt2)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

adPodId2 := reqDataExt2.Appnexus.AdPodId

assert.Equal(t, adPodId1, adPodId2, "AdPod id is not the same for the same pod")
}

func TestVideoTwoPods(t *testing.T) {
var a AppNexusAdapter
a.URI = "http://test.com/openrtb2"
a.hbSource = 5

var reqInfo adapters.ExtraRequestInfo
reqInfo.PbsEntryPoint = "video"

var req openrtb.BidRequest
req.ID = "test_id"

reqExt := `{"prebid":{}}`
impExt := `{"bidder":{"placementId":123}}`
req.Ext = []byte(reqExt)

req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)})

req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)})

res, err := a.MakeRequests(&req, &reqInfo)

assert.Empty(t, err, "Errors array should be empty")
assert.Len(t, res, 2, "Two request should be returned")

var error error
var reqData1 *openrtb.BidRequest
error = json.Unmarshal(res[0].Body, &reqData1)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt1 *appnexusReqExt
error = json.Unmarshal(reqData1.Ext, &reqDataExt1)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

adPodId1 := reqDataExt1.Appnexus.AdPodId

var reqData2 *openrtb.BidRequest
error = json.Unmarshal(res[1].Body, &reqData2)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt2 *appnexusReqExt
error = json.Unmarshal(reqData2.Ext, &reqDataExt2)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

adPodId2 := reqDataExt2.Appnexus.AdPodId

assert.NotEqual(t, adPodId1, adPodId2, "AdPod id should be different for different pods")
}

func TestVideoTwoPodsManyImps(t *testing.T) {
var a AppNexusAdapter
a.URI = "http://test.com/openrtb2"
a.hbSource = 5

var reqInfo adapters.ExtraRequestInfo
reqInfo.PbsEntryPoint = "video"

var req openrtb.BidRequest
req.ID = "test_id"

reqExt := `{"prebid":{}}`
impExt := `{"bidder":{"placementId":123}}`
req.Ext = []byte(reqExt)

req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)})

req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_3", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_4", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_5", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_6", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_7", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_8", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_9", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_10", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_11", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_12", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_13", Ext: []byte(impExt)})
req.Imp = append(req.Imp, openrtb.Imp{ID: "2_14", Ext: []byte(impExt)})

res, err := a.MakeRequests(&req, &reqInfo)

assert.Empty(t, err, "Errors array should be empty")
assert.Len(t, res, 3, "Three requests should be returned")

var error error
var reqData1 *openrtb.BidRequest
error = json.Unmarshal(res[0].Body, &reqData1)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt1 *appnexusReqExt
error = json.Unmarshal(reqData1.Ext, &reqDataExt1)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

var reqData2 *openrtb.BidRequest
error = json.Unmarshal(res[1].Body, &reqData2)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt2 *appnexusReqExt
error = json.Unmarshal(reqData2.Ext, &reqDataExt2)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

var reqData3 *openrtb.BidRequest
error = json.Unmarshal(res[2].Body, &reqData3)
assert.NoError(t, error, "Response body unmarshalling error should be nil")

var reqDataExt3 *appnexusReqExt
error = json.Unmarshal(reqData3.Ext, &reqDataExt3)
assert.NoError(t, error, "Response ext unmarshalling error should be nil")

adPodId1 := reqDataExt1.Appnexus.AdPodId
adPodId2 := reqDataExt2.Appnexus.AdPodId
adPodId3 := reqDataExt3.Appnexus.AdPodId

podIds := make(map[string]int)
podIds[adPodId1] = podIds[adPodId1] + 1
podIds[adPodId2] = podIds[adPodId2] + 1
podIds[adPodId3] = podIds[adPodId3] + 1

assert.Len(t, podIds, 2, "Incorrect number of unique pod ids")
}

// ----------------------------------------------------------------------------
// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we
// clean up the existing code and make everything openrtb.
Expand Down
Loading

0 comments on commit d663380

Please sign in to comment.