Repository: prebid/prebid-server
Pull request #1596 (2020-11-23 13:47:59)

Added app capabilities to VerizonMedia adapter


Patch:
diff --git a/adapters/verizonmedia/verizonmedia.go b/adapters/verizonmedia/verizonmedia.go
index ac881df9..dc8e881a 100644
--- a/adapters/verizonmedia/verizonmedia.go
+++ b/adapters/verizonmedia/verizonmedia.go
@@ -89,8 +89,13 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo
 		reqCopy := *request
 		reqCopy.Imp = []openrtb.Imp{imp}
 
-		siteCopy := *request.Site
-		reqCopy.Site = &siteCopy
+		if request.Site != nil {
+			siteCopy := *request.Site
+			reqCopy.Site = &siteCopy
+		} else if request.App != nil {
+			appCopy := *request.App
+			reqCopy.App = &appCopy
+		}
 
 		if err := changeRequestForBidService(&reqCopy, &verizonMediaExt); err != nil {
 			errors = append(errors, err)
@@ -175,9 +180,13 @@ func getImpInfo(impId string, imps []openrtb.Imp) (bool, openrtb_ext.BidType) {
 }
 
 func changeRequestForBidService(request *openrtb.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error {
-	/* Always override the tag ID and site ID of the request */
+	/* Always override the tag ID and (site ID or app ID) of the request */
 	request.Imp[0].TagID = extension.Pos
-	request.Site.ID = extension.Dcn
+	if request.Site != nil {
+		request.Site.ID = extension.Dcn
+	} else if request.App != nil {
+		request.App.ID = extension.Dcn
+	}
 
 	if request.Imp[0].Banner == nil {
 		return nil
diff --git a/analytics/core.go b/analytics/core.go
index 2cbf98d4..68373045 100644
--- a/analytics/core.go
+++ b/analytics/core.go
@@ -1,6 +1,8 @@
 package analytics
 
 import (
+	"time"
+
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -26,11 +28,12 @@ type PBSAnalyticsModule interface {
 
 //Loggable object of a transaction at /openrtb2/auction endpoint
 type AuctionObject struct {
-	Status   int
-	Errors   []error
-	Request  *openrtb.BidRequest
-	Response *openrtb.BidResponse
-	Account  *config.Account
+	Status    int
+	Errors    []error
+	Request   *openrtb.BidRequest
+	Response  *openrtb.BidResponse
+	Account   *config.Account
+	StartTime time.Time
 }
 
 //Loggable object of a transaction at /openrtb2/amp endpoint
@@ -41,6 +44,7 @@ type AmpObject struct {
 	AuctionResponse    *openrtb.BidResponse
 	AmpTargetingValues map[string]string
 	Origin             string
+	StartTime          time.Time
 }
 
 //Loggable object of a transaction at /openrtb2/video endpoint
@@ -51,6 +55,7 @@ type VideoObject struct {
 	Response      *openrtb.BidResponse
 	VideoRequest  *openrtb_ext.BidRequestVideo
 	VideoResponse *openrtb_ext.BidResponseVideo
+	StartTime     time.Time
 }
 
 //Loggable object of a transaction at /setuid
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 486b5cfc..d82acf9c 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -87,22 +87,22 @@ func NewAmpEndpoint(
 }
 
 func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
-
-	ao := analytics.AmpObject{
-		Status: http.StatusOK,
-		Errors: make([]error, 0),
-	}
-
 	// Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing
 	// to wait for bids. However, tmax may be defined in the Stored Request data.
 	//
 	// If so, then the trip to the backend might use a significant amount of this time.
 	// We can respect timeouts more accurately if we note the *real* start time, and use it
 	// to compute the auction timeout.
+	start := time.Now()
+
+	ao := analytics.AmpObject{
+		Status:    http.StatusOK,
+		Errors:    make([]error, 0),
+		StartTime: start,
+	}
 
 	// Set this as an AMP request in Metrics.
 
-	start := time.Now()
 	labels := pbsmetrics.Labels{
 		Source:        pbsmetrics.DemandWeb,
 		RType:         pbsmetrics.ReqTypeAMP,
@@ -188,6 +188,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		Account:      *account,
 		UserSyncs:    usersyncs,
 		RequestType:  labels.RType,
+		StartTime:    start,
 		LegacyLabels: labels,
 	}
 
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 7d7e362e..ee4f5d1d 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -97,12 +97,6 @@ type endpointDeps struct {
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
-
-	ao := analytics.AuctionObject{
-		Status: http.StatusOK,
-		Errors: make([]error, 0),
-	}
-
 	// Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing
 	// to wait for bids. However, tmax may be defined in the Stored Request data.
 	//
@@ -110,6 +104,13 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	// We can respect timeouts more accurately if we note the *real* start time, and use it
 	// to compute the auction timeout.
 	start := time.Now()
+
+	ao := analytics.AuctionObject{
+		Status:    http.StatusOK,
+		Errors:    make([]error, 0),
+		StartTime: start,
+	}
+
 	labels := pbsmetrics.Labels{
 		Source:        pbsmetrics.DemandUnknown,
 		RType:         pbsmetrics.ReqTypeORTB2Web,
@@ -167,6 +168,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		Account:      *account,
 		UserSyncs:    usersyncs,
 		RequestType:  labels.RType,
+		StartTime:    start,
 		LegacyLabels: labels,
 	}
 
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index d1fcda0a..29866677 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -93,13 +93,14 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal
 11. Build proper response format.
 */
 func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	start := time.Now()
 
 	vo := analytics.VideoObject{
-		Status: http.StatusOK,
-		Errors: make([]error, 0),
+		Status:    http.StatusOK,
+		Errors:    make([]error, 0),
+		StartTime: start,
 	}
 
-	start := time.Now()
 	labels := pbsmetrics.Labels{
 		Source:        pbsmetrics.DemandUnknown,
 		RType:         pbsmetrics.ReqTypeVideo,
@@ -266,6 +267,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		Account:      *account,
 		UserSyncs:    usersyncs,
 		RequestType:  labels.RType,
+		StartTime:    start,
 		LegacyLabels: labels,
 	}
 
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 9056caf8..a2768e06 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -102,6 +102,7 @@ type AuctionRequest struct {
 	Account     config.Account
 	UserSyncs   IdFetcher
 	RequestType pbsmetrics.RequestType
+	StartTime   time.Time
 
 	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
 	// in HoldAuction until we get to factoring it away. Do not use for anything new.
@@ -188,7 +189,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		}
 	}
 
-	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, r.BidRequest, debugInfo, errs)
+	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
 
 	// Ensure caching errors are added in case auc.doCache was called and errors were returned
 	if len(cacheErrs) > 0 {
@@ -714,7 +715,8 @@ func getPrimaryAdServer(adServerId int) (string, error) {
 }
 
 // Extract all the data from the SeatBids and build the ExtBidResponse
-func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, req *openrtb.BidRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse {
+func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse {
+	req := r.BidRequest
 	bidResponseExt := &openrtb_ext.ExtBidResponse{
 		Errors:               make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)),
 		ResponseTimeMillis:   make(map[openrtb_ext.BidderName]int, len(adapterBids)),
@@ -726,6 +728,12 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb
 			ResolvedRequest: req,
 		}
 	}
+	if !r.StartTime.IsZero() {
+		// auctiontimestamp is the only response.ext.prebid attribute we may emit
+		bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{
+			AuctionTimestamp: r.StartTime.UnixNano() / 1e+6,
+		}
+	}
 
 	for bidderName, responseExtra := range adapterExtra {
 
diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go
index fb99e78b..589dad40 100644
--- a/openrtb_ext/response.go
+++ b/openrtb_ext/response.go
@@ -17,6 +17,8 @@ type ExtBidResponse struct {
 	RequestTimeoutMillis int64 `json:"tmaxrequest,omitempty"`
 	// ResponseUserSync defines the contract for bidresponse.ext.usersync
 	Usersync map[BidderName]*ExtResponseSyncData `json:"usersync,omitempty"`
+	// Prebid defines the contract for bidresponse.ext.prebid
+	Prebid *ExtResponsePrebid `json:"prebid,omitempty"`
 }
 
 // ExtResponseDebug defines the contract for bidresponse.ext.debug
@@ -34,6 +36,11 @@ type ExtResponseSyncData struct {
 	Syncs []*ExtUserSync `json:"syncs"`
 }
 
+// ExtResponsePrebid defines the contract for bidresponse.ext.prebid
+type ExtResponsePrebid struct {
+	AuctionTimestamp int64 `json:"auctiontimestamp,omitempty"`
+}
+
 // ExtUserSync defines the contract for bidresponse.ext.usersync.{bidder}.syncs[i]
 type ExtUserSync struct {
 	Url  string       `json:"url"`
diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml
index 024cafad..c00f2158 100644
--- a/static/bidder-info/verizonmedia.yaml
+++ b/static/bidder-info/verizonmedia.yaml
@@ -1,6 +1,9 @@
 maintainer:
   email: "dsp-supply-prebid@verizonmedia.com"
 capabilities:
+  app:
+    mediaTypes:
+      - banner
   site:
     mediaTypes:
       - banner


Test patch:
diff --git a/adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json b/adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json
new file mode 100644
index 00000000..dafa911d
--- /dev/null
+++ b/adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json
@@ -0,0 +1,117 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "tagid": "123",
+        "ext": {
+          "bidder": {
+            "dcn": "dcn1",
+            "pos": "pos2"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+      "ip": "127.0.0.1"
+    },
+    "app": {
+      "id": "test-app-id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ]
+        },
+        "uri": "",
+        "body": {
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
+            "ip": "127.0.0.1"
+          },
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "pos2",
+              "banner": {
+                "format": [{"w": 728, "h": 90}],
+                "w": 728,
+                "h": 90
+              },
+              "ext": {
+                "bidder": {
+                  "dcn": "dcn1",
+                  "pos": "pos2"
+                }
+              }
+            }
+          ],
+          "app": {
+            "id": "dcn1"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "verizonmedia",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index d22acf0f..73fb170b 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -252,6 +252,7 @@ func TestDebugBehaviour(t *testing.T) {
 			BidRequest: bidRequest,
 			Account:    config.Account{},
 			UserSyncs:  &emptyUsersync{},
+			StartTime:  time.Now(),
 		}
 
 		// Run test
@@ -265,6 +266,10 @@ func TestDebugBehaviour(t *testing.T) {
 		err = json.Unmarshal(outBidResponse.Ext, actualExt)
 		assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext)
 
+		assert.NotEmpty(t, actualExt.Prebid, "%s. ext.prebid should not be empty")
+		assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set")
+		assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value")
+
 		if test.out.debugInfoIncluded {
 			assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil.  outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug)