From 4768ba445833419dbf716d1455220534c46d1f7e Mon Sep 17 00:00:00 2001 From: Andre Gielow Date: Thu, 3 Jul 2025 18:25:01 -0400 Subject: [PATCH 1/2] arg-ttd-exception-for-malformed-endpoint --- adapters/thetradedesk/thetradedesk.go | 8 ++++---- adapters/thetradedesk/thetradedesk_test.go | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/adapters/thetradedesk/thetradedesk.go b/adapters/thetradedesk/thetradedesk.go index 47fb9ee0fde..2fdf227d15d 100644 --- a/adapters/thetradedesk/thetradedesk.go +++ b/adapters/thetradedesk/thetradedesk.go @@ -17,8 +17,6 @@ import ( "github.com/prebid/openrtb/v20/openrtb2" ) -//const PREBID_INTEGRATION_TYPE = "1" - type adapter struct { bidderEndpointTemplate string defaultEndpoint string @@ -87,13 +85,12 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E bidderEndpoint, err := a.buildEndpointURL(supplySourceId) if err != nil { - return nil, []error{errors.New("Failed to build endpoint URL")} + return nil, []error{err} } headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") - //headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) this will be parsed and added conditionally later return []*adapters.RequestData{{ Method: "POST", Uri: bidderEndpoint, @@ -105,6 +102,9 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E func (a *adapter) buildEndpointURL(supplySourceId string) (string, error) { if supplySourceId == "" { + if a.defaultEndpoint == "" { + return "", errors.New("Either supplySourceId or a default endpoint must be provided") + } return a.defaultEndpoint, nil } diff --git a/adapters/thetradedesk/thetradedesk_test.go b/adapters/thetradedesk/thetradedesk_test.go index 42166c4b76c..22fdcebeec4 100644 --- a/adapters/thetradedesk/thetradedesk_test.go +++ b/adapters/thetradedesk/thetradedesk_test.go @@ -2,6 +2,7 @@ package thetradedesk import ( "encoding/json" + "errors" "github.com/prebid/prebid-server/v3/adapters/adapterstest" "net/http" "testing" @@ -425,7 +426,7 @@ func TestTheTradeDeskAdapter_BuildEndpoint(t *testing.T) { supplySourceId: "", defaultEndpoint: "", expectedEndpoint: "", - wantErr: nil, + wantErr: []error{errors.New("Either supplySourceId or a default endpoint must be provided")}, }, } @@ -442,6 +443,9 @@ func TestTheTradeDeskAdapter_BuildEndpoint(t *testing.T) { finalEndpoint, err := a.buildEndpointURL(tt.supplySourceId) if tt.wantErr != nil { assert.NotNil(t, err) + assert.Equal(t, tt.wantErr[0].Error(), err.Error()) + } else { + assert.Nil(t, err) } assert.Equal(t, tt.expectedEndpoint, finalEndpoint) }) From 1cfda49f97a78fd1a4b71a2291e5c375783a916e Mon Sep 17 00:00:00 2001 From: Andre Gielow Date: Mon, 21 Jul 2025 16:27:35 -0400 Subject: [PATCH 2/2] arg-resolve-auction-price-macro --- adapters/thetradedesk/thetradedesk.go | 12 ++ adapters/thetradedesk/thetradedesk_test.go | 144 ++++++++++++++++++ .../auction-price-macro-replacement.json | 142 +++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/auction-price-macro-replacement.json diff --git a/adapters/thetradedesk/thetradedesk.go b/adapters/thetradedesk/thetradedesk.go index 2fdf227d15d..f22744ffdc2 100644 --- a/adapters/thetradedesk/thetradedesk.go +++ b/adapters/thetradedesk/thetradedesk.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "regexp" + "strconv" + "strings" "text/template" "github.com/prebid/prebid-server/v3/adapters" @@ -176,6 +178,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest for _, bid := range seatBid.Bid { bid := bid + resolveAuctionPriceMacros(&bid) bidType, err := getBidType(bid.MType) if err != nil { @@ -232,3 +235,12 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co templateEndpoint: template, }, nil } + +func resolveAuctionPriceMacros(bid *openrtb2.Bid) { + if bid == nil { + return + } + price := strconv.FormatFloat(bid.Price, 'f', -1, 64) + bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1) + bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1) +} diff --git a/adapters/thetradedesk/thetradedesk_test.go b/adapters/thetradedesk/thetradedesk_test.go index 22fdcebeec4..11ffda30d9f 100644 --- a/adapters/thetradedesk/thetradedesk_test.go +++ b/adapters/thetradedesk/thetradedesk_test.go @@ -451,3 +451,147 @@ func TestTheTradeDeskAdapter_BuildEndpoint(t *testing.T) { }) } } + +func TestResolveAuctionPriceMacros(t *testing.T) { + tests := []struct { + name string + bid *openrtb2.Bid + expectedNURL string + expectedAdM string + }{ + { + name: "nil_bid", + bid: nil, + }, + { + name: "no_macros", + bid: &openrtb2.Bid{ + Price: 1.23, + NURL: "http://example.com/nurl", + AdM: "
Ad content
", + }, + expectedNURL: "http://example.com/nurl", + expectedAdM: "
Ad content
", + }, + { + name: "macros_in_both_nurl_and_adm", + bid: &openrtb2.Bid{ + Price: 1.50, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Ad content with price ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=1.5", + expectedAdM: "
Ad content with price 1.5
", + }, + { + name: "macro_only_in_nurl", + bid: &openrtb2.Bid{ + Price: 2.75, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Ad content
", + }, + expectedNURL: "http://example.com/nurl?price=2.75", + expectedAdM: "
Ad content
", + }, + { + name: "macro_only_in_adm", + bid: &openrtb2.Bid{ + Price: 0.99, + NURL: "http://example.com/nurl", + AdM: "
Price: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl", + expectedAdM: "
Price: 0.99
", + }, + { + name: "multiple_macros_in_same_field", + bid: &openrtb2.Bid{ + Price: 3.14, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}&backup_price=${AUCTION_PRICE}", + AdM: "
Price: ${AUCTION_PRICE}, Backup: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=3.14&backup_price=3.14", + expectedAdM: "
Price: 3.14, Backup: 3.14
", + }, + { + name: "zero_price", + bid: &openrtb2.Bid{ + Price: 0.0, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Price: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=0", + expectedAdM: "
Price: 0
", + }, + { + name: "very_small_price", + bid: &openrtb2.Bid{ + Price: 0.001, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Price: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=0.001", + expectedAdM: "
Price: 0.001
", + }, + { + name: "large_price", + bid: &openrtb2.Bid{ + Price: 999.999, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Price: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=999.999", + expectedAdM: "
Price: 999.999
", + }, + { + name: "integer_price", + bid: &openrtb2.Bid{ + Price: 5.0, + NURL: "http://example.com/nurl?price=${AUCTION_PRICE}", + AdM: "
Price: ${AUCTION_PRICE}
", + }, + expectedNURL: "http://example.com/nurl?price=5", + expectedAdM: "
Price: 5
", + }, + { + name: "empty_nurl_and_adm", + bid: &openrtb2.Bid{ + Price: 1.23, + NURL: "", + AdM: "", + }, + expectedNURL: "", + expectedAdM: "", + }, + { + name: "case_sensitive_macro_not_replaced", + bid: &openrtb2.Bid{ + Price: 1.50, + NURL: "http://example.com/nurl?price=${auction_price}", + AdM: "
Price: ${Auction_Price}
", + }, + expectedNURL: "http://example.com/nurl?price=${auction_price}", + expectedAdM: "
Price: ${Auction_Price}
", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var testBid *openrtb2.Bid + if tt.bid != nil { + bidCopy := *tt.bid + testBid = &bidCopy + } + + resolveAuctionPriceMacros(testBid) + + if tt.bid == nil { + assert.Nil(t, testBid) + return + } + + assert.Equal(t, tt.expectedNURL, testBid.NURL, "NURL should match expected value") + assert.Equal(t, tt.expectedAdM, testBid.AdM, "AdM should match expected value") + }) + } +} diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/auction-price-macro-replacement.json b/adapters/thetradedesk/thetradedesktest/supplemental/auction-price-macro-replacement.json new file mode 100644 index 00000000000..2629242b010 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/auction-price-macro-replacement.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 150 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 1.50, + "crid": "creative-123", + "adm": "", + "nurl": "http://win-notification.ttd.com?wp=${AUCTION_PRICE}&id=test-slot-id", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 1.50, + "crid": "creative-123", + "adm": "", + "nurl": "http://win-notification.ttd.com?wp=1.5&id=test-slot-id", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file