From 805e7ab40eaf27c0afe4e2cc2f3cc54d63c8c456 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 25 Feb 2026 10:39:04 +0530 Subject: [PATCH 1/2] RM-1477 : Added a new adapter for adsmartx --- adapters/adsmartx/adsmartx.go | 136 +++++++++++++++ adapters/adsmartx/adsmartx_test.go | 164 ++++++++++++++++++ .../exemplary/banner_bid_floor_set.json | 36 ++++ .../exemplary/imp_banner_and_video.json | 108 ++++++++++++ .../exemplary/multiple_seatbids_bids.json | 102 +++++++++++ .../adsmartxtest/exemplary/simple_banner.json | 119 +++++++++++++ .../adsmartxtest/exemplary/simple_video.json | 109 ++++++++++++ .../adsmartxtest/exemplary/test_mode_set.json | 82 +++++++++ .../supplemental/bad_media_type.json | 89 ++++++++++ .../supplemental/bad_response.json | 40 +++++ .../supplemental/imp_no_banner_no_video.json | 21 +++ .../supplemental/invalid_imp_ext.json | 22 +++ .../supplemental/no_valid_impressions.json | 12 ++ .../adsmartxtest/supplemental/status_204.json | 35 ++++ .../supplemental/status_not_200.json | 40 +++++ adapters/adsmartx/params_test.go | 59 +++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adsmartx.go | 8 + static/bidder-info/adsmartx.yaml | 20 +++ static/bidder-params/adsmartx.json | 29 ++++ 21 files changed, 1235 insertions(+) create mode 100644 adapters/adsmartx/adsmartx.go create mode 100644 adapters/adsmartx/adsmartx_test.go create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/banner_bid_floor_set.json create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/imp_banner_and_video.json create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/multiple_seatbids_bids.json create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/simple_banner.json create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/simple_video.json create mode 100644 adapters/adsmartx/adsmartxtest/exemplary/test_mode_set.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/bad_media_type.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/bad_response.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/imp_no_banner_no_video.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/invalid_imp_ext.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/no_valid_impressions.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/status_204.json create mode 100644 adapters/adsmartx/adsmartxtest/supplemental/status_not_200.json create mode 100644 adapters/adsmartx/params_test.go create mode 100644 openrtb_ext/imp_adsmartx.go create mode 100644 static/bidder-info/adsmartx.yaml create mode 100644 static/bidder-params/adsmartx.json diff --git a/adapters/adsmartx/adsmartx.go b/adapters/adsmartx/adsmartx.go new file mode 100644 index 00000000000..1025dc21958 --- /dev/null +++ b/adapters/adsmartx/adsmartx.go @@ -0,0 +1,136 @@ +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/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 { + 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", mtype) + } +} diff --git a/adapters/adsmartx/adsmartx_test.go b/adapters/adsmartx/adsmartx_test.go new file mode 100644 index 00000000000..3896a0801dd --- /dev/null +++ b/adapters/adsmartx/adsmartx_test.go @@ -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": "
ad
", "mtype": 1}, + {"id": "bad-bid", "impid": "2", "price": 2.0, "adm": "
ad2
", "mtype": 99}, + {"id": "good-bid-2", "impid": "3", "price": 3.0, "adm": "
ad3
", "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") +} diff --git a/adapters/adsmartx/adsmartxtest/exemplary/banner_bid_floor_set.json b/adapters/adsmartx/adsmartxtest/exemplary/banner_bid_floor_set.json new file mode 100644 index 00000000000..f70dd8ec0ba --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/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": { "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": [] +} diff --git a/adapters/adsmartx/adsmartxtest/exemplary/imp_banner_and_video.json b/adapters/adsmartx/adsmartxtest/exemplary/imp_banner_and_video.json new file mode 100644 index 00000000000..4025a048e56 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/exemplary/imp_banner_and_video.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "mixed-media-request", + "imp": [ + { + "id": "banner-imp", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": { "bidfloor": 0.5 } } + }, + { + "id": "video-imp", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "ext": { "bidder": { "bidfloor": 2.0 } } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "mixed-media-request", + "imp": [ + { + "id": "banner-imp", + "banner": { "w": 300, "h": 250 }, + "bidfloor": 0.5, + "ext": { "bidder": { "bidfloor": 0.5 } } + }, + { + "id": "video-imp", + "video": { "mimes": ["video/mp4"], "w": 640, "h": 480 }, + "bidfloor": 2.0, + "ext": { "bidder": { "bidfloor": 2.0 } } + } + ] + }, + "impIDs": ["banner-imp", "video-imp"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "mixed-media-request", + "seatbid": [ + { + "bid": [ + { + "id": "b1", + "impid": "banner-imp", + "price": 1.0, + "adm": "
banner
", + "crid": "cr1", + "w": 300, + "h": 250, + "mtype": 1 + }, + { + "id": "b2", + "impid": "video-imp", + "price": 5.0, + "adm": "", + "crid": "cr2", + "w": 640, + "h": 480, + "mtype": 2 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "b1", + "impid": "banner-imp", + "price": 1.0, + "adm": "
banner
", + "crid": "cr1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "b2", + "impid": "video-imp", + "price": 5.0, + "adm": "", + "crid": "cr2", + "w": 640, + "h": 480, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/exemplary/multiple_seatbids_bids.json b/adapters/adsmartx/adsmartxtest/exemplary/multiple_seatbids_bids.json new file mode 100644 index 00000000000..036e992d984 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/exemplary/multiple_seatbids_bids.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "multi-seat-request", + "imp": [ + { + "id": "imp-1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "multi-seat-request", + "imp": [ + { + "id": "imp-1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "impIDs": ["imp-1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "multi-seat-request", + "seatbid": [ + { + "seat": "seat-a", + "bid": [ + { + "id": "bid-a1", + "impid": "imp-1", + "price": 1.5, + "adm": "
Ad A
", + "crid": "cr-a1", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + }, + { + "seat": "seat-b", + "bid": [ + { + "id": "bid-b1", + "impid": "imp-1", + "price": 2.0, + "adm": "
Ad B
", + "crid": "cr-b1", + "w": 300, + "h": 250, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-a1", + "impid": "imp-1", + "price": 1.5, + "adm": "
Ad A
", + "crid": "cr-a1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "bid-b1", + "impid": "imp-1", + "price": 2.0, + "adm": "
Ad B
", + "crid": "cr-b1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/exemplary/simple_banner.json b/adapters/adsmartx/adsmartxtest/exemplary/simple_banner.json new file mode 100644 index 00000000000..732eb4f240d --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/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.adsmartx.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, + "sspId": "ssp-123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.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, + "sspId": "ssp-123" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.adsmartx.sampleapp" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adsmartx", + "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": [ + { + "currency": "USD", + "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/adsmartx/adsmartxtest/exemplary/simple_video.json b/adapters/adsmartx/adsmartxtest/exemplary/simple_video.json new file mode 100644 index 00000000000..38795dda174 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/exemplary/simple_video.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-video-request", + "device": { + "ip": "10.0.0.1", + "ua": "Mozilla/5.0" + }, + "site": { + "page": "https://example.com" + }, + "imp": [ + { + "id": "video-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [1, 2], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "bidfloor": 1.0, + "siteId": "site-456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "test-video-request", + "imp": [ + { + "id": "video-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [1, 2], + "w": 640, + "h": 480 + }, + "bidfloor": 1.0, + "ext": { + "bidder": { + "bidfloor": 1.0, + "siteId": "site-456" + } + } + } + ], + "site": { + "page": "https://example.com" + }, + "device": { + "ip": "10.0.0.1", + "ua": "Mozilla/5.0" + } + }, + "impIDs": ["video-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-video-request", + "seatbid": [ + { + "seat": "adsmartx", + "bid": [ + { + "id": "video-bid-id", + "impid": "video-imp-id", + "price": 5.0, + "adm": "", + "crid": "video-creative-001", + "w": 640, + "h": 480, + "mtype": 2 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video-bid-id", + "impid": "video-imp-id", + "price": 5.0, + "adm": "", + "crid": "video-creative-001", + "w": 640, + "h": 480, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/exemplary/test_mode_set.json b/adapters/adsmartx/adsmartxtest/exemplary/test_mode_set.json new file mode 100644 index 00000000000..232f383a42e --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/exemplary/test_mode_set.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-mode-request", + "imp": [ + { + "id": "1", + "banner": { "w": 728, "h": 90 }, + "ext": { + "bidder": { + "testMode": 1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "test-mode-request", + "imp": [ + { + "id": "1", + "banner": { "w": 728, "h": 90 }, + "ext": { + "bidder": { + "testMode": 1 + } + } + } + ], + "test": 1 + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-mode-request", + "seatbid": [ + { + "bid": [ + { + "id": "b1", + "impid": "1", + "price": 1.0, + "adm": "
test
", + "crid": "cr1", + "w": 728, + "h": 90, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "b1", + "impid": "1", + "price": 1.0, + "adm": "
test
", + "crid": "cr1", + "w": 728, + "h": 90, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/bad_media_type.json b/adapters/adsmartx/adsmartxtest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..f5f29b09bdb --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/bad_media_type.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "bad-mtype-request", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "bad-mtype-request", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "bad-mtype-request", + "seatbid": [ + { + "bid": [ + { + "id": "good-bid", + "impid": "1", + "price": 1.0, + "adm": "
good
", + "crid": "cr1", + "w": 300, + "h": 250, + "mtype": 1 + }, + { + "id": "bad-bid", + "impid": "1", + "price": 2.0, + "adm": "
bad
", + "crid": "cr2", + "w": 300, + "h": 250, + "mtype": 99 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "good-bid", + "impid": "1", + "price": 1.0, + "adm": "
good
", + "crid": "cr1", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unknown bid type mtype=99", + "comparison": "literal" + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/bad_response.json b/adapters/adsmartx/adsmartxtest/supplemental/bad_response.json new file mode 100644 index 00000000000..d4a208f4868 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/bad_response.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "bad-response-request", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "bad-response-request", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 200, + "body": "not-json" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "expect \\{ or n, but found", + "comparison": "regex" + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/imp_no_banner_no_video.json b/adapters/adsmartx/adsmartxtest/supplemental/imp_no_banner_no_video.json new file mode 100644 index 00000000000..df4cd1b8bf2 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/imp_no_banner_no_video.json @@ -0,0 +1,21 @@ +{ + "mockBidRequest": { + "id": "no-banner-video", + "imp": [ + { + "id": "1", + "ext": { "bidder": { "bidfloor": 0.5 } } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "impID 1: no banner or video object specified", + "comparison": "literal" + }, + { + "value": "no valid impressions", + "comparison": "literal" + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/invalid_imp_ext.json b/adapters/adsmartx/adsmartxtest/supplemental/invalid_imp_ext.json new file mode 100644 index 00000000000..53d06f24dc7 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/invalid_imp_ext.json @@ -0,0 +1,22 @@ +{ + "mockBidRequest": { + "id": "invalid-ext", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": "not-json" + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "impID 1:", + "comparison": "regex" + }, + { + "value": "no valid impressions", + "comparison": "literal" + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/no_valid_impressions.json b/adapters/adsmartx/adsmartxtest/supplemental/no_valid_impressions.json new file mode 100644 index 00000000000..3b9d228728f --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/no_valid_impressions.json @@ -0,0 +1,12 @@ +{ + "mockBidRequest": { + "id": "no-valid-imps", + "imp": [] + }, + "expectedMakeRequestsErrors": [ + { + "value": "no valid impressions", + "comparison": "literal" + } + ] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/status_204.json b/adapters/adsmartx/adsmartxtest/supplemental/status_204.json new file mode 100644 index 00000000000..cf64b41e026 --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/status_204.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "status-204", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "status-204", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adsmartx/adsmartxtest/supplemental/status_not_200.json b/adapters/adsmartx/adsmartxtest/supplemental/status_not_200.json new file mode 100644 index 00000000000..41eb8d2715f --- /dev/null +++ b/adapters/adsmartx/adsmartxtest/supplemental/status_not_200.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "status-500", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.adsmartx.com/ads/rtb/prebid/server", + "body": { + "id": "status-500", + "imp": [ + { + "id": "1", + "banner": { "w": 300, "h": 250 }, + "ext": { "bidder": {} } + } + ] + }, + "impIDs": ["1"] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500", + "comparison": "regex" + } + ] +} diff --git a/adapters/adsmartx/params_test.go b/adapters/adsmartx/params_test.go new file mode 100644 index 00000000000..335c6893286 --- /dev/null +++ b/adapters/adsmartx/params_test.go @@ -0,0 +1,59 @@ +package adsmartx + +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}`}, + {"Valid sspId only", `{"sspId": "ssp-123"}`}, + {"Valid siteId only", `{"siteId": "site-456"}`}, + {"Valid all params", `{"bidfloor": 1.0, "testMode": 0, "sspId": "ssp-123", "siteId": "site-456"}`}, + {"Empty object", `{}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.NoError(t, validator.Validate(openrtb_ext.BidderAdsmartx, 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}`}, + {"Invalid sspId type", `{"sspId": 123}`}, + {"Invalid siteId type", `{"siteId": 456}`}, + {"Empty sspId", `{"sspId": ""}`}, + {"Empty siteId", `{"siteId": ""}`}, + {"Unknown property", `{"unknownParam": "value"}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Error(t, validator.Validate(openrtb_ext.BidderAdsmartx, json.RawMessage(tt.input))) + }) + } +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index e6bba140a57..b23e6b3380f 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -24,6 +24,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/adprime" "github.com/prebid/prebid-server/v3/adapters/adquery" "github.com/prebid/prebid-server/v3/adapters/adrino" + "github.com/prebid/prebid-server/v3/adapters/adsmartx" "github.com/prebid/prebid-server/v3/adapters/ads_interactive" "github.com/prebid/prebid-server/v3/adapters/adsinteractive" "github.com/prebid/prebid-server/v3/adapters/adtarget" @@ -294,6 +295,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdprime: adprime.Builder, openrtb_ext.BidderAdquery: adquery.Builder, openrtb_ext.BidderAdrino: adrino.Builder, + openrtb_ext.BidderAdsmartx: adsmartx.Builder, openrtb_ext.BidderAdsInteractive: ads_interactive.Builder, openrtb_ext.BidderAdsinteractive: adsinteractive.Builder, openrtb_ext.BidderAdtarget: adtarget.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 82a6df4f77c..f97811f85f8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -40,6 +40,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderAdprime, BidderAdquery, BidderAdrino, + BidderAdsmartx, BidderAdsInteractive, BidderAdsinteractive, BidderAdtarget, @@ -417,6 +418,7 @@ const ( BidderAdprime BidderName = "adprime" BidderAdquery BidderName = "adquery" BidderAdrino BidderName = "adrino" + BidderAdsmartx BidderName = "adsmartx" BidderAdsInteractive BidderName = "ads_interactive" BidderAdsinteractive BidderName = "adsinteractive" BidderAdtarget BidderName = "adtarget" diff --git a/openrtb_ext/imp_adsmartx.go b/openrtb_ext/imp_adsmartx.go new file mode 100644 index 00000000000..64b915a4bcf --- /dev/null +++ b/openrtb_ext/imp_adsmartx.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ImpExtAdsmartx struct { + BidFloor float64 `json:"bidfloor,omitempty"` + TestMode int `json:"testMode,omitempty"` + SspID string `json:"sspId,omitempty"` + SspSiteID string `json:"siteId,omitempty"` +} diff --git a/static/bidder-info/adsmartx.yaml b/static/bidder-info/adsmartx.yaml new file mode 100644 index 00000000000..dbd08216831 --- /dev/null +++ b/static/bidder-info/adsmartx.yaml @@ -0,0 +1,20 @@ +endpoint: "https://ads.adsmartx.com/ads/rtb/prebid/server" +endpointCompression: gzip +maintainer: + email: prebid@aidigital.com +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: "https://ads.adsmartx.com/sync?ssp_site_id=49964415&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&iframe_enabled=true&redir={{.RedirectURL}}" + userMacro: "[UID]" + redirect: + url: "https://ads.adsmartx.com/sync?ssp_site_id=49964415&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&iframe_enabled=false&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-params/adsmartx.json b/static/bidder-params/adsmartx.json new file mode 100644 index 00000000000..ae53cd997da --- /dev/null +++ b/static/bidder-params/adsmartx.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adsmartx Adapter Params", + "description": "A schema which validates params accepted by the Adsmartx adapter", + "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" + }, + "sspId": { + "type": "string", + "minLength": 1, + "description": "SSP ID for the publisher, used for user sync and request routing" + }, + "siteId": { + "type": "string", + "minLength": 1, + "description": "SSP Site ID for the publisher's site, used for user sync and request routing" + } + }, + "additionalProperties": false +} From 5cfb1d2853ba94947a1a4e4fbda83c910af339dc Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 25 Feb 2026 11:51:58 +0530 Subject: [PATCH 2/2] RM-1477 : Copilot review handled --- adapters/adsmartx/adsmartx.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adapters/adsmartx/adsmartx.go b/adapters/adsmartx/adsmartx.go index 1025dc21958..fdad895b91f 100644 --- a/adapters/adsmartx/adsmartx.go +++ b/adapters/adsmartx/adsmartx.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -131,6 +132,8 @@ func getBidType(mtype openrtb2.MarkupType) (openrtb_ext.BidType, error) { case openrtb2.MarkupVideo: return openrtb_ext.BidTypeVideo, nil default: - return "", fmt.Errorf("unknown bid type mtype=%d", mtype) + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("unknown bid type mtype=%d", mtype), + } } }