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

New Adapter: Bidtheatre #4069

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions adapters/bidtheatre/bidtheatre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package bidtheatre

import (
"fmt"
"strconv"
"strings"

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/errortypes"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/util/jsonutil"
)

type adapter struct {
endpoint string
}

// Builder builds a new instance of the Bidtheatre adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
bidder := &adapter{
endpoint: config.Endpoint,
}

return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
requestJSON, err := jsonutil.Marshal(request)
if err != nil {
return nil, []error{err}
}

requestData := &adapters.RequestData{
Method: "POST",
Uri: a.endpoint,
Body: requestJSON,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}

return []*adapters.RequestData{requestData}, nil
}

func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
var bidExt openrtb_ext.ExtBid
if err := jsonutil.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt.Prebid != nil {
return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))

Choose a reason for hiding this comment

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

Consider this as a suggestion. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, recommends implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression.

}

return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID),
}
Comment on lines +51 to +53
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add test coverage for these lines by adding a supplemental JSON test that contains a malformed bid.ext.

Copy link
Author

Choose a reason for hiding this comment

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

Not sure if this were your polite way of saying these lines can't be reached or not.. because I seem to be unable to create a test that triggers them =)

The code comes from your references implementation but would you prefer I remove them?

}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(responseData) {
return nil, nil
}

if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
return nil, []error{err}
}

var response openrtb2.BidResponse
if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
bidResponse.Currency = response.Cur
for _, seatBid := range response.SeatBid {
for i := range seatBid.Bid {
resolveMacros(&seatBid.Bid[i])
bidType, err := getMediaTypeForBid(seatBid.Bid[i])
if err != nil {
continue
}
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
})
}
}
return bidResponse, nil
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
}

func resolveMacros(bid *openrtb2.Bid) {
if bid == nil {
return
}
price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
}
90 changes: 90 additions & 0 deletions adapters/bidtheatre/bidtheatre_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package bidtheatre
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved

import (
"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters/adapterstest"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"strings"
"testing"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderBidtheatre, config.Adapter{
Endpoint: "http://any.url"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "bidtheatretest", bidder)
}

andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
func TestGetBidTypes(t *testing.T) {
mockBid := openrtb2.Bid{
ID: "mock-bid-id",
ImpID: "mock-imp-id",
Price: 1.23,
AdID: "mock-ad-id",
CrID: "mock-cr-id",
DealID: "mock-deal-id",
W: 980,
H: 240,
Ext: []byte(`{"prebid": {"type": "banner"}}`),
BURL: "https://example.com/win-notify",
Cat: []string{"IAB1"},
}

actualBidTypeValue, _ := getMediaTypeForBid(mockBid)

if actualBidTypeValue != openrtb_ext.BidTypeBanner {
t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
}

mockBid.Ext = []byte(`{"prebid": {"type": "video"}}`)

actualBidTypeValue, _ = getMediaTypeForBid(mockBid)

if actualBidTypeValue != openrtb_ext.BidTypeVideo {
t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeVideo, actualBidTypeValue)
}

}

func TestReplaceMacros(t *testing.T) {
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
mockBid := openrtb2.Bid{
ID: "mock-bid-id",
ImpID: "mock-imp-id",
Price: 1.23,
AdID: "mock-ad-id",
CrID: "mock-cr-id",
DealID: "mock-deal-id",
W: 980,
H: 240,
Ext: []byte(`{"prebid": {"type": "banner"}}`),
BURL: "https://example.com/win-notify",
Cat: []string{"IAB1"},
AdM: "<script type=\"text/javascript\">\n var uri = 'https://adsby.bidtheatre.com/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=${AUCTION_PRICE}&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"></sc'+'ript>');\n</script>",
NURL: "https://adsby.bidtheatre.com/video?z=27025;a=1922926;ex=36;es=prebid.org;eb=3672319;xs=940698616;so=1;tag=unspec_640_360;kuid=1d10dda6-740d-4386-94a0-7042b2ad2a66;wp=${AUCTION_PRICE};su=unknown;iab=vast2;dealId=;ma=eyJjZCI6ZmFsc2UsInN0IjozLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjkuNTY5ODA3NDQzNzY3Nzg2RS00LCJtemlwIjoiMTExMjAiLCJiaXAiOiI4MS4yMjcuODIuMjgiLCJhZ2lkIjozNTYyNzAyLCJtbG1vZGVsIjoibWFzdGVyX21sX2Nsa181NDMiLCJ1YSI6ImN1cmxcLzcuODcuMCIsImJycmUiOiJhYiIsIm1sb24iOjE4LjA2ODYsIm1yZWdpb24iOiJhYiIsImR0Ijo4LCJicmNvIjoic3dlIiwibWNpdHkiOiJzdG9ja2hvbG0iLCJicmNpIjoic3RvY2tob2xtIiwicGFnZXVybCI6InByZWJpZC5vcmciLCJpbXBpZCI6IngzNl9hc3gtYi1zMV8yNTY5OTI0ODYzMjY2ODA4OTM2IiwibWNvdW50cnkiOiJzd2UiLCJ0cyI6MTczMjA5NjgyNjg5OH0%3D;cd=0;cb0=;impId=x36_asx-b-s1_2569924863266808936;gdpr=0;gdpr_consent=",
}

resolveMacros(&mockBid)

if !strings.Contains(mockBid.AdM, "&wp=1.23&") {
t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
}

if strings.Contains(mockBid.AdM, "${AUCTION_PRICE}") {
t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
}

if !strings.Contains(mockBid.NURL, ";wp=1.23;") {
t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
}

if strings.Contains(mockBid.NURL, "${AUCTION_PRICE}") {
t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
}

}
128 changes: 128 additions & 0 deletions adapters/bidtheatre/bidtheatretest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 980,
"h": 240
},
{
"w": 980,
"h": 300
}
]
},
"ext": {
"bidder": {
"publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
}
}
}
],
"site": {
"page": "prebid.org"
},
"device": {
"ip": "81.227.82.28"
}
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://any.url",
"body": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 980,
"h": 240
},
{
"w": 980,
"h": 300
}
]
},
"ext": {
"bidder": {
"publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
}
}
}
],
"site": {
"page": "prebid.org"
},
"device": {
"ip": "81.227.82.28"
}
},
"impIDs":["test-imp-id"]
},
"mockResponse": {
"status": 200,
"body": {
"cur": "USD",
"seatbid": [
{
"seat": "5",
"bid": [
{
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 5.08712911605835,
"adm": "<script type=\"text\/javascript\">\n var uri = 'https:\/\/adsby.bidtheatre.com\/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=${AUCTION_PRICE}&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text\/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"><\/sc'+'ript>');\n<\/script>",
"adid": "1915538",
"cid": "c154375",
"crid": "1915538",
"w": 980,
"h": 240,
"ext": {
"prebid": {
"type": "banner"
}
}
}
]
}
]
}
}
}
],
"expectedBidResponses": [
{
"bids":[
{
"currency": "USD",
"bid": {
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 5.08712911605835,
"adm": "<script type=\"text\/javascript\">\n var uri = 'https:\/\/adsby.bidtheatre.com\/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=5.08712911605835&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text\/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"><\/sc'+'ript>');\n<\/script>",
"adid": "1915538",
"cid": "c154375",
"crid": "1915538",
"w": 980,
"h": 240,
"ext": {
"prebid": {
"type": "banner"
}
}
},
"type": "banner"
}
]
}
]
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

what happend if we add muli-format, multi-imps ?
please add multi-format and multi-imp

Copy link
Author

Choose a reason for hiding this comment

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

The mock impression already is multi-format, you want me to add mock impression requests that's multi-imp?

Loading
Loading