diff --git a/adapters/risemediatech/params_test.go b/adapters/risemediatech/params_test.go new file mode 100644 index 00000000000..588c6fcfb38 --- /dev/null +++ b/adapters/risemediatech/params_test.go @@ -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))) + }) + } +} diff --git a/adapters/risemediatech/risemediatech.go b/adapters/risemediatech/risemediatech.go new file mode 100644 index 00000000000..93a50f62a5a --- /dev/null +++ b/adapters/risemediatech/risemediatech.go @@ -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 + 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) { + impExt, err := parseImpExt(imp.Ext) + if err != nil { + errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("impID %s: %v", imp.ID, err)}) + 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 + } + + 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} + } + + 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) + } +} diff --git a/adapters/risemediatech/risemediatech_test.go b/adapters/risemediatech/risemediatech_test.go new file mode 100644 index 00000000000..eb4afdfc97f --- /dev/null +++ b/adapters/risemediatech/risemediatech_test.go @@ -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) + } + 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":"
test
","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) + }) + } +} diff --git a/adapters/risemediatech/risemediatechtest/exemplary/banner_bid_floor_set.json b/adapters/risemediatech/risemediatechtest/exemplary/banner_bid_floor_set.json new file mode 100644 index 00000000000..7b1bab68044 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/banner_bid_floor_set.json @@ -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": [] +} \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/exemplary/imp_banner_and_video.json b/adapters/risemediatech/risemediatechtest/exemplary/imp_banner_and_video.json new file mode 100644 index 00000000000..e5ceb557765 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/imp_banner_and_video.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "test-banner-and-video", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "video": { "w": 640, "h": 480, "mimes": ["video/mp4"] }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-banner-and-video", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "video": { "w": 640, "h": 480, "mimes": ["video/mp4"] }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/exemplary/multiple_seatbids_bids.json b/adapters/risemediatech/risemediatechtest/exemplary/multiple_seatbids_bids.json new file mode 100644 index 00000000000..e1dbabfc477 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/multiple_seatbids_bids.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-multiple-seatbids-bids", + "imp": [ + { + "id": "imp-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "abc" + } + } + }, + { + "id": "imp-2", + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": "def" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-multiple-seatbids-bids", + "imp": [ + { + "id": "imp-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "abc" + } + } + }, + { + "id": "imp-2", + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": "def" + } + } + } + ] + }, + "impIDs": [ + "imp-1", + "imp-2" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-multiple-seatbids-bids", + "seatbid": [ + { + "seat": "seat-1", + "bid": [ + { + "id": "bid-1", + "impid": "imp-1", + "price": 1.0, + "adm": "
Banner
", + "mtype": 1, + "w": 300, + "h": 250 + } + ] + }, + { + "seat": "seat-2", + "bid": [ + { + "id": "bid-2", + "impid": "imp-2", + "price": 2.0, + "adm": "", + "mtype": 2, + "w": 640, + "h": 480 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-1", + "impid": "imp-1", + "price": 1.0, + "adm": "
Banner
", + "mtype": 1, + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "bid-2", + "impid": "imp-2", + "price": 2.0, + "adm": "", + "mtype": 2, + "w": 640, + "h": 480 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/exemplary/simple_banner.json b/adapters/risemediatech/risemediatechtest/exemplary/simple_banner.json new file mode 100644 index 00000000000..e541667ac0f --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/simple_banner.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-banner-slot", + "banner": { + "format": [ + { "w": 300, "h": 250 } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "bidfloor": 0.5, + "testMode": 1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test-banner-slot", + "banner": { + "format": [ + { "w": 300, "h": 250 } + ], + "w": 300, + "h": 250 + }, + "bidfloor": 0.5, + "ext": { + "bidder": { + "bidfloor": 0.5, + "testMode": 1 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "test": 1 + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "risemediatech", + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.85, + "adm": "
Sample Banner Ad
", + "crid": "banner-creative-001", + "cid": "banner-campaign-abc", + "dealid": "deal-123", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.85, + "adm": "
Sample Banner Ad
", + "crid": "banner-creative-001", + "cid": "banner-campaign-abc", + "dealid": "deal-123", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/exemplary/simple_video.json b/adapters/risemediatech/risemediatechtest/exemplary/simple_video.json new file mode 100644 index 00000000000..3d0012b9910 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/simple_video.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.risemediatech.videotestapp" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "video-tag", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "bidfloor": 0.75, + "testMode": 1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.risemediatech.videotestapp" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "video-tag", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "bidfloor": 0.75, + "ext": { + "bidder": { + "bidfloor": 0.75, + "testMode": 1 + } + } + } + ], + "test": 1 + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "risemediatech", + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.88, + "adm": "00:00:30", + "cid": "video-campaign-001", + "crid": "video-creative-abc", + "dealid": "deal-999", + "mtype": 2 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.88, + "adm": "00:00:30", + "cid": "video-campaign-001", + "crid": "video-creative-abc", + "dealid": "deal-999", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/exemplary/test_mode_set.json b/adapters/risemediatech/risemediatechtest/exemplary/test_mode_set.json new file mode 100644 index 00000000000..e080642f716 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/exemplary/test_mode_set.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "test-test-mode-set", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "testMode": 1 } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-test-mode-set", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "testMode": 1 } } + } + ], + "test": 1 + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/all_imps_invalid.json b/adapters/risemediatech/risemediatechtest/supplementary/all_imps_invalid.json new file mode 100644 index 00000000000..7b5ba3c379c --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/all_imps_invalid.json @@ -0,0 +1,20 @@ +{ + "mockBidRequest": { + "id": "test-all-imps-invalid", + "imp": [ + { + "id": "1", + "banner": { "h": 250 }, + "ext": { "bidder": { "placementId": "abc" } } + }, + { + "id": "2", + "video": { "w": 640, "h": 0, "mimes": ["video/mp4"] }, + "ext": { "bidder": { "placementId": "def" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "no valid impressions", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/bad_media_type.json b/adapters/risemediatech/risemediatechtest/supplementary/bad_media_type.json new file mode 100644 index 00000000000..9fa9dea72d8 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/bad_media_type.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "bidfloor": 1.0 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "
test
", + "mtype": 9, + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unknown bid type mtype=9", + "comparison": "contains" + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/bad_response.json b/adapters/risemediatech/risemediatechtest/supplementary/bad_response.json new file mode 100644 index 00000000000..c1bbb070ef0 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/bad_response.json @@ -0,0 +1,44 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "bidfloor": 1.0 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": "invalid-json-response" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "invalid character", + "comparison": "contains" + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_negative.json b/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_negative.json new file mode 100644 index 00000000000..ded7b647e95 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_negative.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "test-bidfloor-negative", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "bidFloor": -1 } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-bidfloor-negative", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "bidFloor": -1 } } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_zero.json b/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_zero.json new file mode 100644 index 00000000000..1a889e49435 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/bidfloor_zero.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "test-bidfloor-zero", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "bidFloor": 0 } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-bidfloor-zero", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "bidFloor": 0 } } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/imp_no_banner_no_video.json b/adapters/risemediatech/risemediatechtest/supplementary/imp_no_banner_no_video.json new file mode 100644 index 00000000000..2132e3203a8 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/imp_no_banner_no_video.json @@ -0,0 +1,14 @@ +{ + "mockBidRequest": { + "id": "test-no-banner-no-video", + "imp": [ + { + "id": "1", + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "impID 1: no banner or video object specified", "comparison": "literal" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_missing_width.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_missing_width.json new file mode 100644 index 00000000000..054c0b9e5ac --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_missing_width.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-banner-missing-width", + "imp": [ + { + "id": "1", + "banner": { "h": 250 }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "invalid banner dimensions", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_zero_height.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_zero_height.json new file mode 100644 index 00000000000..a767d712b53 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_banner_zero_height.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-banner-zero-height", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 0 }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "invalid banner dimensions", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_imp_ext.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_imp_ext.json new file mode 100644 index 00000000000..a3651c3d20e --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_imp_ext.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-imp-ext", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": "not-a-valid-json" + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "impID 1: invalid character", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_rise_ext_type.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_rise_ext_type.json new file mode 100644 index 00000000000..bff351a595c --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_rise_ext_type.json @@ -0,0 +1,17 @@ +{ + "mockBidRequest": { + "id": "test-invalid-rise-ext-not-object", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { + "bidder": "not-an-object" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "impID 1:", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_empty_mimes.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_empty_mimes.json new file mode 100644 index 00000000000..5fcbc963fb9 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_empty_mimes.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-video-empty-mimes", + "imp": [ + { + "id": "1", + "video": { "w": 640, "h": 480, "mimes": [] }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "missing or empty video.mimes", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_missing_width.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_missing_width.json new file mode 100644 index 00000000000..beed163e6fe --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_missing_width.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-video-missing-width", + "imp": [ + { + "id": "1", + "video": { "h": 250, "mimes": ["video/mp4"] }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "missing or invalid video width/height", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_zero_height.json b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_zero_height.json new file mode 100644 index 00000000000..aa529466da6 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/invalid_video_zero_height.json @@ -0,0 +1,15 @@ +{ + "mockBidRequest": { + "id": "test-invalid-video-zero-height", + "imp": [ + { + "id": "1", + "video": { "w": 640, "h": 0, "mimes": ["video/mp4"] }, + "ext": { "bidder": { "placementId": "abc" } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { "value": "missing or invalid video width/height", "comparison": "contains" } + ] + } \ No newline at end of file diff --git a/adapters/risemediatech/risemediatechtest/supplementary/no_test_mode.json b/adapters/risemediatech/risemediatechtest/supplementary/no_test_mode.json new file mode 100644 index 00000000000..7134afe8e1a --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/no_test_mode.json @@ -0,0 +1,46 @@ +{ + "mockBidRequest": { + "id": "no-test-mode", + "imp": [ + { + "id": "imp-4", + "tagid": "tag-4", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "bidfloor": 0.8 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "no-test-mode", + "imp": [ + { + "id": "imp-4", + "tagid": "tag-4", + "banner": { + "w": 300, + "h": 250 + }, + "bidfloor": 0.8 + } + ] + }, + "impIDs": ["imp-4"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/no_valid_impressions.json b/adapters/risemediatech/risemediatechtest/supplementary/no_valid_impressions.json new file mode 100644 index 00000000000..f97d33c3e8c --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/no_valid_impressions.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "no valid impressions", + "comparison": "contains" + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/status_204.json b/adapters/risemediatech/risemediatechtest/supplementary/status_204.json new file mode 100644 index 00000000000..699f5a1a403 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/status_204.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "bidfloor": 1.0 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/status_not_200.json b/adapters/risemediatech/risemediatechtest/supplementary/status_not_200.json new file mode 100644 index 00000000000..a7e31d0c231 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/status_not_200.json @@ -0,0 +1,44 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "bidfloor": 1.0 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.risemediatech.sampleapp" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500", + "comparison": "contains" + } + ] +} diff --git a/adapters/risemediatech/risemediatechtest/supplementary/test_mode_zero.json b/adapters/risemediatech/risemediatechtest/supplementary/test_mode_zero.json new file mode 100644 index 00000000000..f908b469589 --- /dev/null +++ b/adapters/risemediatech/risemediatechtest/supplementary/test_mode_zero.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "test-test-mode-zero", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "testMode": 0 } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://dev-ads.risemediatech.com/ads/rtb/prebid/server", + "body": { + "id": "test-test-mode-zero", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "placementId": "abc", "testMode": 0 } } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index e025eb0ed3c..dd1e566e67e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -196,6 +196,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/revcontent" "github.com/prebid/prebid-server/v3/adapters/richaudience" "github.com/prebid/prebid-server/v3/adapters/rise" + "github.com/prebid/prebid-server/v3/adapters/risemediatech" "github.com/prebid/prebid-server/v3/adapters/roulax" "github.com/prebid/prebid-server/v3/adapters/rtbhouse" "github.com/prebid/prebid-server/v3/adapters/rubicon" @@ -464,6 +465,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, + openrtb_ext.BidderRiseMediaTech: risemediatech.Builder, openrtb_ext.BidderRoulax: roulax.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index cdb6dcd2c7c..a7191a7237f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -214,6 +214,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderRevcontent, BidderRichaudience, BidderRise, + BidderRiseMediaTech, BidderRoulax, BidderRTBHouse, BidderRubicon, @@ -586,6 +587,7 @@ const ( BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" + BidderRiseMediaTech BidderName = "risemediatech" BidderRoulax BidderName = "roulax" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" diff --git a/openrtb_ext/imp_risemediatech.go b/openrtb_ext/imp_risemediatech.go new file mode 100644 index 00000000000..faab784c296 --- /dev/null +++ b/openrtb_ext/imp_risemediatech.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpRiseMediaTech struct { + BidFloor float64 `json:"bidfloor,omitempty"` + TestMode int `json:"testMode,omitempty"` +} diff --git a/static/bidder-info/risemediatech.yaml b/static/bidder-info/risemediatech.yaml new file mode 100644 index 00000000000..8c56278578f --- /dev/null +++ b/static/bidder-info/risemediatech.yaml @@ -0,0 +1,14 @@ +enabled: true +endpoint: https://dev-ads.risemediatech.com/ads/rtb/prebid/server +endpointCompression: gzip +maintainer: + email: prebid@risemediatech.io +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/risemediatech.json b/static/bidder-params/risemediatech.json new file mode 100644 index 00000000000..fbbaaf7a693 --- /dev/null +++ b/static/bidder-params/risemediatech.json @@ -0,0 +1,19 @@ +{ + "title": "RiseMediaTech", + "description": "Prebid Server adapter for RiseMediaTech", + "type": "object", + "properties": { + "bidfloor": { + "type": "number", + "minimum": 0, + "description": "Bid Floor Price" + }, + "testMode": { + "type": "integer", + "enum": [0, 1], + "description": "Test Mode parameter to indicate test request" + } + }, + "additionalProperties": false, + "required": [] +}