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

Add Adot adapter #1605

Merged
merged 3 commits into from
Jan 11, 2021
Merged
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
117 changes: 117 additions & 0 deletions adapters/adot/adot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package adot
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved

import (
"encoding/json"
"fmt"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
"net/http"
)

type adapter struct {
endpoint string
}

type adotBidExt struct {
Adot bidExt `json:"adot"`
}

type bidExt struct {
MediaType string `json:"media_type"`
}

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

// MakeRequests makes the HTTP requests which should be made to fetch bids.
func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var reqJSON []byte
var err error

if reqJSON, err = json.Marshal(request); err != nil {
return nil, []error{fmt.Errorf("unable to marshal openrtb request (%v)", err)}
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")

return []*adapters.RequestData{{
Method: "POST",
Uri: a.endpoint,
Body: reqJSON,
Headers: headers,
}}, nil
}

// MakeBids unpacks the server's response into Bids.
// The bidder return a status code 204 when it cannot delivery an ad.
func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if response.StatusCode == http.StatusNoContent {
return nil, nil
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
}

if response.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're missing coverage here. See adapters/connectad/connectadtest/supplemental/err500.json for an idea on how to achieve coverage of this error case.

Copy link
Contributor

Choose a reason for hiding this comment

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

This specific error condition still looks to be untested.

Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}

if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're missing coverage here. See adapters/connectad/connectadtest/supplemental/err500.json for an idea on how to achieve coverage of this error case.

Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}

var bidResp openrtb.BidResponse
if err := json.Unmarshal(response.Body, &bidResp); err != nil {
return nil, []error{err}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're missing coverage here. See adapters/connectad/connectadtest/supplemental/badresponse.json as an example.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We're still missing coverage here for this error condition.

}

bidsCapacity := 1
if len(bidResp.SeatBid) > 0 {
bidsCapacity = len(bidResp.SeatBid[0].Bid)
}
bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity)

for _, sb := range bidResp.SeatBid {
for i := range sb.Bid {
if bidType, err := getMediaTypeForBid(&sb.Bid[i]); err == nil {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &sb.Bid[i],
BidType: bidType,
})
}
}
}

return bidResponse, nil
}

// getMediaTypeForBid determines which type of bid.
func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) {
if bid == nil {
return "", fmt.Errorf("the bid request object is nil")
}

var impExt adotBidExt
if err := json.Unmarshal(bid.Ext, &impExt); err == nil {
switch impExt.Adot.MediaType {
case string(openrtb_ext.BidTypeBanner):
return openrtb_ext.BidTypeBanner, nil
case string(openrtb_ext.BidTypeVideo):
return openrtb_ext.BidTypeVideo, nil
case string(openrtb_ext.BidTypeNative):
return openrtb_ext.BidTypeNative, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a native JSON test please?

}
}

return "", fmt.Errorf("unrecognized bid type in response from adot")
}
76 changes: 76 additions & 0 deletions adapters/adot/adot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package adot

import (
"encoding/json"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/adapters/adapterstest"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
"testing"
)

const testsBidderEndpoint = "https://dsp.adotmob.com/headerbidding/bidrequest"

func TestJsonSamples(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you've tried running the JSON tests, you'll see that they are passing but they are actually giving false positives. I was able to figure this out from looking at your code coverage. I noticed that none of the case statements in getMediaTypeForBid were being reached when I just ran the JSON tests, and the logic that assigns bidResponse.Bids assuming the media type lookup didn't error also was not being reached when running the whole test suite. This led me to believe you had malformed JSON files. See my comments on your JSON files.

As a side note, our team is currently working on updating the JSON test framework to eliminate the chance of a false positive.

bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{
Endpoint: testsBidderEndpoint})

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

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

//Test the media type error
func TestMediaTypeError(t *testing.T) {
_, err := getMediaTypeForBid(nil)

assert.Error(t, err)

byteInvalid, _ := json.Marshal(&adotBidExt{Adot: bidExt{"invalid"}})
_, err = getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteInvalid)})

assert.Error(t, err)
}

//Test the bid response when the bidder return a status code 204
func TestBidResponseNoContent(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could also achieve this coverage using the JSON test framework.

bidder, buildErr := Builder(openrtb_ext.BidderAdot, config.Adapter{
Endpoint: "https://dsp.adotmob.com/headerbidding/bidrequest"})

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

bidResponse, err := bidder.MakeBids(nil, nil, &adapters.ResponseData{StatusCode: 204})
if bidResponse != nil {
t.Fatalf("the bid response should be nil since the bidder status is No Content")
} else if err != nil {
t.Fatalf("the error should be nil since the bidder status is 204 : No Content")
}
}

//Test the media type for a bid response
func TestMediaTypeForBid(t *testing.T) {
byteBanner, _ := json.Marshal(&adotBidExt{Adot: bidExt{"banner"}})
byteVideo, _ := json.Marshal(&adotBidExt{Adot: bidExt{"video"}})
byteNative, _ := json.Marshal(&adotBidExt{Adot: bidExt{"native"}})

bidTypeBanner, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteBanner)})
if bidTypeBanner != openrtb_ext.BidTypeBanner {
t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeBanner, openrtb_ext.BidTypeBanner)
}

bidTypeVideo, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteVideo)})
if bidTypeVideo != openrtb_ext.BidTypeVideo {
t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeVideo, openrtb_ext.BidTypeVideo)
}

bidTypeNative, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteNative)})
if bidTypeNative != openrtb_ext.BidTypeNative {
t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeNative, openrtb_ext.BidTypeVideo)
}
}
94 changes: 94 additions & 0 deletions adapters/adot/adottest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"mockBidRequest": {
"id": "test-request-banner-id",
"imp": [
{
"id": "test-imp-banner-id",
"banner": {
"format": [
{
"w": 320,
"h": 250
}
],
"w": 320,
"h": 250
},
"ext": {
"adot": {}
}
}
]
},
"httpCalls": [{
"expectedRequest": {
"uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
"body": {
"id": "test-request-banner-id",
"imp": [
{
"id": "test-imp-banner-id",
"banner": {
"format": [
{
"w": 320,
"h": 250
}
],
"w": 320,
"h": 250
},
"ext": {
"adot": {}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-banner-id",
"seatbid": [{
"seat": "adot",
"bid": [{
"id": "test-request-banner-id",
"impid": "test-imp-banner-id",
"price": 1.16346,
"adm": "some-test-ad",
"w": 320,
"h": 50,
"ext": {
"adot": {
"media_type": "banner"
}
}
}]
}
],
"cur": "USD"
}
}
}],

"expectedBidResponses": [{
"currency": "USD",
"bids": [{
"bid": {
"id": "test-request-banner-id",
"impid": "test-imp-banner-id",
"price": 1.16346,
"adm": "some-test-ad",
"w": 320,
"h": 50,
"ext": {
"adot": {
"media_type": "banner"
}
}
},
"type": "banner"
}]
}]
}

101 changes: 101 additions & 0 deletions adapters/adot/adottest/exemplary/simple-interstitial.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

You'll need to make changes similar to those recommended in simple-banner.json in all of your exemplary and supplemental JSON test files.

"mockBidRequest": {
"id": "test-request-inter-id",
"imp": [
{
"id": "test-imp-inter-id",
"banner": {
"format": [
{
"w": 320,
"h": 480
}
],
"w": 320,
"h": 480
},
"instl":1,
"ext": {
"adot": {}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://dsp.adotmob.com/headerbidding/bidrequest",
"body": {
"id": "test-request-inter-id",
"imp": [
{
"id": "test-imp-inter-id",
"banner": {
"format": [
{
"w": 320,
"h": 480
}
],
"w": 320,
"h": 480
},
"instl":1,
"ext": {
"adot": {}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"seatbid": [
{
"seat": "adot",
"bid": [{
"id": "test-request-inter-id",
"impid": "test-imp-inter-id",
"adm": "some-test-ad",
"price": 1.16346,
"w": 320,
"h": 480,
"ext": {
"adot": {
"media_type": "banner"
}
}
}]
}
],
"cur": "USD"
}
}
}
],

"expectedBidResponses": [
{
"bids": [{
"bid": {
"id": "test-request-inter-id",
"impid": "test-imp-inter-id",
"price": 1.16346,
"adm": "some-test-ad",
"w": 320,
"h": 480,
"ext": {
"adot": {
"media_type": "banner"
}
}
},
"type": "banner"
}
]
}
]
}

Loading