Skip to content
This repository has been archived by the owner on Dec 22, 2022. It is now read-only.

Commit

Permalink
Kidoz adapter (prebid#1210)
Browse files Browse the repository at this point in the history
Co-authored-by: Ryan Haksi <ryan.haksi@freshgrade.com>
  • Loading branch information
rhaksi-kidoz and ryan-haksi committed Mar 18, 2020
1 parent 1517202 commit a9a5d95
Show file tree
Hide file tree
Showing 35 changed files with 1,394 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ debug
pbs.*
inventory_url.yaml

# generated log files during tests
analytics/config/testFiles/
analytics/filesystem/testFiles/

# autogenerated version file
# static/version.txt

Expand Down
188 changes: 188 additions & 0 deletions adapters/kidoz/kidoz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package kidoz

import (
"encoding/json"
"errors"
"net/http"
"strconv"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
)

type KidozAdapter struct {
endpoint string
}

func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-openrtb-version", "2.5")

impressions := request.Imp
result := make([]*adapters.RequestData, 0, len(impressions))
errs := make([]error, 0, len(impressions))

for i, impression := range impressions {
if impression.Banner == nil && impression.Video == nil {
errs = append(errs, &errortypes.BadInput{
Message: "Kidoz only supports banner or video ads",
})
continue
}

if impression.Banner != nil {
banner := impression.Banner
if banner.Format == nil {
errs = append(errs, &errortypes.BadInput{
Message: "banner format required",
})
continue
}
if len(banner.Format) == 0 {
errs = append(errs, &errortypes.BadInput{
Message: "banner format array is empty",
})
continue
}
}

if len(impression.Ext) == 0 {
errs = append(errs, errors.New("impression extensions required"))
continue
}
var bidderExt adapters.ExtImpBidder
err := json.Unmarshal(impression.Ext, &bidderExt)
if err != nil {
errs = append(errs, err)
continue
}
if len(bidderExt.Bidder) == 0 {
errs = append(errs, errors.New("bidder required"))
continue
}
var impressionExt openrtb_ext.ExtImpKidoz
err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
if err != nil {
errs = append(errs, err)
continue
}
if impressionExt.AccessToken == "" {
errs = append(errs, errors.New("Kidoz access_token required"))
continue
}
if impressionExt.PublisherID == "" {
errs = append(errs, errors.New("Kidoz publisher_id required"))
continue
}

request.Imp = impressions[i : i+1]
body, err := json.Marshal(request)
if err != nil {
errs = append(errs, err)
continue
}
result = append(result, &adapters.RequestData{
Method: "POST",
Uri: a.endpoint,
Body: body,
Headers: headers,
})
}

request.Imp = impressions

if len(result) == 0 {
return nil, errs
}
return result, errs
}

func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
var errs []error

switch responseData.StatusCode {
case http.StatusNoContent:
fallthrough
case http.StatusServiceUnavailable:
return nil, nil

case http.StatusBadRequest:
fallthrough
case http.StatusUnauthorized:
fallthrough
case http.StatusForbidden:
return nil, []error{&errortypes.BadInput{
Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
}}

case http.StatusOK:
break

default:
return nil, []error{&errortypes.BadServerResponse{
Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
}}
}

var bidResponse openrtb.BidResponse
err := json.Unmarshal(responseData.Body, &bidResponse)
if err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: err.Error(),
}}
}

response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))

for _, seatBid := range bidResponse.SeatBid {
for _, bid := range seatBid.Bid {
thisBid := bid
bidType := GetMediaTypeForImp(bid.ImpID, request.Imp)
if bidType == UndefinedMediaType {
errs = append(errs, &errortypes.BadServerResponse{
Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID,
})
continue
}
response.Bids = append(response.Bids, &adapters.TypedBid{
Bid: &thisBid,
BidType: bidType,
})
}
}

return response, errs
}

func NewKidozBidder(endpoint string) *KidozAdapter {
return &KidozAdapter{
endpoint: endpoint,
}
}

const UndefinedMediaType = openrtb_ext.BidType("")

func GetMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
var bidType openrtb_ext.BidType = UndefinedMediaType
for _, impression := range imps {
if impression.ID != impID {
continue
}
switch {
case impression.Banner != nil:
bidType = openrtb_ext.BidTypeBanner
case impression.Video != nil:
bidType = openrtb_ext.BidTypeVideo
case impression.Native != nil:
bidType = openrtb_ext.BidTypeNative
case impression.Audio != nil:
bidType = openrtb_ext.BidTypeAudio
}
break
}
return bidType
}
113 changes: 113 additions & 0 deletions adapters/kidoz/kidoz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package kidoz

import (
"math"
"net/http"
"testing"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/adapters/adapterstest"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "kidoztest", NewKidozBidder("http://example.com/prebid"))
}

func makeBidRequest() *openrtb.BidRequest {
request := &openrtb.BidRequest{
ID: "test-req-id-0",
Imp: []openrtb.Imp{
{
ID: "test-imp-id-0",
Banner: &openrtb.Banner{
Format: []openrtb.Format{
{
W: 320,
H: 50,
},
},
},
Ext: []byte(`{"bidder":{"access_token":"token-0","publisher_id":"pub-0"}}`),
},
},
}
return request
}

func TestMakeRequests(t *testing.T) {
kidoz := NewKidozBidder("http://example.com/prebid")

t.Run("Handles Request marshal failure", func(t *testing.T) {
request := makeBidRequest()
request.Imp[0].BidFloor = math.Inf(1) // cant be marshalled
extra := &adapters.ExtraRequestInfo{}
reqs, errs := kidoz.MakeRequests(request, extra)
// cant assert message its different on different versions of go
assert.Equal(t, 1, len(errs))
assert.Contains(t, errs[0].Error(), "json")
assert.Equal(t, 0, len(reqs))
})
}

func TestMakeBids(t *testing.T) {
kidoz := NewKidozBidder("http://example.com/prebid")

t.Run("Handles response marshal failure", func(t *testing.T) {
request := makeBidRequest()
requestData := &adapters.RequestData{}
responseData := &adapters.ResponseData{
StatusCode: http.StatusOK,
}

resp, errs := kidoz.MakeBids(request, requestData, responseData)
// cant assert message its different on different versions of go
assert.Equal(t, 1, len(errs))
assert.Contains(t, errs[0].Error(), "JSON")
assert.Nil(t, resp)
})
}

func TestGetMediaTypeForImp(t *testing.T) {
imps := []openrtb.Imp{
{
ID: "1",
Banner: &openrtb.Banner{},
},
{
ID: "2",
Video: &openrtb.Video{},
},
{
ID: "3",
Native: &openrtb.Native{},
},
{
ID: "4",
Audio: &openrtb.Audio{},
},
}

t.Run("Bid not found is type empty string", func(t *testing.T) {
actual := GetMediaTypeForImp("ARGLE_BARGLE", imps)
assert.Equal(t, UndefinedMediaType, actual)
})
t.Run("Can find banner type", func(t *testing.T) {
actual := GetMediaTypeForImp("1", imps)
assert.Equal(t, openrtb_ext.BidTypeBanner, actual)
})
t.Run("Can find video type", func(t *testing.T) {
actual := GetMediaTypeForImp("2", imps)
assert.Equal(t, openrtb_ext.BidTypeVideo, actual)
})
t.Run("Can find native type", func(t *testing.T) {
actual := GetMediaTypeForImp("3", imps)
assert.Equal(t, openrtb_ext.BidTypeNative, actual)
})
t.Run("Can find audio type", func(t *testing.T) {
actual := GetMediaTypeForImp("4", imps)
assert.Equal(t, openrtb_ext.BidTypeAudio, actual)
})
}
71 changes: 71 additions & 0 deletions adapters/kidoz/kidoztest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-impression-id-1",
"banner": {
"format": [
{
"w": 320,
"h": 50
}
]
},
"ext": {
"bidder": {
"access_token": "test-token-1",
"publisher_id": "test-publisher-1"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "http://example.com/prebid",
"body": {
"id": "test-request-id",
"imp": [
{
"id": "test-impression-id-1",
"banner": {
"format": [
{
"w": 320,
"h": 50
}
]
},
"ext": {
"bidder": {
"access_token": "test-token-1",
"publisher_id": "test-publisher-1"
}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-response-id",
"seatbid": [
{
"bid": [
{
"id": "test-bid-id-1",
"impid": "test-impression-id-1",
"price": 1
}
],
"seat": "kidoz"
}
]
}
}
}
]
}
Loading

0 comments on commit a9a5d95

Please sign in to comment.