Skip to content

Commit

Permalink
New Adapter: VideoHeroes (#2399)
Browse files Browse the repository at this point in the history
  • Loading branch information
videoheroes authored Jan 4, 2023
1 parent 588c883 commit fa50f2e
Show file tree
Hide file tree
Showing 21 changed files with 1,801 additions and 0 deletions.
51 changes: 51 additions & 0 deletions adapters/videoheroes/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package videoheroes

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/openrtb_ext"
)

func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, validParam := range validParams {
if err := validator.Validate(openrtb_ext.BidderVideoHeroes, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected VideoHeroes params: %s", validParam)
}
}
}

func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, invalidParam := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderVideoHeroes, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}

var validParams = []string{
`{ "placementId": "f897beb0daba0253d8e59a098eef9311" }`,
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`4.2`,
`[]`,
`{}`,
`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
`{ "placementid": "f897beb0daba0253d8e59a098eef9311" }`,
`{ "PlacementId": "f897beb0daba0253d8e59a098eef9311" }`,
}
157 changes: 157 additions & 0 deletions adapters/videoheroes/videoheroes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package videoheroes

import (
"encoding/json"
"fmt"
"net/http"
"text/template"

"github.com/prebid/openrtb/v17/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/macros"
"github.com/prebid/prebid-server/openrtb_ext"
)

type adapter struct {
endpoint *template.Template
}

// Builder builds a new instance of the VideoHeroes adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
uri, err := template.New("endpointTemplate").Parse(config.Endpoint)
if err != nil {
return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
}

bidder := &adapter{
endpoint: uri,
}
return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var videoHeroesExt *openrtb_ext.ExtImpVideoHeroes
var err error

videoHeroesExt, err = a.getImpressionExt(&request.Imp[0])
if err != nil {
return nil, []error{err}
}

request.Imp[0].Ext = nil

reqJSON, err := json.Marshal(request)
if err != nil {
return nil, []error{err}
}

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

url, err := a.buildEndpointURL(videoHeroesExt)
if err != nil {
return nil, []error{err}
}

return []*adapters.RequestData{{
Method: http.MethodPost,
Body: reqJSON,
Uri: url,
Headers: headers,
}}, nil
}

func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoHeroes, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
var videoHeroesExt openrtb_ext.ExtImpVideoHeroes
if err := json.Unmarshal(bidderExt.Bidder, &videoHeroesExt); err != nil {
return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
return &videoHeroesExt, nil
}

func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpVideoHeroes) (string, error) {
endpointParams := macros.EndpointTemplateParams{PublisherID: params.PlacementID}
return macros.ResolveMacros(a.endpoint, endpointParams)
}

func (a *adapter) MakeBids(
receivedRequest *openrtb2.BidRequest,
bidderRequest *adapters.RequestData,
bidderResponse *adapters.ResponseData,
) (
*adapters.BidderResponse,
[]error,
) {

if bidderResponse.StatusCode == http.StatusNoContent {
return nil, []error{&errortypes.BadInput{Message: "No bid"}}
}

if bidderResponse.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
}}
}

if bidderResponse.StatusCode == http.StatusServiceUnavailable {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Service Unavailable. Status Code: [ %d ] ", bidderResponse.StatusCode),
}}
}

if bidderResponse.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderResponse.StatusCode),
}}
}

var bidResponse openrtb2.BidResponse
if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Bad Server Response",
}}
}

if len(bidResponse.SeatBid) == 0 {
return nil, []error{&errortypes.BadServerResponse{
Message: "Empty SeatBid array",
}}
}

bidResponseFinal := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid))
sb := bidResponse.SeatBid[0]

for _, bid := range sb.Bid {
bidResponseFinal.Bids = append(bidResponseFinal.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImp(bid.ImpID, receivedRequest.Imp),
})
}
return bidResponseFinal, nil
}

func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
mediaType := openrtb_ext.BidTypeBanner
for _, imp := range imps {
if imp.ID == impId {
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
} else if imp.Native != nil {
mediaType = openrtb_ext.BidTypeNative
}
return mediaType
}
}
return mediaType
}
30 changes: 30 additions & 0 deletions adapters/videoheroes/videoheroes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package videoheroes

import (
"testing"

"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"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderVideoHeroes, config.Adapter{
Endpoint: "http://point.contextualadv.com/?t=3&partner={{.PublisherID}}"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

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

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

func TestEndpointTemplateMalformed(t *testing.T) {
_, buildErr := Builder(openrtb_ext.BidderVideoHeroes, config.Adapter{
Endpoint: "{{Malformed}}"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

assert.Error(t, buildErr)
}
140 changes: 140 additions & 0 deletions adapters/videoheroes/videoheroestest/exemplary/banner-app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{
"mockBidRequest": {
"id": "request-id",
"app": {
"publisher": {
"id": "123456789"
},
"cat": [
"IAB9-1"
],
"bundle": "com.app.test",
"name": "Test App",
"id": "12345678"
},
"device": {
"ua": "useragent",
"ip": "100.100.100.100",
"language": "en"
},
"tmax": 1000,
"user": {
"id": "some-user"
},
"imp": [
{
"id": "impression-id",
"tagid": "tid",
"banner": {
"w":320,
"h":50
},
"ext": {
"bidder": {
"placementId": "f897beb0daba0253d8e59a098eef9311"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"headers": {
"Content-Type": [
"application/json;charset=utf-8"
],
"Accept": [
"application/json"
]
},
"uri": "http://point.contextualadv.com/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
"body": {
"id": "request-id",
"device": {
"ua": "useragent",
"ip": "100.100.100.100",
"language": "en"
},
"imp": [
{
"id": "impression-id",
"banner": {
"w":320,
"h":50
},
"tagid": "tid"
}
],
"app": {
"publisher": {
"id": "123456789"
},
"cat": [
"IAB9-1"
],
"bundle": "com.app.test",
"name": "Test App",
"id": "12345678"
},
"user": {
"id": "some-user"
},
"tmax": 1000
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "resp-id",
"seatbid": [
{
"bid": [
{
"id": "123456789",
"impid": "impression-id",
"price": 2,
"adm": "adm code",
"adomain": [
"testdomain.com"
],
"crid": "100",
"w":320,
"h":50
}
],
"type": "banner",
"seat": "videoheroes"
}
],
"cur": "USD",
"ext": {
"responsetimemillis": {
"videoheroes": 120
},
"tmaxrequest": 1000
}
}
}
}
],
"expectedBidResponses": [
{
"bids": [{
"bid": {
"id": "123456789",
"impid": "impression-id",
"price": 2,
"adm": "adm code",
"adomain": [
"testdomain.com"
],
"crid": "100",
"w":320,
"h":50
},
"type": "banner"
}]
}
]
}
Loading

0 comments on commit fa50f2e

Please sign in to comment.