Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e4d7e09
RM 825 : Prebid server adapter for risemediatech
pritishmd-talentica Jun 18, 2025
03c291c
Merge remote-tracking branch 'upstream/master' into rm-pbs-adapter
pritishmd-talentica Jun 18, 2025
ff5d135
RM-825 : Modified logic
pritishmd-talentica Jul 10, 2025
f6d1651
Modified the tests
pritishmd-talentica Jul 15, 2025
a87b0a1
Merge pull request #1 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Jul 15, 2025
9e92df2
Changes as per review comments in upstream repo PR
pritishmd-talentica Jul 25, 2025
90590dd
Changes for review comments and improving unit test coverage
pritishmd-talentica Jul 25, 2025
9579ed9
Review comment changes
pritishmd-talentica Jul 25, 2025
5d6ea8e
Updated test cases to improve code coverage
pritishmd-talentica Jul 25, 2025
0525929
Merge pull request #2 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Jul 25, 2025
a31a0af
Merge branch 'prebid:master' into master
pritishmd-talentica Jul 26, 2025
ddbb2d8
Changes as per review comments.
pritishmd-talentica Jul 26, 2025
1da085f
Merge pull request #3 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Jul 26, 2025
efe1f59
Additional PR Review comment handled
pritishmd-talentica Jul 28, 2025
3c2fcaa
Merge pull request #4 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Jul 28, 2025
43073e0
Modified error string as per review comments
pritishmd-talentica Jul 29, 2025
769df42
Merge pull request #5 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Jul 29, 2025
ed0afae
Modifications as per reviewer comments
pritishmd-talentica Aug 6, 2025
555eed6
Merge pull request #6 from Rise-Media-Technologies/rm-pbs-adapter
pritishmd-talentica Aug 6, 2025
e75127a
Merge branch 'prebid:master' into master
pritishmd-talentica Aug 6, 2025
6b46abf
Changes as per review comments
pritishmd-talentica Aug 20, 2025
87651f5
Merge branch 'prebid:master' into master
pritishmd-talentica Aug 20, 2025
4bc1cc8
Merge branch 'prebid:master' into master
pritishmd-talentica Sep 1, 2025
f8b6285
Merge branch 'prebid:master' into master
pritishmd-talentica Sep 9, 2025
92e43bd
Executed go fmt as per review comments
pritishmd-talentica Sep 9, 2025
8a36c57
Handled review comments
pritishmd-talentica Oct 6, 2025
afe46c2
Merge pull request #8 from smart-exchange-ai-digital/pr-review-comments
pritishmd-talentica Oct 10, 2025
fbf1fe8
Merge branch 'master' of https://github.com/prebid/prebid-server
pritishmd-talentica Oct 28, 2025
4cdb024
Handled PR Review comments by reviwer in upstream PR on 27-10-2025
pritishmd-talentica Oct 28, 2025
cb85b99
Merge pull request #9 from smart-exchange-ai-digital/sync_upstream
sushant-dey-talentica Oct 28, 2025
217d60a
Merge pull request #10 from smart-exchange-ai-digital/review/pr_revie…
sushant-dey-talentica Oct 28, 2025
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
50 changes: 50 additions & 0 deletions adapters/risemediatech/params_test.go
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)))
})
}
}
141 changes: 141 additions & 0 deletions adapters/risemediatech/risemediatech.go
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
}

Copy link
Copy Markdown
Contributor

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 bidderName and server parameters are unused. In Go, it's conventional to mark them with _ to signal intent.

Recommendation:

func Builder(_ openrtb_ext.BidderName, cfg config.Adapter, _ config.Server) (adapters.Bidder, error) {

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 *imp creates a copy anyway. Standard iteration is clearer and matches other adapters in the codebase.

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)})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (blocking): Use %w instead of %v for proper error wrapping.

Using %w allows error chaining, making it easier to trace and handle specific error types with errors.Is or errors.As. This is recommended in the Go error handling best practices.

Suggested change
errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("impID %s: %v", imp.ID, err)})
errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("impID %s: %w", imp.ID, err)})

See: https://go.dev/blog/go1.13-errors

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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TestMode Behavior Needs Clarification

The current implementation sets request.Test = 1 if ANY impression has testMode=1. This means a single test impression makes the entire request a test request.

Questions:

  • Is this the intended behavior?
  • What if a request has 5 impressions and only 1 has testMode=1?
  • Should ALL impressions need testMode=1 for the request to be a test?

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 bool

Or 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 mtype, returning nil for the entire BidderResponse discards all previously processed valid bids. This is a data loss issue.

The standard pattern across other adapters (like rise) is to skip the bad bid and continue processing:

Suggested change
bidType, err := getBidType(bid)
if err != nil {
return nil, []error{err}
}
bidType, err := getBidType(bid)
if err != nil {
errs = append(errs, err)
continue
}

Then at the end of MakeBids, return br, errs instead of br, nil. This way valid bids are preserved even when some bids have errors.


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)
}
}
143 changes: 143 additions & 0 deletions adapters/risemediatech/risemediatech_test.go
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)
}
Comment thread
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": []
}
Loading
Loading