-
Notifications
You must be signed in to change notification settings - Fork 901
New Adapter: RiseMediaTech #4437
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
base: master
Are you sure you want to change the base?
Changes from all commits
e4d7e09
03c291c
ff5d135
f6d1651
a87b0a1
9e92df2
90590dd
9579ed9
5d6ea8e
0525929
a31a0af
ddbb2d8
1da085f
efe1f59
3c2fcaa
43073e0
769df42
ed0afae
555eed6
e75127a
6b46abf
87651f5
4bc1cc8
f8b6285
92e43bd
8a36c57
afe46c2
fbf1fe8
4cdb024
cb85b99
217d60a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package risemediatech | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/prebid/prebid-server/v3/openrtb_ext" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestValidParams(t *testing.T) { | ||
| validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
| require.NoError(t, err, "Failed to fetch the JSON schema") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| input string | ||
| }{ | ||
| {"Valid bidfloor only", `{"bidfloor": 0.01}`}, | ||
| {"Valid bidfloor with testMode", `{"bidfloor": 2.5, "testMode": 1}`}, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| assert.NoError(t, validator.Validate(openrtb_ext.BidderRiseMediaTech, json.RawMessage(tt.input))) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestInvalidParams(t *testing.T) { | ||
| validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
| require.NoError(t, err, "Failed to fetch the JSON schema") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| input string | ||
| }{ | ||
| {"Invalid bidfloor type", `{"bidfloor": "1.2"}`}, | ||
| {"Invalid testMode type", `{"testMode": "yes"}`}, | ||
| {"Negative bidfloor", `{"bidfloor": -5}`}, | ||
| {"Invalid testMode value", `{"testMode": 9999}`}, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| assert.Error(t, validator.Validate(openrtb_ext.BidderRiseMediaTech, json.RawMessage(tt.input))) | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,141 @@ | ||||||||||||||||||||
| package risemediatech | ||||||||||||||||||||
|
|
||||||||||||||||||||
| 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/iterutil" | ||||||||||||||||||||
| "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 | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance: Pre-allocate Slice Pre-allocating the slice with capacity avoids multiple allocations during append operations. Recommendation: validImps := make([]openrtb2.Imp, 0, len(request.Imp))This is a standard Go optimization pattern for better performance. |
||||||||||||||||||||
| validImps := make([]openrtb2.Imp, 0, len(request.Imp)) | ||||||||||||||||||||
| // Note: If ANY impression has testMode=1, the entire request is marked as test | ||||||||||||||||||||
| var setTestMode bool | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for imp := range iterutil.SlicePointerValues(request.Imp) { | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Remove SlicePointerValues and use standard iteration to avoid the unnecessary copy on line 52. This pattern is inconsistent - using SlicePointerValues to avoid copies during iteration, but then dereferencing with for _, imp := range request.Imp {
impExt, err := parseImpExt(imp.Ext)
if err != nil {
errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("impID %s: %w", imp.ID, err)})
continue
}
// ... rest of validation ...
validImps = append(validImps, imp)
} |
||||||||||||||||||||
| impExt, err := parseImpExt(imp.Ext) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("impID %s: %v", imp.ID, err)}) | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (blocking): Use Using
Suggested change
|
||||||||||||||||||||
| continue | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if imp.Banner == nil && imp.Video == nil { | ||||||||||||||||||||
| errs = append(errs, &errortypes.BadInput{ | ||||||||||||||||||||
| Message: fmt.Sprintf("impID %s: no banner or video object specified", imp.ID), | ||||||||||||||||||||
| }) | ||||||||||||||||||||
| continue | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if imp.BidFloor == 0 && impExt.BidFloor > 0 { | ||||||||||||||||||||
| imp.BidFloor = impExt.BidFloor | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TestMode Behavior Needs Clarification The current implementation sets Questions:
Recommendation: At minimum, add a comment documenting this behavior: // Note: If ANY impression has testMode=1, the entire request is marked as test
var setTestMode boolOr consider alternative logic if all impressions should have testMode=1. |
||||||||||||||||||||
| if impExt.TestMode == 1 { | ||||||||||||||||||||
| setTestMode = true | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| validImps = append(validImps, *imp) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if len(validImps) == 0 { | ||||||||||||||||||||
| return nil, append(errs, &errortypes.BadInput{Message: "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.ExtImpRiseMediaTech, error) { | ||||||||||||||||||||
| var bidderExt adapters.ExtImpBidder | ||||||||||||||||||||
| if err := jsonutil.Unmarshal(ext, &bidderExt); err != nil { | ||||||||||||||||||||
| return openrtb_ext.ExtImpRiseMediaTech{}, err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| var riseExt openrtb_ext.ExtImpRiseMediaTech | ||||||||||||||||||||
| if err := jsonutil.Unmarshal(bidderExt.Bidder, &riseExt); err != nil { | ||||||||||||||||||||
| return openrtb_ext.ExtImpRiseMediaTech{}, err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return riseExt, 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 | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for seatBid := range iterutil.SlicePointerValues(bidResp.SeatBid) { | ||||||||||||||||||||
| for bid := range iterutil.SlicePointerValues(seatBid.Bid) { | ||||||||||||||||||||
| bidType, err := getBidType(bid) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return nil, []error{err} | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+116
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (blocking): MakeBids discards ALL bids when encountering a single bad bid type. When one bid has an unknown The standard pattern across other adapters (like
Suggested change
Then at the end of MakeBids, return |
||||||||||||||||||||
|
|
||||||||||||||||||||
| typedBid := &adapters.TypedBid{ | ||||||||||||||||||||
| Bid: bid, | ||||||||||||||||||||
| BidType: bidType, | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| br.Bids = append(br.Bids, typedBid) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return br, nil | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func getBidType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { | ||||||||||||||||||||
| switch bid.MType { | ||||||||||||||||||||
| case openrtb2.MarkupBanner: | ||||||||||||||||||||
| return openrtb_ext.BidTypeBanner, nil | ||||||||||||||||||||
| case openrtb2.MarkupVideo: | ||||||||||||||||||||
| return openrtb_ext.BidTypeVideo, nil | ||||||||||||||||||||
| default: | ||||||||||||||||||||
| return "", fmt.Errorf("unknown bid type mtype=%d", bid.MType) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| package risemediatech | ||
|
|
||
| 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.BidderRiseMediaTech, | ||
| config.Adapter{ | ||
| Endpoint: "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", | ||
| }, | ||
| config.Server{ | ||
| ExternalUrl: "http://hosturl.com", | ||
| GvlID: 0, | ||
| DataCenter: "2", | ||
| }, | ||
| ) | ||
|
|
||
| if buildErr != nil { | ||
| t.Fatalf("Builder returned unexpected error: %v", buildErr) | ||
| } | ||
|
scr-oath marked this conversation as resolved.
|
||
| require.NoError(t, buildErr, "Builder returned unexpected error") | ||
|
|
||
| adapterstest.RunJSONBidderTest(t, "risemediatechtest", bidder) | ||
| } | ||
|
|
||
| func TestParseImpExt(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| ext jsonutil.RawMessage | ||
| wantErr bool | ||
| }{ | ||
| {"Valid ext", jsonutil.RawMessage(`{"bidder":{"placementId":"abc"}}`), 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) { | ||
| bid := &openrtb2.Bid{MType: tt.mtype} | ||
| bidType, err := getBidType(bid) | ||
| 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`)}, ""}, | ||
| {"Unknown mtype", &adapters.ResponseData{StatusCode: 200, Body: []byte(`{"id":"1","seatbid":[{"bid":[{"id":"b1","impid":"1","price":1.0,"adm":"<div>test</div>","mtype":99}]}],"cur":"USD"}`)}, "unknown bid type"}, | ||
| } | ||
| 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) | ||
| }) | ||
| } | ||
| } | ||
| 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": { "placementId": "abc", "bidFloor": 1.23 } } | ||
| } | ||
| ] | ||
| }, | ||
| "httpCalls": [ | ||
| { | ||
| "expectedRequest": { | ||
| "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", | ||
| "body": { | ||
| "id": "test-bid-floor-set", | ||
| "imp": [ | ||
| { | ||
| "id": "1", | ||
| "banner": { "w": 300, "h": 250 }, | ||
| "bidfloor": 1.23, | ||
| "ext": { "bidder": { "placementId": "abc", "bidFloor": 1.23 } } | ||
| } | ||
| ] | ||
| }, | ||
| "impIDs": ["1"] | ||
| }, | ||
| "mockResponse": { | ||
| "status": 204, | ||
| "body": {} | ||
| } | ||
| } | ||
| ], | ||
| "expectedBidResponses": [] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go Idiom: Mark Unused Parameters
The
bidderNameandserverparameters are unused. In Go, it's conventional to mark them with_to signal intent.Recommendation: