Skip to content
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
139 changes: 139 additions & 0 deletions adapters/adsmartx/adsmartx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package adsmartx

import (
"fmt"
"net/http"

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

func Builder(_ openrtb_ext.BidderName, cfg config.Adapter, _ config.Server) (adapters.Bidder, error) {
return &adapter{endpoint: cfg.Endpoint}, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var errs []error
validImps := make([]openrtb2.Imp, 0, len(request.Imp))
var setTestMode bool

for _, imp := range request.Imp {
impExt, err := parseImpExt(imp.Ext)
if err != nil {
errs = append(errs, fmt.Errorf("impID %s: %w", imp.ID, err))
continue
}

if imp.Banner == nil && imp.Video == nil {
errs = append(errs, fmt.Errorf("impID %s: no banner or video object specified", imp.ID))
continue
}

if imp.BidFloor == 0 && impExt.BidFloor > 0 {
imp.BidFloor = impExt.BidFloor
}

if impExt.TestMode == 1 {
setTestMode = true
}

validImps = append(validImps, imp)
}

if len(validImps) == 0 {
return nil, append(errs, fmt.Errorf("no valid impressions"))
}

request.Imp = validImps
if setTestMode {
request.Test = 1
}

reqJSON, err := jsonutil.Marshal(request)
if err != nil {
return nil, append(errs, err)
}

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

return []*adapters.RequestData{
{
Method: "POST",
Uri: a.endpoint,
Body: reqJSON,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(validImps),
},
}, errs
}

func parseImpExt(ext jsonutil.RawMessage) (openrtb_ext.ImpExtAdsmartx, error) {
var bidderExt adapters.ExtImpBidder
if err := jsonutil.Unmarshal(ext, &bidderExt); err != nil {
return openrtb_ext.ImpExtAdsmartx{}, err
}
var adsmartxExt openrtb_ext.ImpExtAdsmartx
if err := jsonutil.Unmarshal(bidderExt.Bidder, &adsmartxExt); err != nil {
return openrtb_ext.ImpExtAdsmartx{}, err
}
return adsmartxExt, nil
}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, reqData *adapters.RequestData, respData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(respData) {
return nil, nil
}
if err := adapters.CheckResponseStatusCodeForErrors(respData); err != nil {
return nil, []error{err}
}

var bidResp openrtb2.BidResponse
if err := jsonutil.Unmarshal(respData.Body, &bidResp); err != nil {
return nil, []error{err}
}

br := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid))
if bidResp.Cur != "" {
br.Currency = bidResp.Cur
}

var errs []error
for _, seatBid := range bidResp.SeatBid {
for i, bid := range seatBid.Bid {
bidType, err := getBidType(bid.MType)
if err != nil {
errs = append(errs, err)
continue
}

br.Bids = append(br.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
})
}
}
return br, errs
}

func getBidType(mtype openrtb2.MarkupType) (openrtb_ext.BidType, error) {
switch mtype {
Comment thread
pritishmd-talentica marked this conversation as resolved.
Comment thread
pritishmd-talentica marked this conversation as resolved.
Comment thread
pritishmd-talentica marked this conversation as resolved.
Comment thread
pritishmd-talentica marked this conversation as resolved.
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupVideo:
return openrtb_ext.BidTypeVideo, nil
default:
return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("unknown bid type mtype=%d", mtype),
}
}
}
164 changes: 164 additions & 0 deletions adapters/adsmartx/adsmartx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package adsmartx

import (
"strings"
"testing"

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/adapters/adapterstest"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/util/jsonutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(
openrtb_ext.BidderAdsmartx,
config.Adapter{
Endpoint: "https://ads.adsmartx.com/ads/rtb/prebid/server",
},
config.Server{
ExternalUrl: "http://hosturl.com",
GvlID: 0,
DataCenter: "2",
},
)

require.NoError(t, buildErr, "Builder returned unexpected error")
adapterstest.RunJSONBidderTest(t, "adsmartxtest", bidder)
}

func TestParseImpExt(t *testing.T) {
tests := []struct {
name string
ext jsonutil.RawMessage
wantErr bool
}{
{"Valid ext", jsonutil.RawMessage(`{"bidder":{"bidfloor":0.5}}`), false},
{"Valid ext with sspId", jsonutil.RawMessage(`{"bidder":{"sspId":"ssp-123","siteId":"site-456"}}`), false},
{"Invalid JSON", jsonutil.RawMessage(`not-json`), true},
{"Not an object", jsonutil.RawMessage(`"string"`), true},
{"Bidder not object", jsonutil.RawMessage(`{"bidder":"not-an-object"}`), true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := parseImpExt(tt.ext)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

func TestGetBidType(t *testing.T) {
tests := []struct {
name string
mtype openrtb2.MarkupType
wantErr bool
wantBidTy openrtb_ext.BidType
}{
{"Banner", openrtb2.MarkupBanner, false, openrtb_ext.BidTypeBanner},
{"Video", openrtb2.MarkupVideo, false, openrtb_ext.BidTypeVideo},
{"Unknown", 99, true, ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bidType, err := getBidType(tt.mtype)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantBidTy, bidType)
})
}
}

func TestMakeRequestsErrors(t *testing.T) {
a := &adapter{endpoint: "http://test-endpoint"}
tests := []struct {
name string
imps []openrtb2.Imp
wantErr string
}{
{"Invalid ext", []openrtb2.Imp{{ID: "1", Ext: jsonutil.RawMessage(`not-json`)}}, "impID 1:"},
{"No valid imps", []openrtb2.Imp{}, "no valid impressions"},
{"No banner or video", []openrtb2.Imp{{ID: "1", Ext: jsonutil.RawMessage(`{"bidder":{"bidfloor": 0.5}}`)}}, "no banner or video object specified"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &openrtb2.BidRequest{Imp: tt.imps}
_, errs := a.MakeRequests(req, nil)
require.NotEmpty(t, errs, "expected error, got none")
found := false
for _, err := range errs {
if err != nil && (tt.wantErr == "" || strings.Contains(err.Error(), tt.wantErr)) {
found = true
break
}
}
assert.True(t, found, "expected error containing %q, got %v", tt.wantErr, errs)
})
}
}

func TestMakeBidsErrors(t *testing.T) {
a := &adapter{endpoint: "http://test-endpoint"}
validReq := &openrtb2.BidRequest{ID: "1"}
validReqData := &adapters.RequestData{}
tests := []struct {
name string
respData *adapters.ResponseData
wantErr string
}{
{"Non-200/204 response", &adapters.ResponseData{StatusCode: 500, Body: []byte(`{}`)}, "Unexpected status code"},
{"Invalid JSON", &adapters.ResponseData{StatusCode: 200, Body: []byte(`not-json`)}, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, errs := a.MakeBids(validReq, validReqData, tt.respData)
require.NotEmpty(t, errs, "expected error, got none")
found := false
for _, err := range errs {
if err != nil && strings.Contains(err.Error(), tt.wantErr) {
found = true
break
}
}
assert.True(t, found, "expected error containing %q, got %v", tt.wantErr, errs)
})
}
}

func TestMakeBidsSkipsBadBidType(t *testing.T) {
a := &adapter{endpoint: "http://test-endpoint"}
validReq := &openrtb2.BidRequest{ID: "1"}
validReqData := &adapters.RequestData{}

respBody := `{
"id": "1",
"seatbid": [{
"bid": [
{"id": "good-bid", "impid": "1", "price": 1.0, "adm": "<div>ad</div>", "mtype": 1},
{"id": "bad-bid", "impid": "2", "price": 2.0, "adm": "<div>ad2</div>", "mtype": 99},
{"id": "good-bid-2", "impid": "3", "price": 3.0, "adm": "<div>ad3</div>", "mtype": 2}
]
}],
"cur": "USD"
}`

resp := &adapters.ResponseData{StatusCode: 200, Body: []byte(respBody)}
bidderResp, errs := a.MakeBids(validReq, validReqData, resp)

require.NotNil(t, bidderResp, "expected bid response, got nil")
assert.Len(t, bidderResp.Bids, 2, "expected 2 valid bids (bad bid should be skipped)")
assert.Len(t, errs, 1, "expected 1 error for the bad bid type")
assert.Contains(t, errs[0].Error(), "unknown bid type")
}
36 changes: 36 additions & 0 deletions adapters/adsmartx/adsmartxtest/exemplary/banner_bid_floor_set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"mockBidRequest": {
"id": "test-bid-floor-set",
"imp": [
{
"id": "1",
"banner": { "w": 300, "h": 250 },
"ext": { "bidder": { "bidfloor": 1.23, "sspId": "ssp-123" } }
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://ads.adsmartx.com/ads/rtb/prebid/server",
"body": {
"id": "test-bid-floor-set",
"imp": [
{
"id": "1",
"banner": { "w": 300, "h": 250 },
"bidfloor": 1.23,
"ext": { "bidder": { "bidfloor": 1.23, "sspId": "ssp-123" } }
}
]
},
"impIDs": ["1"]
},
"mockResponse": {
"status": 204,
"body": {}
}
}
],
"expectedBidResponses": []
}
Loading
Loading