From 947d114baf5c674e267c9d35eeea0b2227a83d37 Mon Sep 17 00:00:00 2001 From: Jaydeep Mohite <30924180+pm-jaydeep-mohite@users.noreply.github.com> Date: Mon, 6 May 2024 11:19:27 +0530 Subject: [PATCH 1/6] OTT-1248: Use Updated floor value derived from floor rules into logger in case of No bid case (#776) --- analytics/pubmatic/logger.go | 15 +++ analytics/pubmatic/logger_test.go | 203 ++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/analytics/pubmatic/logger.go b/analytics/pubmatic/logger.go index 67fb1562265..c0a1f577c77 100644 --- a/analytics/pubmatic/logger.go +++ b/analytics/pubmatic/logger.go @@ -35,6 +35,8 @@ func GetLogAuctionObjectAsURL(ao analytics.AuctionObject, rCtx *models.RequestCt if ao.RequestWrapper == nil || ao.RequestWrapper.BidRequest == nil || rCtx == nil || rCtx.PubID == 0 || rCtx.LoggerDisabled { return "", nil } + // Get Updated Floor values using floor rules from updated request + getFloorValueFromUpdatedRequest(ao.RequestWrapper, rCtx) wlog := WloggerRecord{ record: record{ @@ -191,6 +193,19 @@ func GetRequestCtx(hookExecutionOutcome []hookexecution.StageOutcome) *models.Re return nil } +// getFloorValueFromUpdatedRequest gets updated floor values by floor module +func getFloorValueFromUpdatedRequest(reqWrapper *openrtb_ext.RequestWrapper, rCtx *models.RequestCtx) { + for _, imp := range reqWrapper.BidRequest.Imp { + if impCtx, ok := rCtx.ImpBidCtx[imp.ID]; ok { + if imp.BidFloor > 0 && impCtx.BidFloor != imp.BidFloor { + impCtx.BidFloor = imp.BidFloor + impCtx.BidFloorCur = imp.BidFloorCur + rCtx.ImpBidCtx[imp.ID] = impCtx + } + } + } +} + func convertNonBidToBidWrapper(nonBid *openrtb_ext.NonBid) (bid bidWrapper) { bid.Bid = &openrtb2.Bid{ Price: nonBid.Ext.Prebid.Bid.Price, diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index 82328929f36..f4b935a1147 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -4192,6 +4192,79 @@ func TestGetLogAuctionObjectAsURLForFloorDetailsAndCDS(t *testing.T) { }, }, }, + { + name: "set floor value from updated impression if tracker details are absent", + args: args{ + ao: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp-1", + BidFloor: 10.10, + BidFloorCur: "USD", + }, + }, + }, + }, + Response: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: []openrtb2.Bid{ + { + ID: "bid-id-1", + ImpID: "imp-1", + }, + }, + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + NewReqExt: &models.RequestExt{ + ExtRequest: openrtb_ext.ExtRequest{}, + }, + ImpBidCtx: map[string]models.ImpCtx{ + "imp-1": { + AdUnitName: "au", + SlotName: "sn", + BidFloor: 2.0, + BidFloorCur: "USD", + }, + }, + ResponseExt: openrtb_ext.ExtBidResponse{ + Prebid: &openrtb_ext.ExtResponsePrebid{ + Floors: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchError, + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model-version", + }, + }, + FloorProvider: "provider1", + }, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: ptrutil.ToPtr(true), + }, + }, + }, + }, + }, + logInfo: false, + forRespExt: true, + }, + want: want{ + logger: ow.cfg.Endpoint + `?json={"pubid":5890,"pid":"0","pdvid":"0","sl":1,"s":[{"sid":"uuid","sn":"sn","au":"au","ps":[{"pn":"pubmatic","bc":"pubmatic","kgpv":"","kgpsv":"","psz":"0x0","af":"","eg":0,"en":0,"l1":0,"l2":0,"t":0,"wb":0,"bidid":"bid-id-1","origbidid":"bid-id-1","di":"-1","dc":"","db":0,"ss":1,"mi":0,"ocpm":0,"ocry":"USD","fv":10.1,"frv":10.1}]}],"dvc":{},"fmv":"model-version","fsrc":2,"ft":1,"ffs":2,"fp":"provider1"}&pubid=5890`, + header: http.Header{ + models.USER_AGENT_HEADER: []string{""}, + models.IP_HEADER: []string{""}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -4425,3 +4498,133 @@ func TestSlotRecordsInGetLogAuctionObjectAsURL(t *testing.T) { }) } } + +func Test_getFloorValueFromUpdatedRequest(t *testing.T) { + type args struct { + reqWrapper *openrtb_ext.RequestWrapper + rCtx *models.RequestCtx + } + tests := []struct { + name string + args args + want *models.RequestCtx + }{ + { + name: "No floor present in request and in rctx", + args: args{ + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + }, + }, + }, + }, + want: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + }, + }, + }, + }, + { + name: "No floor change in request and in rctx", + args: args{ + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + BidFloor: 10, + BidFloorCur: "USD", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + BidFloor: 10, + BidFloorCur: "USD", + }, + }, + }, + }, + want: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + BidFloor: 10, + BidFloorCur: "USD", + }, + }, + }, + }, + { + name: "floor updated in request", + args: args{ + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + TagID: "tagid_1", + BidFloor: 20, + BidFloorCur: "EUR", + }, + }, + }, + }, + rCtx: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + BidFloor: 10, + BidFloorCur: "USD", + }, + }, + }, + }, + want: &models.RequestCtx{ + PubID: 5890, + Endpoint: models.EndpointV25, + ImpBidCtx: map[string]models.ImpCtx{ + "imp_1": { + AdUnitName: "tagid_1", + BidFloor: 20, + BidFloorCur: "EUR", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + getFloorValueFromUpdatedRequest(tt.args.reqWrapper, tt.args.rCtx) + assert.Equal(t, tt.want, tt.args.rCtx, tt.name) + }) + } +} From 154195e92425312d28c5888660a55b97e44cae62 Mon Sep 17 00:00:00 2001 From: Ankit-Pinge <128145896+Ankit-Pinge@users.noreply.github.com> Date: Tue, 7 May 2024 11:01:20 +0530 Subject: [PATCH 2/6] OTT-1697: Fix- Remove httpcounter stat (#774) --- metrics/config/metrics_ow.go | 5 - metrics/go_metrics_ow.go | 3 - metrics/metrics_mock.go | 3 - metrics/metrics_ow.go | 1 - metrics/prometheus/prometheus_ow.go | 15 --- .../openwrap/metrics/config/multimetrics.go | 6 - .../metrics/config/multimetrics_test.go | 2 - modules/pubmatic/openwrap/metrics/metrics.go | 1 - .../pubmatic/openwrap/metrics/mock/mock.go | 12 -- .../openwrap/metrics/prometheus/prometheus.go | 13 +-- .../metrics/prometheus/prometheus_test.go | 10 -- .../openwrap/metrics/stats/tcp_stats.go | 1 - .../pubmatic/openwrap/prometheus_handler.go | 56 ++++++++++ .../openwrap/prometheus_handler_test.go | 104 +++++++++++++++++- server/prometheus_ow.go | 33 ------ server/server.go | 3 +- 16 files changed, 158 insertions(+), 110 deletions(-) create mode 100644 modules/pubmatic/openwrap/prometheus_handler.go rename server/prometheus_ow_test.go => modules/pubmatic/openwrap/prometheus_handler_test.go (63%) delete mode 100644 server/prometheus_ow.go diff --git a/metrics/config/metrics_ow.go b/metrics/config/metrics_ow.go index 62a950438cd..cc9b8a88cfc 100644 --- a/metrics/config/metrics_ow.go +++ b/metrics/config/metrics_ow.go @@ -39,8 +39,6 @@ func (me *MultiMetricsEngine) RecordBids(pubid, profileid, biddder, deal string) thisME.RecordBids(pubid, profileid, biddder, deal) } } -func (me *MultiMetricsEngine) RecordHttpCounter() { -} func (me *MultiMetricsEngine) RecordVastVersion(biddder, vastVersion string) { for _, thisME := range *me { @@ -82,9 +80,6 @@ func (me *NilMetricsEngine) RecordBids(pubid, profileid, biddder, deal string) { func (me *NilMetricsEngine) RecordVastVersion(biddder, vastVersion string) { } -func (m *NilMetricsEngine) RecordHttpCounter() { -} - // RecordRejectedBidsForBidder as a noop func (me *NilMetricsEngine) RecordRejectedBidsForBidder(bidder openrtb_ext.BidderName) { } diff --git a/metrics/go_metrics_ow.go b/metrics/go_metrics_ow.go index e03a402ee7a..d3c04a7f2f2 100644 --- a/metrics/go_metrics_ow.go +++ b/metrics/go_metrics_ow.go @@ -38,9 +38,6 @@ func (me *Metrics) RecordBids(pubid, profileid, biddder, deal string) { func (me *Metrics) RecordVastVersion(biddder, vastVersion string) { } -func (me *Metrics) RecordHttpCounter() { -} - // RecordVASTTagType as a noop func (me *Metrics) RecordVASTTagType(biddder, vastTag string) { } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 07e09db5f0a..836031839e2 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -241,6 +241,3 @@ func (me *MetricsEngineMock) RecordRejectedBids(pubid, bidder, code string) { func (me *MetricsEngineMock) RecordDynamicFetchFailure(pubId, code string) { me.Called(pubId, code) } - -func (me *MetricsEngineMock) RecordHttpCounter() { -} diff --git a/metrics/metrics_ow.go b/metrics/metrics_ow.go index 958394dd03d..2daa9d086df 100644 --- a/metrics/metrics_ow.go +++ b/metrics/metrics_ow.go @@ -1,7 +1,6 @@ package metrics type OWMetricsEngine interface { - RecordHttpCounter() //RecordBids records the bidder deal bids labeled by pubid, profile, bidder and deal RecordBids(pubid, profileid, bidder, deal string) //RecordVastVersion record the count of vast version labelled by bidder and vast version diff --git a/metrics/prometheus/prometheus_ow.go b/metrics/prometheus/prometheus_ow.go index 789788a93ce..bf4a3ee681e 100644 --- a/metrics/prometheus/prometheus_ow.go +++ b/metrics/prometheus/prometheus_ow.go @@ -45,16 +45,6 @@ type OWMetrics struct { // podCompExclTimer indicates time taken by compititve exclusion // algorithm to generate final pod response based on bid response and ad pod request podCompExclTimer *prometheus.HistogramVec - httpCounter prometheus.Counter -} - -func newHttpCounter(cfg config.PrometheusMetrics, registry *prometheus.Registry) prometheus.Counter { - httpCounter := prometheus.NewCounter(prometheus.CounterOpts{ - Name: "http_requests_total", - Help: "Number of http requests.", - }) - registry.MustRegister(httpCounter) - return httpCounter } // RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor @@ -179,12 +169,7 @@ func (m *Metrics) RecordDynamicFetchFailure(pubId, code string) { } } -func (m *Metrics) RecordHttpCounter() { - m.httpCounter.Inc() -} - func (m *OWMetrics) init(cfg config.PrometheusMetrics, reg *prometheus.Registry) { - m.httpCounter = newHttpCounter(cfg, reg) m.rejectedBids = newCounter(cfg, reg, "rejected_bids", "Count of rejected bids by publisher id, bidder and rejection reason code", diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics.go b/modules/pubmatic/openwrap/metrics/config/multimetrics.go index a8ae0176a37..fe6788e28c1 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics.go @@ -460,12 +460,6 @@ func (me *MultiMetricsEngine) RecordAmpVideoResponses(pubid, profileid string) { } } -func (me *MultiMetricsEngine) RecordHTTPCounter() { - for _, thisME := range *me { - thisME.RecordHTTPCounter() - } -} - // RecordUnwrapRequestStatus record VAST unwrap status func (me *MultiMetricsEngine) RecordUnwrapRequestStatus(accountId, bidder, status string) { for _, thisME := range *me { diff --git a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go index 81aea23ae64..7a677de5c41 100644 --- a/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go +++ b/modules/pubmatic/openwrap/metrics/config/multimetrics_test.go @@ -220,7 +220,6 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { mockEngine.EXPECT().RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") mockEngine.EXPECT().RecordAmpVideoRequests("pubid", "profileid") mockEngine.EXPECT().RecordAmpVideoResponses("pubid", "profileid") - mockEngine.EXPECT().RecordHTTPCounter() // create the multi-metric engine multiMetricEngine := MultiMetricsEngine{} @@ -286,5 +285,4 @@ func TestRecordFunctionForMultiMetricsEngine(t *testing.T) { multiMetricEngine.RecordOWServerPanic("endpoint", "methodName", "nodeName", "podName") multiMetricEngine.RecordAmpVideoRequests("pubid", "profileid") multiMetricEngine.RecordAmpVideoResponses("pubid", "profileid") - multiMetricEngine.RecordHTTPCounter() } diff --git a/modules/pubmatic/openwrap/metrics/metrics.go b/modules/pubmatic/openwrap/metrics/metrics.go index b6a96ca8335..24c75136846 100644 --- a/modules/pubmatic/openwrap/metrics/metrics.go +++ b/modules/pubmatic/openwrap/metrics/metrics.go @@ -25,7 +25,6 @@ type MetricsEngine interface { RecordPublisherRequests(endpoint string, publisher string, platform string) RecordReqImpsWithContentCount(publisher, contentType string) RecordInjectTrackerErrorCount(adformat, publisher, partner string) - RecordHTTPCounter() // not-captured in openwrap module, dont provide enough insights RecordPBSAuctionRequestsStats() diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go index 270381b7abc..c80cb38b990 100644 --- a/modules/pubmatic/openwrap/metrics/mock/mock.go +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -263,18 +263,6 @@ func (mr *MockMetricsEngineMockRecorder) RecordGetProfileDataTime(arg0 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordGetProfileDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordGetProfileDataTime), arg0) } -// RecordHTTPCounter mocks base method. -func (m *MockMetricsEngine) RecordHTTPCounter() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RecordHTTPCounter") -} - -// RecordHTTPCounter indicates an expected call of RecordHTTPCounter. -func (mr *MockMetricsEngineMockRecorder) RecordHTTPCounter() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordHTTPCounter", reflect.TypeOf((*MockMetricsEngine)(nil).RecordHTTPCounter)) -} - // RecordImpDisabledViaConfigStats mocks base method. func (m *MockMetricsEngine) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go index dc6cb8cd614..4e68a07102e 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus.go @@ -13,8 +13,7 @@ import ( type Metrics struct { // general metrics - panics *prometheus.CounterVec - httpCounter *prometheus.CounterVec + panics *prometheus.CounterVec // publisher-partner level metrics pubPartnerNoCookie *prometheus.CounterVec @@ -119,12 +118,6 @@ func newMetrics(cfg *config.PrometheusMetrics, promRegistry *prometheus.Registry []string{hostLabel, methodLabel}, ) - metrics.httpCounter = newCounter(cfg, promRegistry, - "total_http_request", - "Count of total http requests", - []string{}, - ) - // publisher-partner level metrics // TODO : check description of this metrics.pubPartnerNoCookie = newCounter(cfg, promRegistry, @@ -498,10 +491,6 @@ func (m *Metrics) RecordPublisherWrapperLoggerFailure(publisher, profile, versio }).Inc() } -func (m *Metrics) RecordHTTPCounter() { - m.httpCounter.With(nil).Inc() -} - // RecordAnalyticsTrackingThrottled record analytics throttling at publisher profile level func (m *Metrics) RecordAnalyticsTrackingThrottled(pubid, profileid, analyticsType string) { m.analyticsThrottle.With(prometheus.Labels{ diff --git a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go index 9d8abf28cec..59e81b9c010 100644 --- a/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go +++ b/modules/pubmatic/openwrap/metrics/prometheus/prometheus_test.go @@ -325,16 +325,6 @@ func TestRecordDBQueryFailure(t *testing.T) { }) } -func TestRecordHTTPCounter(t *testing.T) { - m := createMetricsForTesting() - - m.RecordHTTPCounter() - - expectedCount := float64(1) - assertCounterVecValue(t, "", "httpCounter", m.httpCounter, - expectedCount, nil) -} - func getHistogramFromHistogram(histogram prometheus.Histogram) dto.Histogram { var result dto.Histogram processMetrics(histogram, func(m dto.Metric) { diff --git a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go index e15fb26bf17..0003f7f8c07 100644 --- a/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go +++ b/modules/pubmatic/openwrap/metrics/stats/tcp_stats.go @@ -341,7 +341,6 @@ func (st *StatsTCP) RecordRequestTime(requestType string, requestTime time.Durat func (st *StatsTCP) RecordOWServerPanic(endpoint, methodName, nodeName, podName string) {} func (st *StatsTCP) RecordAmpVideoRequests(pubid, profileid string) {} func (st *StatsTCP) RecordAmpVideoResponses(pubid, profileid string) {} -func (st *StatsTCP) RecordHTTPCounter() {} func (st *StatsTCP) RecordUnwrapRequestStatus(accountId, bidder, status string) {} func (st *StatsTCP) RecordUnwrapWrapperCount(accountId, bidder, wrapper_count string) {} func (st *StatsTCP) RecordUnwrapRequestTime(accountId, bidder string, respTime time.Duration) {} diff --git a/modules/pubmatic/openwrap/prometheus_handler.go b/modules/pubmatic/openwrap/prometheus_handler.go new file mode 100644 index 00000000000..4eba5bc8af1 --- /dev/null +++ b/modules/pubmatic/openwrap/prometheus_handler.go @@ -0,0 +1,56 @@ +package openwrap + +import ( + "net/http" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + + ow_metrics_prometheus "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/prometheus" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type loggerForPrometheus struct{} + +func (loggerForPrometheus) Println(v ...interface{}) { + glog.Warningln(v...) +} + +func PrometheusHandler(reg prometheus.Gatherer, timeDur time.Duration, endpoint string) http.Handler { + handler := promhttp.HandlerFor( + reg, + promhttp.HandlerOpts{ + ErrorLog: loggerForPrometheus{}, + MaxRequestsInFlight: 5, + Timeout: timeDur, + }, + ) + return http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { + ow.metricEngine.RecordRequest( + metrics.Labels{ + RType: metrics.RequestType(endpoint), + RequestStatus: ow_metrics_prometheus.RequestStatusOK, + }, + ) + handler.ServeHTTP(rsp, req) + }) +} + +func initPrometheusStatsEndpoint(cfg *config.Configuration, gatherer *prometheus.Registry, promMux *http.ServeMux) { + promMux.Handle("/stats", PrometheusHandler(gatherer, cfg.Metrics.Prometheus.Timeout(), "stats")) +} + +func initPrometheusMetricsEndpoint(cfg *config.Configuration, promMux *http.ServeMux) { + promMux.Handle("/metrics", PrometheusHandler(prometheus.DefaultGatherer, cfg.Metrics.Prometheus.Timeout(), "metrics")) +} + +func GetOpenWrapPrometheusServer(cfg *config.Configuration, gatherer *prometheus.Registry, server *http.Server) *http.Server { + promMux := http.NewServeMux() + initPrometheusStatsEndpoint(cfg, gatherer, promMux) + initPrometheusMetricsEndpoint(cfg, promMux) + server.Handler = promMux + return server +} diff --git a/server/prometheus_ow_test.go b/modules/pubmatic/openwrap/prometheus_handler_test.go similarity index 63% rename from server/prometheus_ow_test.go rename to modules/pubmatic/openwrap/prometheus_handler_test.go index d4e1b6f6e37..7f5944b00c1 100644 --- a/server/prometheus_ow_test.go +++ b/modules/pubmatic/openwrap/prometheus_handler_test.go @@ -1,21 +1,78 @@ -package server +package openwrap import ( "context" "net/http" "net/http/httptest" "testing" + "time" - "github.com/magiconair/properties/assert" + "github.com/golang/mock/gomock" "github.com/prebid/prebid-server/v2/config" + mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" ) +func TestPrometheusHandler(t *testing.T) { + ctrl := gomock.NewController(t) + mockMetrics := mock_metrics.NewMockMetricsEngine(ctrl) + ow = &OpenWrap{} + ow.metricEngine = mockMetrics + type args struct { + gatherer prometheus.Gatherer + duration time.Duration + setup func() + endpoint string + } + type testCase struct { + name string + args args + expectCode int + } + + tests := []testCase{ + { + name: "valid test", + args: args{ + gatherer: prometheus.Gatherers{}, + duration: 10 * time.Second, + endpoint: "/abc", + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, + }, + expectCode: http.StatusOK, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.args.setup() + handler := PrometheusHandler(tt.args.gatherer, tt.args.duration, tt.args.endpoint) + server := &http.Server{ + Addr: ":" + "8991", + Handler: handler, + } + server.Shutdown(context.Background()) + req := httptest.NewRequest("GET", tt.args.endpoint, nil) + rec := httptest.NewRecorder() + server.Handler.ServeHTTP(rec, req) + assert.Equal(t, rec.Code, tt.expectCode) + + }) + } +} + func TestInitPrometheusStatsEndpoint(t *testing.T) { + ctrl := gomock.NewController(t) + mockMetrics := mock_metrics.NewMockMetricsEngine(ctrl) + ow = &OpenWrap{} + ow.metricEngine = mockMetrics type args struct { endpoint string cfg *config.Configuration gatherer *prometheus.Registry + setup func() promMux *http.ServeMux } tests := []struct { @@ -36,7 +93,10 @@ func TestInitPrometheusStatsEndpoint(t *testing.T) { }, }, gatherer: &prometheus.Registry{}, - promMux: http.NewServeMux(), + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, + promMux: http.NewServeMux(), }, want: http.StatusOK, @@ -54,7 +114,10 @@ func TestInitPrometheusStatsEndpoint(t *testing.T) { }, }, gatherer: &prometheus.Registry{}, - promMux: http.NewServeMux(), + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, + promMux: http.NewServeMux(), }, want: http.StatusNotFound, @@ -62,6 +125,7 @@ func TestInitPrometheusStatsEndpoint(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt.args.setup() initPrometheusStatsEndpoint(tt.args.cfg, tt.args.gatherer, tt.args.promMux) req := httptest.NewRequest("GET", tt.args.endpoint, nil) rec := httptest.NewRecorder() @@ -72,9 +136,14 @@ func TestInitPrometheusStatsEndpoint(t *testing.T) { } func TestInitPrometheusMetricsEndpoint(t *testing.T) { + ctrl := gomock.NewController(t) + mockMetrics := mock_metrics.NewMockMetricsEngine(ctrl) + ow = &OpenWrap{} + ow.metricEngine = mockMetrics type args struct { endpoint string cfg *config.Configuration + setup func() promMux *http.ServeMux } tests := []struct { @@ -94,6 +163,9 @@ func TestInitPrometheusMetricsEndpoint(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, promMux: http.NewServeMux(), }, want: http.StatusOK, @@ -110,6 +182,9 @@ func TestInitPrometheusMetricsEndpoint(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, promMux: http.NewServeMux(), }, want: http.StatusNotFound, @@ -117,6 +192,7 @@ func TestInitPrometheusMetricsEndpoint(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt.args.setup() initPrometheusMetricsEndpoint(tt.args.cfg, tt.args.promMux) req := httptest.NewRequest("GET", tt.args.endpoint, nil) rec := httptest.NewRecorder() @@ -127,9 +203,14 @@ func TestInitPrometheusMetricsEndpoint(t *testing.T) { } func TestGetOpenWrapPrometheusServer(t *testing.T) { + ctrl := gomock.NewController(t) + mockMetrics := mock_metrics.NewMockMetricsEngine(ctrl) + ow = &OpenWrap{} + ow.metricEngine = mockMetrics type args struct { endpoint string cfg *config.Configuration + setup func() gatherer *prometheus.Registry } @@ -153,6 +234,9 @@ func TestGetOpenWrapPrometheusServer(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, gatherer: prometheus.NewRegistry(), }, want: http.StatusOK, @@ -169,6 +253,9 @@ func TestGetOpenWrapPrometheusServer(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, gatherer: prometheus.NewRegistry(), }, want: http.StatusOK, @@ -185,6 +272,9 @@ func TestGetOpenWrapPrometheusServer(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, gatherer: prometheus.NewRegistry(), }, want: http.StatusNotFound, @@ -201,6 +291,9 @@ func TestGetOpenWrapPrometheusServer(t *testing.T) { }, }, }, + setup: func() { + mockMetrics.EXPECT().RecordRequest(gomock.Any()).AnyTimes() + }, gatherer: prometheus.NewRegistry(), }, want: http.StatusNotFound, @@ -208,7 +301,8 @@ func TestGetOpenWrapPrometheusServer(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - promServer := getOpenWrapPrometheusServer(tt.args.cfg, tt.args.gatherer, server) + tt.args.setup() + promServer := GetOpenWrapPrometheusServer(tt.args.cfg, tt.args.gatherer, server) defer promServer.Shutdown(context.Background()) req := httptest.NewRequest("GET", tt.args.endpoint, nil) rec := httptest.NewRecorder() diff --git a/server/prometheus_ow.go b/server/prometheus_ow.go deleted file mode 100644 index 8766f43ab15..00000000000 --- a/server/prometheus_ow.go +++ /dev/null @@ -1,33 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/prebid/prebid-server/v2/config" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -func initPrometheusStatsEndpoint(cfg *config.Configuration, gatherer *prometheus.Registry, promMux *http.ServeMux) { - promMux.Handle("/stats", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{ - ErrorLog: loggerForPrometheus{}, - MaxRequestsInFlight: 5, - Timeout: cfg.Metrics.Prometheus.Timeout(), - })) -} - -func initPrometheusMetricsEndpoint(cfg *config.Configuration, promMux *http.ServeMux) { - promMux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ - ErrorLog: loggerForPrometheus{}, - MaxRequestsInFlight: 5, - Timeout: cfg.Metrics.Prometheus.Timeout(), - })) -} - -func getOpenWrapPrometheusServer(cfg *config.Configuration, gatherer *prometheus.Registry, server *http.Server) *http.Server { - promMux := http.NewServeMux() - initPrometheusStatsEndpoint(cfg, gatherer, promMux) - initPrometheusMetricsEndpoint(cfg, promMux) - server.Handler = promMux - return server -} diff --git a/server/server.go b/server/server.go index fd377517f59..9c5cdc66acc 100644 --- a/server/server.go +++ b/server/server.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/metrics" metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap" ) // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down. @@ -68,7 +69,7 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H prometheusListener net.Listener prometheusServer = newPrometheusServer(cfg, metrics) ) - prometheusServer = getOpenWrapPrometheusServer(cfg, metrics.PrometheusMetrics.Gatherer, prometheusServer) + prometheusServer = openwrap.GetOpenWrapPrometheusServer(cfg, metrics.PrometheusMetrics.Gatherer, prometheusServer) go shutdownAfterSignals(prometheusServer, stopPrometheus, done) if prometheusListener, err = newTCPListener(prometheusServer.Addr, nil); err != nil { glog.Errorf("Error listening for TCP connections on %s: %v for prometheus server", adminServer.Addr, err) From 5cb451a6c7f171c8a92e89eaa4c7dc1df5879ac2 Mon Sep 17 00:00:00 2001 From: Pubmatic-Supriya-Patil <131644110+Pubmatic-Supriya-Patil@users.noreply.github.com> Date: Tue, 7 May 2024 16:27:12 +0530 Subject: [PATCH 3/6] OTT-1736: Add prometheus stats for Incorrect floors data (#782) OTT-1736: Add prometheus stats for Incorrect floors data --- exchange/exchange.go | 2 +- floors/fetcher.go | 3 + floors/fetcher_test.go | 26 +- floors/floors.go | 43 ++- floors/floors_test.go | 317 ++++++++++++++++++++++- metrics/config/metrics_ow.go | 10 +- metrics/go_metrics.go | 4 +- metrics/metrics.go | 4 +- metrics/metrics_mock.go | 4 +- metrics/prometheus/prometheus_ow.go | 9 +- metrics/prometheus/prometheus_ow_test.go | 44 ++++ 11 files changed, 434 insertions(+), 32 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 7ba55544945..c5ded5b62ee 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -276,7 +276,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog var floorErrs []error if e.priceFloorEnabled { - floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher) + floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher, e.me) if floors.RequestHasFloors(r.BidRequestWrapper.BidRequest) { // Record request count with non-zero imp.bidfloor value e.me.RecordFloorsRequestForAccount(r.PubID) diff --git a/floors/fetcher.go b/floors/fetcher.go index 5ed96b9ec36..1c87f095777 100644 --- a/floors/fetcher.go +++ b/floors/fetcher.go @@ -233,6 +233,7 @@ func (f *PriceFloorFetcher) Fetcher() { func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (*openrtb_ext.PriceFloorRules, int) { floorResp, maxAge, err := f.fetchFloorRulesFromURL(config) if floorResp == nil || err != nil { + f.metricEngine.RecordFloorStatus(config.AccountID, openrtb_ext.FetchLocation, fetchFailure) glog.Errorf("Error while fetching floor data from URL: %s, reason : %s", config.URL, err.Error()) return nil, 0 } @@ -244,11 +245,13 @@ func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (* var priceFloors openrtb_ext.PriceFloorRules if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { + f.metricEngine.RecordFloorStatus(config.AccountID, openrtb_ext.FetchLocation, unmarshalFailure) glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) return nil, 0 } if err := validateRules(config, &priceFloors); err != nil { + f.metricEngine.RecordFloorStatus(config.AccountID, openrtb_ext.FetchLocation, invalidFloors) glog.Errorf("Validation failed for floor JSON from URL: %s, reason: %s", config.URL, err.Error()) return nil, 0 } diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go index cdf0537c9ed..ce29e905e14 100644 --- a/floors/fetcher_test.go +++ b/floors/fetcher_test.go @@ -12,8 +12,10 @@ import ( "github.com/alitto/pond" "github.com/coocood/freecache" + "github.com/golang/mock/gomock" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/ptrutil" @@ -628,6 +630,11 @@ func TestFetchFloorRulesFromURLInvalidMaxAge(t *testing.T) { } func TestFetchAndValidate(t *testing.T) { + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + me := &metrics.MetricsEngineMock{} + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add(MaxAge, "30") @@ -646,6 +653,7 @@ func TestFetchAndValidate(t *testing.T) { responseStatus int want *openrtb_ext.PriceFloorRules want1 int + setup func() }{ { name: "Recieved valid price floor rules response", @@ -676,6 +684,7 @@ func TestFetchAndValidate(t *testing.T) { name: "No response from server", args: args{ configs: config.AccountFloorFetch{ + AccountID: "5890", Enabled: true, Timeout: 30, MaxFileSizeKB: 700, @@ -688,6 +697,9 @@ func TestFetchAndValidate(t *testing.T) { responseStatus: 500, want: nil, want1: 0, + setup: func() { + me.On("RecordFloorStatus", "5890", "fetch", "1").Return() + }, }, { name: "File is greater than MaxFileSize", @@ -713,6 +725,7 @@ func TestFetchAndValidate(t *testing.T) { name: "Malformed response : json unmarshalling failed", args: args{ configs: config.AccountFloorFetch{ + AccountID: "5890", Enabled: true, Timeout: 30, MaxFileSizeKB: 800, @@ -728,11 +741,15 @@ func TestFetchAndValidate(t *testing.T) { responseStatus: 200, want: nil, want1: 0, + setup: func() { + me.On("RecordFloorStatus", "5890", "fetch", "2").Return() + }, }, { name: "Validations failed for price floor rules response", args: args{ configs: config.AccountFloorFetch{ + AccountID: "5890", Enabled: true, Timeout: 30, MaxFileSizeKB: 700, @@ -748,14 +765,21 @@ func TestFetchAndValidate(t *testing.T) { responseStatus: 200, want: nil, want1: 0, + setup: func() { + me.On("RecordFloorStatus", "5890", "fetch", "3").Return() + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) defer mockHttpServer.Close() ppf := PriceFloorFetcher{ - httpClient: mockHttpServer.Client(), + httpClient: mockHttpServer.Client(), + metricEngine: &metricsConf.NilMetricsEngine{}, } tt.args.configs.URL = mockHttpServer.URL got, got1 := ppf.fetchAndValidate(tt.args.configs) diff --git a/floors/floors.go b/floors/floors.go index 12642d6917f..bce6ddcef6d 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/metrics" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/ptrutil" ) @@ -32,9 +33,20 @@ const ( floorPrecision float64 = 0.01 ) +const ( + fetchFailure = "1" + unmarshalFailure = "2" + invalidFloors = "3" + floorsSkipped = "4" + zeroFloorValue = "5" + highFloorValue = "6" + ZERO_FLOOR_VALUE = 0 + HIGH_FLOOR_VALUE = 200 +) + // EnrichWithPriceFloors checks for floors enabled in account and request and selects floors data from dynamic fetched if present // else selects floors data from req.ext.prebid.floors and update request with selected floors details -func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher) []error { +func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher, metricsEngine metrics.MetricsEngine) []error { if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil { return []error{errors.New("Empty bidrequest")} } @@ -42,16 +54,15 @@ func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, accoun if !isPriceFloorsEnabled(account, bidRequestWrapper) { return []error{errors.New("Floors feature is disabled at account or in the request")} } + floors, err := resolveFloors(account, bidRequestWrapper, conversions, priceFloorFetcher, metricsEngine) - floors, err := resolveFloors(account, bidRequestWrapper, conversions, priceFloorFetcher) - - updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions) + updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions, metricsEngine, account.ID) updateFloorsInRequest(bidRequestWrapper, floors) return append(err, updateReqErrs...) } // updateBidRequestWithFloors will update imp.bidfloor and imp.bidfloorcur based on rules matching -func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, request *openrtb_ext.RequestWrapper, conversions currency.Conversions) []error { +func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, request *openrtb_ext.RequestWrapper, conversions currency.Conversions, metricEngine metrics.MetricsEngine, accountID string) []error { var ( floorErrList []error floorVal float64 @@ -69,6 +80,7 @@ func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, requ extFloorRules.Skipped = new(bool) if shouldSkipFloors(modelGroup.SkipRate, extFloorRules.Data.SkipRate, extFloorRules.SkipRate, rand.Intn) { *extFloorRules.Skipped = true + metricEngine.RecordFloorStatus(accountID, extFloorRules.PriceFloorLocation, floorsSkipped) return []error{} } @@ -94,7 +106,12 @@ func updateBidRequestWithFloors(extFloorRules *openrtb_ext.PriceFloorRules, requ if floorMinVal > 0.0 && floorVal < floorMinVal { bidFloor = floorMinVal } - + if bidFloor < ZERO_FLOOR_VALUE { + metricEngine.RecordFloorStatus(accountID, extFloorRules.PriceFloorLocation, zeroFloorValue) + } + if bidFloor > HIGH_FLOOR_VALUE { + metricEngine.RecordFloorStatus(accountID, extFloorRules.PriceFloorLocation, highFloorValue) + } imp.BidFloor = bidFloor imp.BidFloorCur = floorCur @@ -146,7 +163,7 @@ func useFetchedData(rate *int) bool { } // resolveFloors does selection of floors fields from request data and dynamic fetched data if dynamic fetch is enabled -func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher) (*openrtb_ext.PriceFloorRules, []error) { +func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher, metricsEngine metrics.MetricsEngine) (*openrtb_ext.PriceFloorRules, []error) { var ( errList []error floorRules *openrtb_ext.PriceFloorRules @@ -166,17 +183,17 @@ func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.Reques if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess && useFetchedData(fetchResult.Data.FetchRate) { mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) - floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) + floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation, metricsEngine) } else if reqFloor != nil { - floorRules, errList = createFloorsFrom(reqFloor, account, fetchStatus, openrtb_ext.RequestLocation) + floorRules, errList = createFloorsFrom(reqFloor, account, fetchStatus, openrtb_ext.RequestLocation, metricsEngine) } else { - floorRules, errList = createFloorsFrom(nil, account, fetchStatus, openrtb_ext.NoDataLocation) + floorRules, errList = createFloorsFrom(nil, account, fetchStatus, openrtb_ext.NoDataLocation, metricsEngine) } return floorRules, errList } // createFloorsFrom does preparation of floors data which shall be used for further processing -func createFloorsFrom(floors *openrtb_ext.PriceFloorRules, account config.Account, fetchStatus, floorLocation string) (*openrtb_ext.PriceFloorRules, []error) { +func createFloorsFrom(floors *openrtb_ext.PriceFloorRules, account config.Account, fetchStatus, floorLocation string, metricsEngine metrics.MetricsEngine) (*openrtb_ext.PriceFloorRules, []error) { var floorModelErrList []error finalFloors := &openrtb_ext.PriceFloorRules{ FetchStatus: fetchStatus, @@ -186,12 +203,16 @@ func createFloorsFrom(floors *openrtb_ext.PriceFloorRules, account config.Accoun if floors != nil { floorValidationErr := validateFloorParams(floors) if floorValidationErr != nil { + metricsEngine.RecordFloorStatus(account.ID, floorLocation, invalidFloors) return finalFloors, append(floorModelErrList, floorValidationErr) } finalFloors.Enforcement = floors.Enforcement if floors.Data != nil { validModelGroups, floorModelErrList := selectValidFloorModelGroups(floors.Data.ModelGroups, account) + if len(floorModelErrList) > 0 { + metricsEngine.RecordFloorStatus(account.ID, floorLocation, invalidFloors) + } if len(validModelGroups) == 0 { return finalFloors, floorModelErrList } else { diff --git a/floors/floors_test.go b/floors/floors_test.go index f065d2ac84e..4d8786a555f 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -6,9 +6,12 @@ import ( "reflect" "testing" + "github.com/golang/mock/gomock" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/prebid/prebid-server/v2/util/ptrutil" @@ -375,7 +378,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates), &mockPriceFloorFetcher{}) + ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates), &mockPriceFloorFetcher{}, &metricsConf.NilMetricsEngine{}) if tc.bidRequestWrapper != nil { assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloor, tc.expFloorVal, tc.name) assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloorCur, tc.expFloorCur, tc.name) @@ -635,7 +638,7 @@ func TestResolveFloors(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), &MockFetch{}) + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), &MockFetch{}, &metricsConf.NilMetricsEngine{}) if !reflect.DeepEqual(resolvedFloors, tc.expFloors) { t.Errorf("resolveFloors error: \nreturn:\t%v\nwant:\t%v", printFloors(resolvedFloors), printFloors(tc.expFloors)) } @@ -872,7 +875,7 @@ func TestResolveFloorsWithUseDataRate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher) + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher, &metricsConf.NilMetricsEngine{}) assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) }) } @@ -1004,7 +1007,12 @@ func printFloors(floors *openrtb_ext.PriceFloorRules) string { func TestCreateFloorsFrom(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + me := &metrics.MetricsEngineMock{} + testAccountConfig := config.Account{ + ID: "5890", PriceFloors: config.AccountPriceFloors{ Enabled: true, UseDynamicData: false, @@ -1024,6 +1032,7 @@ func TestCreateFloorsFrom(t *testing.T) { args args want *openrtb_ext.PriceFloorRules want1 []error + setup func() }{ { name: "floor provider should be selected from floor json", @@ -1212,11 +1221,55 @@ func TestCreateFloorsFrom(t *testing.T) { want1: []error{ errors.New("Invalid Floor Model = 'model from fetched' due to SkipRate = '110' is out of range (1-100)"), }, + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "3").Return() + }, + }, + { + name: "Invalid floors", + args: args{ + account: testAccountConfig, + floors: &openrtb_ext.PriceFloorRules{ + FloorMin: -1, + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + }, + }, + fetchStatus: openrtb_ext.FetchNone, + floorLocation: openrtb_ext.RequestLocation, + }, + want: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + }, + want1: []error{ + errors.New("Invalid FloorMin = '-1', value should be >= 0"), + }, + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "3").Return() + }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got, got1 := createFloorsFrom(tc.args.floors, tc.args.account, tc.args.fetchStatus, tc.args.floorLocation) + if tc.setup != nil { + tc.setup() + } + got, got1 := createFloorsFrom(tc.args.floors, tc.args.account, tc.args.fetchStatus, tc.args.floorLocation, me) assert.Equal(t, got1, tc.want1, tc.name) assert.Equal(t, got, tc.want, tc.name) }) @@ -1890,3 +1943,259 @@ func TestMergeFloors(t *testing.T) { }) } } + +func TestUpdateBidRequestWithFloors(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + me := &metrics.MetricsEngineMock{} + + type args struct { + extFloorRules *openrtb_ext.PriceFloorRules + request *openrtb_ext.RequestWrapper + conversions currency.Conversions + metricEngine metrics.MetricsEngine + accountID string + } + + tests := []struct { + name string + args args + setup func() + }{ + + { + name: "test record floor status with no failures", + args: args{ + extFloorRules: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 5, + FloorMinCur: "USD", + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }}, + request: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":0},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + accountID: "5890", + }, + }, + { + name: "test record floor status with skiprate 100", + args: args{ + extFloorRules: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 5, + FloorMinCur: "USD", + SkipRate: 100, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }}, + request: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":0},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + accountID: "5890", + }, + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "4").Return() + }, + }, + { + name: "test record floor status with negative floor value, rule matched", + args: args{ + extFloorRules: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMinCur: "USD", + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": -7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }}, + request: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":0},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + accountID: "5890", + }, + + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "5").Return() + }, + }, + { + name: "test record floor status with high floor value, rule matched", + args: args{ + extFloorRules: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMinCur: "USD", + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 201, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }}, + request: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":0},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + accountID: "5890", + }, + + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "6").Return() + }, + }, + { + name: "test record floor status with high floor value ih the request, rule not matched", + args: args{ + extFloorRules: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 201, + FloorMinCur: "USD", + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 201, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }}, + request: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":0},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + accountID: "5890", + }, + setup: func() { + me.On("RecordFloorStatus", "5890", "request", "6").Return() + }, + }, + } + for _, test := range tests { + if test.setup != nil { + test.setup() + } + updateBidRequestWithFloors(test.args.extFloorRules, test.args.request, test.args.conversions, me, test.args.accountID) + } +} diff --git a/metrics/config/metrics_ow.go b/metrics/config/metrics_ow.go index cc9b8a88cfc..558530a4aa3 100644 --- a/metrics/config/metrics_ow.go +++ b/metrics/config/metrics_ow.go @@ -53,10 +53,10 @@ func (me *MultiMetricsEngine) RecordRejectedBidsForBidder(bidder openrtb_ext.Bid } } -// RecordDynamicFetchFailure across all engines -func (me *MultiMetricsEngine) RecordDynamicFetchFailure(pubId, code string) { +// RecordFloorStatus across all engines +func (me *MultiMetricsEngine) RecordFloorStatus(pubId, source, code string) { for _, thisME := range *me { - thisME.RecordDynamicFetchFailure(pubId, code) + thisME.RecordFloorStatus(pubId, source, code) } } @@ -64,8 +64,8 @@ func (me *MultiMetricsEngine) RecordDynamicFetchFailure(pubId, code string) { func (me *NilMetricsEngine) RecordVASTTagType(biddder, vastTag string) { } -// RecordDynamicFetchFailure as a noop -func (me *NilMetricsEngine) RecordDynamicFetchFailure(pubId, code string) { +// RecordFloorStatus as a noop +func (me *NilMetricsEngine) RecordFloorStatus(pubId, source, code string) { } // RecordRejectedBids as a noop diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index e216c58bcb4..c104cc91cc1 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -650,8 +650,8 @@ func (me *Metrics) RecordRejectedBidsForAccount(pubId string) { } } -// RecordDynamicFetchFailure implements a part of the MetricsEngine interface. Records dynamic fetch failure -func (me *Metrics) RecordDynamicFetchFailure(pubId, code string) { +// RecordFloorStatus implements a part of the MetricsEngine interface. Records dynamic fetch failure +func (me *Metrics) RecordFloorStatus(pubId, source, code string) { if pubId != PublisherUnknown { me.getAccountMetrics(pubId).dynamicFetchFailureMeter.Mark(1) } diff --git a/metrics/metrics.go b/metrics/metrics.go index 682589f8e88..0a6be2cb0ef 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -519,8 +519,8 @@ type MetricsEngine interface { //RecordAdapterVideoBidDuration records actual ad duration returned by the bidder RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) - //RecordDynamicFetchFailure records the dynamic fetch failure labeled by pubid and reason code - RecordDynamicFetchFailure(pubId, code string) + //RecordFloorStatus records the floor validation status labeled by pubid, source and reason code + RecordFloorStatus(pubId, source, code string) //RecordRejectedBids records the rejected bids labeled by pubid, bidder and reason code RecordRejectedBids(pubid, bidder, code string) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 836031839e2..0e5682a0ba2 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -238,6 +238,6 @@ func (me *MetricsEngineMock) RecordRejectedBids(pubid, bidder, code string) { me.Called(pubid, bidder, code) } -func (me *MetricsEngineMock) RecordDynamicFetchFailure(pubId, code string) { - me.Called(pubId, code) +func (me *MetricsEngineMock) RecordFloorStatus(pubId, source, code string) { + me.Called(pubId, source, code) } diff --git a/metrics/prometheus/prometheus_ow.go b/metrics/prometheus/prometheus_ow.go index bf4a3ee681e..60211b3fd3a 100644 --- a/metrics/prometheus/prometheus_ow.go +++ b/metrics/prometheus/prometheus_ow.go @@ -160,10 +160,11 @@ func (m *Metrics) RecordFloorsRequestForAccount(pubId string) { }).Inc() } } -func (m *Metrics) RecordDynamicFetchFailure(pubId, code string) { +func (m *Metrics) RecordFloorStatus(pubId, source, code string) { if pubId != metrics.PublisherUnknown { m.dynamicFetchFailure.With(prometheus.Labels{ accountLabel: pubId, + sourceLabel: source, codeLabel: code, }).Inc() } @@ -186,9 +187,9 @@ func (m *OWMetrics) init(cfg config.PrometheusMetrics, reg *prometheus.Registry) []string{bidderLabel, vastTagTypeLabel}) m.dynamicFetchFailure = newCounter(cfg, reg, - "floors_account_fetch_err", - "Count of failures in case of dynamic fetch labeled by account", - []string{codeLabel, accountLabel}) + "floors_account_status", + "Count of floor validation status labeled by account, source and reason code", + []string{accountLabel, codeLabel, sourceLabel}) m.adapterDuplicateBidIDCounter = newCounter(cfg, reg, "duplicate_bid_ids", diff --git a/metrics/prometheus/prometheus_ow_test.go b/metrics/prometheus/prometheus_ow_test.go index 35a8c9d05b8..81916c7b8a8 100644 --- a/metrics/prometheus/prometheus_ow_test.go +++ b/metrics/prometheus/prometheus_ow_test.go @@ -3,6 +3,7 @@ package prometheusmetrics import ( "testing" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) @@ -168,3 +169,46 @@ func TestRecordVASTTagType(t *testing.T) { }) } } + +func TestRecordFloorStatus(t *testing.T) { + type args struct { + code, account, source string + } + type want struct { + expCount int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "record_floor_status", + args: args{ + account: "5890", + code: "1", + source: openrtb_ext.FetchLocation, + }, + want: want{ + expCount: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + pm := createMetricsForTesting() + pm.RecordFloorStatus(tt.args.account, tt.args.source, tt.args.code) + assertCounterVecValue(t, + "", + "record dynamic fetch failure", + pm.dynamicFetchFailure, + float64(tt.want.expCount), + prometheus.Labels{ + accountLabel: tt.args.account, + sourceLabel: tt.args.source, + codeLabel: tt.args.code, + }) + }) + } +} From 28ca71bfcfc50384ba851854987aec1b1e6b2830 Mon Sep 17 00:00:00 2001 From: Saurabh Narkhede <108730956+pm-saurabh-narkhede@users.noreply.github.com> Date: Wed, 8 May 2024 16:51:41 +0530 Subject: [PATCH 4/6] UOE-10419: Support Regex Mapping for Prebid S2S Profiles (#785) --- .../pubmatic/openwrap/bidderparams/others.go | 6 +- .../openwrap/bidderparams/others_test.go | 302 ++++++++++++++++++ .../openwrap/bidderparams/pubmatic.go | 6 +- .../openwrap/bidderparams/pubmatic_test.go | 196 ++++++++++++ modules/pubmatic/openwrap/models/constants.go | 1 + modules/pubmatic/openwrap/models/utils.go | 4 +- .../pubmatic/openwrap/models/utils_test.go | 24 ++ 7 files changed, 533 insertions(+), 6 deletions(-) create mode 100644 modules/pubmatic/openwrap/bidderparams/others_test.go diff --git a/modules/pubmatic/openwrap/bidderparams/others.go b/modules/pubmatic/openwrap/bidderparams/others.go index eaaa2b1058c..9b693281947 100644 --- a/modules/pubmatic/openwrap/bidderparams/others.go +++ b/modules/pubmatic/openwrap/bidderparams/others.go @@ -16,10 +16,12 @@ func PrepareAdapterParamsV25(rctx models.RequestCtx, cache cache.Cache, bidReque return "", "", false, nil, errors.New("ErrBidderParamsValidationError") } - var isRegexSlot bool + var isRegexSlot, isRegexKGP bool var matchedSlot, matchedPattern string - isRegexKGP := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN] == models.REGEX_KGP + if kgp := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN]; kgp == models.REGEX_KGP || kgp == models.ADUNIT_SIZE_REGEX_KGP { + isRegexKGP = true + } slots, slotMap, slotMappingInfo, hw := getSlotMeta(rctx, cache, bidRequest, imp, impExt, partnerID) for i, slot := range slots { diff --git a/modules/pubmatic/openwrap/bidderparams/others_test.go b/modules/pubmatic/openwrap/bidderparams/others_test.go new file mode 100644 index 00000000000..1eb42fa6189 --- /dev/null +++ b/modules/pubmatic/openwrap/bidderparams/others_test.go @@ -0,0 +1,302 @@ +package bidderparams + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/adapters" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" + mock_cache "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache/mock" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestPrepareAdapterParamsV25(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockCache := mock_cache.NewMockCache(ctrl) + type args struct { + rctx models.RequestCtx + cache cache.Cache + bidRequest openrtb2.BidRequest + imp openrtb2.Imp + impExt models.ImpExtension + partnerID int + } + type want struct { + matchedSlot string + matchedPattern string + isRegexSlot bool + params []byte + wantErr bool + } + tests := []struct { + name string + args args + setup func() + want want + }{ + { + name: "AdUnit,Size slot matched", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 19323: { + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/43743431/DMDEMO1", + }, + }, + imp: getTestImp("/43743431/DMDEMO1", true, false), + partnerID: 19323, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/43743431/dmdemo1@200x300": { + PartnerId: 19323, + AdapterId: 2, + VersionId: 92588, + SlotName: "/43743431/DMDemo1@200x300", + MappingJson: "{\"placementId\":\"9880618\"}", + SlotMappings: map[string]interface{}{ + "placementId": "9880618", + }, + OrderID: 1, + Hash: "", + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"/43743431/DMDemo1@200x300"}, + HashValueMap: map[string]string{"/43743431/DMDemo1@200x300": ""}, + }) + }, + want: want{ + matchedSlot: "/43743431/DMDEMO1@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte("{\"placementId\":9880618}"), + wantErr: false, + }, + }, + { + name: "partnerconfig not found for partnerId", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: nil, + }, + cache: mockCache, + partnerID: 1, + }, + setup: func() {}, + want: want{ + matchedSlot: "", + matchedPattern: "", + isRegexSlot: false, + params: nil, + wantErr: true, + }, + }, + { + name: "slots not founds", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 1, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 256: { + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "appnexus": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + Wrapper: &models.ExtImpWrapper{ + Div: "Div1", + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 256, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(nil) + }, + want: want{ + matchedSlot: "", + matchedPattern: "", + isRegexSlot: false, + params: nil, + wantErr: false, + }, + }, + { + name: "regex mapping slot found", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 19323: { + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_AU_@_DIV_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/43743431/DMDEMO1", + }, + }, + imp: getTestImp("/43743431/DMDEMO1", true, false), + partnerID: 19323, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "^/43743431/dmdemo[0-9]*@div[12]@^200x300$": { + PartnerId: 19323, + AdapterId: 2, + VersionId: 92588, + SlotName: "^/43743431/DMDemo[0-9]*@Div[12]@^200x300$", + MappingJson: "{\"placementId\":\"9880618\"}", + SlotMappings: map[string]interface{}{ + "placementId": "9880618", + }, + OrderID: 1, + Hash: "", + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"^/43743431/DMDemo[0-9]*@Div[12]@^200x300$"}, + HashValueMap: map[string]string{"^/43743431/DMDemo[0-9]*@Div[12]@^200x300$": ""}, + }) + mockCache.EXPECT().Get("psregex_5890_123_1_19323_/43743431/DMDEMO1@@200x300").Return(regexSlotEntry{ + SlotName: "/43743431/DMDEMO1@@200x300", + RegexPattern: "^/43743431/DMDemo[0-9]*@Div[12]@^200x300$", + }, true) + }, + want: want{ + matchedSlot: "/43743431/DMDEMO1@@200x300", + matchedPattern: "^/43743431/DMDemo[0-9]*@Div[12]@^200x300$", + isRegexSlot: true, + params: []byte("{\"placementId\":9880618}"), + wantErr: false, + }, + }, + { + name: "prebid s2s regex mapping slot found", + args: args{ + rctx: models.RequestCtx{ + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 19323: { + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_RE_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Data: openrtb_ext.ExtImpData{ + PbAdslot: "/43743431/DMDEMO1", + }, + }, + imp: getTestImp("/43743431/DMDEMO1", true, false), + partnerID: 19323, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "^/43743431/dmdemo[0-9]*@^200x300$": { + PartnerId: 19323, + AdapterId: 2, + VersionId: 92588, + SlotName: "^/43743431/DMDemo[0-9]*@^200x300$", + MappingJson: "{\"placementId\":\"9880618\"}", + SlotMappings: map[string]interface{}{ + "placementId": "9880618", + }, + OrderID: 1, + Hash: "", + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"^/43743431/DMDemo[0-9]*@^200x300$"}, + HashValueMap: map[string]string{"^/43743431/DMDemo[0-9]*@^200x300$": ""}, + }) + mockCache.EXPECT().Get("psregex_5890_123_1_19323_/43743431/DMDEMO1@200x300").Return(regexSlotEntry{ + SlotName: "/43743431/DMDEMO1@200x300", + RegexPattern: "^/43743431/DMDemo[0-9]*@^200x300$", + }, true) + }, + want: want{ + matchedSlot: "/43743431/DMDEMO1@200x300", + matchedPattern: "^/43743431/DMDemo[0-9]*@^200x300$", + isRegexSlot: true, + params: []byte("{\"placementId\":9880618}"), + wantErr: false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + adapters.InitBidders("./static/bidder-params/") + matchedSlot, matchedPattern, isRegexSlot, params, err := PrepareAdapterParamsV25(tt.args.rctx, tt.args.cache, tt.args.bidRequest, tt.args.imp, tt.args.impExt, tt.args.partnerID) + if (err != nil) != tt.want.wantErr { + assert.Equal(t, tt.want.wantErr, err != nil) + return + } + assert.Equal(t, tt.want.matchedSlot, matchedSlot) + assert.Equal(t, tt.want.matchedPattern, matchedPattern) + assert.Equal(t, tt.want.isRegexSlot, isRegexSlot) + assert.Equal(t, tt.want.params, params) + }) + } +} diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic.go b/modules/pubmatic/openwrap/bidderparams/pubmatic.go index 5eadf568d1e..afb65b57651 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic.go @@ -39,10 +39,12 @@ func PreparePubMaticParamsV25(rctx models.RequestCtx, cache cache.Cache, bidRequ hash := "" var err error var matchedSlot, matchedPattern string - isRegexSlot := false + var isRegexSlot, isRegexKGP bool kgp := rctx.PartnerConfigMap[partnerID][models.KEY_GEN_PATTERN] - isRegexKGP := kgp == models.REGEX_KGP + if kgp == models.REGEX_KGP || kgp == models.ADUNIT_SIZE_REGEX_KGP { + isRegexKGP = true + } // simple+regex key match for _, slot := range slots { diff --git a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go index 3186edb4bfc..d51b35b486d 100644 --- a/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go +++ b/modules/pubmatic/openwrap/bidderparams/pubmatic_test.go @@ -374,6 +374,202 @@ func TestPreparePubMaticParamsV25(t *testing.T) { wantErr: false, }, }, + { + name: "exact_matched_slot_found_adslot_updated_from_PubMatic_secondary_flow_for_prebids2s_regex", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_RE_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + models.KEY_PROFILE_ID: "1323", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@200x300": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + "slotName": "/Test_Adunit1234@200x300", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@200x300","wrapper":{"version":0,"profile":1323},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "exact_matched_slot_found_adSlot_upadted_from_owSlotName_prebids2s_regex", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_RE_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "/test_adunit1234@200x300": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + models.KEY_OW_SLOT_NAME: "/Test_Adunit1234@200x300", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + }) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@200x300", + matchedPattern: "", + isRegexSlot: false, + params: []byte(`{"publisherId":"5890","adSlot":"/Test_Adunit1234@200x300","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, + { + name: "prebids2s_regex_matched_slot_found_adSlot_upadted_from_hashValue", + args: args{ + rctx: models.RequestCtx{ + IsTestRequest: 0, + PubID: 5890, + ProfileID: 123, + DisplayID: 1, + PartnerConfigMap: map[int]map[string]string{ + 1: { + models.PREBID_PARTNER_NAME: "pubmatic", + models.BidderCode: "pubmatic", + models.TIMEOUT: "200", + models.KEY_GEN_PATTERN: "_RE_@_W_x_H_", + models.SERVER_SIDE_FLAG: "1", + }, + }, + }, + cache: mockCache, + impExt: models.ImpExtension{ + Bidder: map[string]*models.BidderExtension{ + "pubmatic": { + KeyWords: []models.KeyVal{ + { + Key: "test_key1", + Values: []string{"test_value1", "test_value2"}, + }, + { + Key: "test_key2", + Values: []string{"test_value1", "test_value2"}, + }, + }, + }, + }, + }, + imp: getTestImp("/Test_Adunit1234", true, false), + partnerID: 1, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + ".*@.*": { + PartnerId: 1, + AdapterId: 1, + SlotName: "/Test_Adunit1234@200x300", + SlotMappings: map[string]interface{}{ + "site": "12313", + "adtag": "45343", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"test", "test1"}, + HashValueMap: map[string]string{ + ".*@.*": "2aa34b52a9e941c1594af7565e599c8d", + }, + }) + mockCache.EXPECT().Get("psregex_5890_123_1_1_/Test_Adunit1234@200x300").Return(regexSlotEntry{ + SlotName: "/Test_Adunit1234@200x300", + RegexPattern: ".*@.*", + }, true) + }, + want: want{ + matchedSlot: "/Test_Adunit1234@200x300", + matchedPattern: ".*@.*", + isRegexSlot: true, + params: []byte(`{"publisherId":"5890","adSlot":"2aa34b52a9e941c1594af7565e599c8d","wrapper":{"version":1,"profile":123},"keywords":[{"key":"test_key1","value":["test_value1","test_value2"]},{"key":"test_key2","value":["test_value1","test_value2"]}]}`), + wantErr: false, + }, + }, { name: "exact_matched_slot_found_adSlot_upadted_from_owSlotName", args: args{ diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index 4c0a5733511..c6261bdac41 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -390,6 +390,7 @@ const ( MACRO_VASTTAG = "_VASTTAG_" ADUNIT_SIZE_KGP = "_AU_@_W_x_H_" + ADUNIT_SIZE_REGEX_KGP = "_RE_@_W_x_H_" REGEX_KGP = "_AU_@_DIV_@_W_x_H_" DIV_SIZE_KGP = "_DIV_@_W_x_H_" ADUNIT_SOURCE_VASTTAG_KGP = "_AU_@_SRC_@_VASTTAG_" diff --git a/modules/pubmatic/openwrap/models/utils.go b/modules/pubmatic/openwrap/models/utils.go index 42589e38859..baae78b0eaf 100644 --- a/modules/pubmatic/openwrap/models/utils.go +++ b/modules/pubmatic/openwrap/models/utils.go @@ -314,11 +314,11 @@ func GetKGPSV(bid openrtb2.Bid, bidderMeta PartnerData, adformat string, tagId s func GenerateSlotName(h, w int64, kgp, tagid, div, src string) string { // func (H, W, Div), no need to validate, will always be non-nil switch kgp { - case "_AU_": // adunitconfig + case "_AU_", "_RE_": // adunitconfig or defaultmappingKGP return tagid case "_DIV_": return div - case "_AU_@_W_x_H_": + case "_AU_@_W_x_H_", "_RE_@_W_x_H_": return fmt.Sprintf("%s@%dx%d", tagid, w, h) case "_DIV_@_W_x_H_": return fmt.Sprintf("%s@%dx%d", div, w, h) diff --git a/modules/pubmatic/openwrap/models/utils_test.go b/modules/pubmatic/openwrap/models/utils_test.go index df11e12dd7c..446a0c9b10a 100644 --- a/modules/pubmatic/openwrap/models/utils_test.go +++ b/modules/pubmatic/openwrap/models/utils_test.go @@ -457,6 +457,18 @@ func TestGenerateSlotName(t *testing.T) { }, want: "/15671365/Test_Adunit", }, + { + name: "_RE_", + args: args{ + h: 100, + w: 200, + kgp: "_RE_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit", + }, { name: "_DIV_", args: args{ @@ -493,6 +505,18 @@ func TestGenerateSlotName(t *testing.T) { }, want: "/15671365/Test_Adunit@200x100", }, + { + name: "_RE_@_W_x_H_", + args: args{ + h: 100, + w: 200, + kgp: "_RE_@_W_x_H_", + tagid: "/15671365/Test_Adunit", + div: "Div1", + src: "test.com", + }, + want: "/15671365/Test_Adunit@200x100", + }, { name: "_DIV_@_W_x_H_", args: args{ From eed034c338a0eea1cd85a1c9be96ac3496b5515c Mon Sep 17 00:00:00 2001 From: pm-priyanka-bagade <156899734+pm-priyanka-bagade@users.noreply.github.com> Date: Fri, 17 May 2024 09:04:12 +0530 Subject: [PATCH 5/6] UOE-10137-1 : Fetch platform details from wrapper_profile table (#751) --- .../openwrap/database/mysql/partner_config.go | 15 +- .../database/mysql/partner_config_test.go | 196 +++++++++++++----- modules/pubmatic/openwrap/models/constants.go | 1 + 3 files changed, 152 insertions(+), 60 deletions(-) diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config.go b/modules/pubmatic/openwrap/database/mysql/partner_config.go index aa70aca4e69..657a52226a2 100644 --- a/modules/pubmatic/openwrap/database/mysql/partner_config.go +++ b/modules/pubmatic/openwrap/database/mysql/partner_config.go @@ -14,7 +14,7 @@ import ( // return the list of active server side header bidding partners // with their configurations at publisher-profile-version level func (db *mySqlDB) GetActivePartnerConfigurations(pubID, profileID int, displayVersion int) (map[int]map[string]string, error) { - versionID, displayVersionID, err := db.getVersionID(profileID, displayVersion, pubID) + versionID, displayVersionID, platform, err := db.getVersionID(profileID, displayVersion, pubID) if err != nil { return nil, err } @@ -22,6 +22,10 @@ func (db *mySqlDB) GetActivePartnerConfigurations(pubID, profileID int, displayV partnerConfigMap, err := db.getActivePartnerConfigurations(versionID) if err == nil && partnerConfigMap[-1] != nil { partnerConfigMap[-1][models.DisplayVersionID] = strconv.Itoa(displayVersionID) + // check for SDK new UI + if platform != "" { + partnerConfigMap[-1][models.PLATFORM_KEY] = platform + } } return partnerConfigMap, err } @@ -76,7 +80,7 @@ func (db *mySqlDB) getActivePartnerConfigurations(versionID int) (map[int]map[st return partnerConfigMap, nil } -func (db *mySqlDB) getVersionID(profileID, displayVersion, pubID int) (int, int, error) { +func (db *mySqlDB) getVersionID(profileID, displayVersion, pubID int) (int, int, string, error) { var row *sql.Row if displayVersion == 0 { row = db.conn.QueryRow(db.cfg.Queries.LiveVersionInnerQuery, profileID, pubID) @@ -84,10 +88,11 @@ func (db *mySqlDB) getVersionID(profileID, displayVersion, pubID int) (int, int, row = db.conn.QueryRow(db.cfg.Queries.DisplayVersionInnerQuery, profileID, displayVersion, pubID) } + var platform sql.NullString var versionID, displayVersionIDFromDB int - err := row.Scan(&versionID, &displayVersionIDFromDB) + err := row.Scan(&versionID, &displayVersionIDFromDB, &platform) if err != nil { - return versionID, displayVersionIDFromDB, err + return versionID, displayVersionIDFromDB, platform.String, err } - return versionID, displayVersionIDFromDB, nil + return versionID, displayVersionIDFromDB, platform.String, nil } diff --git a/modules/pubmatic/openwrap/database/mysql/partner_config_test.go b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go index ea89b8b2116..bae4d8d7187 100644 --- a/modules/pubmatic/openwrap/database/mysql/partner_config_test.go +++ b/modules/pubmatic/openwrap/database/mysql/partner_config_test.go @@ -7,6 +7,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/config" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + LiveVersionInnerQuery: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -51,8 +52,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("25_1", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("25_1", "9", models.PLATFORM_DISPLAY) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) return db }, @@ -62,7 +63,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + LiveVersionInnerQuery: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -81,8 +82,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_DISPLAY) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) return db }, @@ -92,8 +93,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + LiveVersionInnerQuery: models.TestQuery, + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -133,8 +134,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_DISPLAY) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). @@ -142,7 +143,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "kgp", "_AU_@_W_x_H_"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "timeout", "200"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "serverSideEnabled", "1") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rowsPartnerConfig) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rowsPartnerConfig) return db }, }, @@ -151,8 +152,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - DisplayVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+)", - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + DisplayVersionInnerQuery: models.TestQuery, + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -192,8 +193,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+)")).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_DISPLAY) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). @@ -201,7 +202,7 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "kgp", "_AU_@_W_x_H_"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "timeout", "200"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "serverSideEnabled", "1") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rowsPartnerConfig) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rowsPartnerConfig) return db }, }, @@ -210,8 +211,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - DisplayVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+)", - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + DisplayVersionInnerQuery: models.TestQuery, + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -259,8 +260,8 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+)")).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_DISPLAY) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) rowsPartnerConfig := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("-1", "ALL", "ALL", 0, -1, 0, -1, "platform", "display"). @@ -268,9 +269,9 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "kgp", "_AU_@_W_x_H_"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "timeout", "200"). AddRow("101", "pubmatic", "pubmatic", 0, 3, 0, 76, "serverSideEnabled", "1"). - AddRow("234", "vastbidder", "test-vastbidder", 0, 3, 0, 546, "vendorId", "999"). - AddRow("234", "vastbidder", "test-vastbidder", 0, 3, 0, 546, "serverSideEnabled", "1") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rowsPartnerConfig) + AddRow("234", "vastbidder", "test-vastbidder", 0, 3, 0, -1, "serverSideEnabled", "1"). + AddRow("234", "vastbidder", "test-vastbidder", 0, 3, 0, -1, "vendorId", "546") + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rowsPartnerConfig) return db }, }, @@ -281,12 +282,12 @@ func Test_mySqlDB_GetActivePartnerConfigurations(t *testing.T) { conn: tt.setup(), cfg: tt.fields.cfg, } - got, err := db.GetActivePartnerConfigurations(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) + gotPartnerConfigMap, err := db.GetActivePartnerConfigurations(tt.args.pubID, tt.args.profileID, tt.args.displayVersion) if (err != nil) != tt.wantErr { t.Errorf("mySqlDB.GetActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) return } - assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want, gotPartnerConfigMap) }) } } @@ -323,7 +324,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -340,7 +341,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { } rows := sqlmock.NewRows([]string{"partnerId", "prebidPartnerName", "bidderCode", "isAlias", "entityTypeID", "testConfig", "vendorId", "keyName", "value"}). AddRow("11_11", "openx", "openx", 0, -1, 0, -1, "k1", "v1") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -349,7 +350,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -386,7 +387,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { AddRow(101, "openx", "openx", 0, -1, 0, 152, "k1", "v1"). AddRow(101, "openx", "openx", 0, -1, 0, 152, "k2", "v2"). AddRow(102, "pubmatic", "pubmatic", 0, -1, 0, 76, "k1", "v2") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -395,7 +396,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -438,7 +439,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, 152, "rev_share", "10"). AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, 100, "k1", "v1"). AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, 100, "k2", "v2") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -447,7 +448,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -493,7 +494,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, 76, "rev_share", "10"). AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, 100, "k1", "v1"). AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, 100, "k2", "v2") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -502,7 +503,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -546,7 +547,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, 76, "rev_share", "10"). AddRow(102, "SecondPartnerName", "SecondBidder", 1, -1, 0, 100, "k1", "v1"). AddRow(102, "SecondPartnerName", "SecondBidder", 1, -1, 0, 100, "k2", "v2") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -555,7 +556,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - GetParterConfig: "^SELECT (.+) FROM wrapper_config_map (.+)", + GetParterConfig: models.TestQuery, }, MaxDbContextTimeout: 1000, }, @@ -599,7 +600,7 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { AddRow(101, "FirstPartnerName", "FirstBidder", 0, 3, 0, 76, "rev_share", "10"). AddRow(102, "-", "-", 0, -1, 0, 100, "k1", "v1"). AddRow(102, "SecondPartnerName", "SecondBidder", 0, -1, 0, 100, "k2", "v2") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_config_map (.+)")).WillReturnRows(rows) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WillReturnRows(rows) return db }, }, @@ -610,12 +611,12 @@ func Test_mySqlDB_getActivePartnerConfigurations(t *testing.T) { conn: tt.setup(), cfg: tt.fields.cfg, } - got, err := db.getActivePartnerConfigurations(tt.args.versionID) + gotPartnerConfigMap, err := db.getActivePartnerConfigurations(tt.args.versionID) if (err != nil) != tt.wantErr { t.Errorf("mySqlDB.getActivePartnerConfigurations() error = %v, wantErr %v", err, tt.wantErr) return } - assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want, gotPartnerConfigMap) }) } } @@ -635,6 +636,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { args args expectedVersionID int expectedDisplayVersionIDFromDB int + expectedPlatform string wantErr bool setup func() *sql.DB }{ @@ -643,7 +645,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + LiveVersionInnerQuery: models.TestQuery, }, }, }, @@ -654,6 +656,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { }, expectedVersionID: 0, expectedDisplayVersionIDFromDB: 0, + expectedPlatform: "", wantErr: true, setup: func() *sql.DB { db, mock, err := sqlmock.New() @@ -661,8 +664,8 @@ func Test_mySqlDB_getVersionID(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("25_1", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("25_1", "9", models.PLATFORM_APP) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) return db }, @@ -672,7 +675,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - LiveVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+) LIVE", + LiveVersionInnerQuery: models.TestQuery, }, }, }, @@ -684,6 +687,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { expectedVersionID: 251, expectedDisplayVersionIDFromDB: 9, + expectedPlatform: "in-app", wantErr: false, setup: func() *sql.DB { db, mock, err := sqlmock.New() @@ -691,8 +695,8 @@ func Test_mySqlDB_getVersionID(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+) LIVE")).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_APP) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) return db }, @@ -702,7 +706,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { fields: fields{ cfg: config.Database{ Queries: config.Queries{ - DisplayVersionInnerQuery: "^SELECT (.+) FROM wrapper_version (.+)", + DisplayVersionInnerQuery: models.TestQuery, }, }, }, @@ -714,6 +718,7 @@ func Test_mySqlDB_getVersionID(t *testing.T) { expectedVersionID: 251, expectedDisplayVersionIDFromDB: 9, + expectedPlatform: "in-app", wantErr: false, setup: func() *sql.DB { db, mock, err := sqlmock.New() @@ -721,12 +726,96 @@ func Test_mySqlDB_getVersionID(t *testing.T) { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } - rowsWrapperVersion := sqlmock.NewRows([]string{"versionId", "displayVersionId"}).AddRow("251", "9") - mock.ExpectQuery(regexp.QuoteMeta("^SELECT (.+) FROM wrapper_version (.+)")).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_APP) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 3, 5890).WillReturnRows(rowsWrapperVersion) return db }, }, + { + name: "Platform is null", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + DisplayVersionInnerQuery: models.TestQuery, + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 2, + pubID: 5890, + }, + expectedVersionID: 123, + expectedDisplayVersionIDFromDB: 12, + expectedPlatform: "", + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("123", "12", nil) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 2, 5890).WillReturnRows(rowsWrapperVersion) + return db + }, + }, + { + name: "Platform is empty string", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: models.TestQuery, + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 0, + pubID: 5890, + }, + expectedVersionID: 251, + expectedDisplayVersionIDFromDB: 9, + expectedPlatform: "", + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", "") + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + return db + }, + }, + { + name: "Platform is not null", + fields: fields{ + cfg: config.Database{ + Queries: config.Queries{ + LiveVersionInnerQuery: models.TestQuery, + }, + }, + }, + args: args{ + profileID: 19109, + displayVersion: 0, + pubID: 5890, + }, + expectedVersionID: 251, + expectedDisplayVersionIDFromDB: 9, + expectedPlatform: "in-app", + wantErr: false, + setup: func() *sql.DB { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + rowsWrapperVersion := sqlmock.NewRows([]string{models.VersionID, models.DisplayVersionID, models.PLATFORM_KEY}).AddRow("251", "9", models.PLATFORM_APP) + mock.ExpectQuery(regexp.QuoteMeta(models.TestQuery)).WithArgs(19109, 5890).WillReturnRows(rowsWrapperVersion) + return db + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -734,17 +823,14 @@ func Test_mySqlDB_getVersionID(t *testing.T) { conn: tt.setup(), cfg: tt.fields.cfg, } - got, got1, err := db.getVersionID(tt.args.profileID, tt.args.displayVersion, tt.args.pubID) + gotVersionID, gotDisplayVersionID, gotPlatform, err := db.getVersionID(tt.args.profileID, tt.args.displayVersion, tt.args.pubID) if (err != nil) != tt.wantErr { t.Errorf("mySqlDB.getVersionID() error = %v, wantErr %v", err, tt.wantErr) return } - if got != tt.expectedVersionID { - t.Errorf("mySqlDB.getVersionID() got = %v, want %v", got, tt.expectedVersionID) - } - if got1 != tt.expectedDisplayVersionIDFromDB { - t.Errorf("mySqlDB.getVersionID() got1 = %v, want %v", got1, tt.expectedDisplayVersionIDFromDB) - } + assert.Equal(t, tt.expectedVersionID, gotVersionID) + assert.Equal(t, tt.expectedDisplayVersionIDFromDB, gotDisplayVersionID) + assert.Equal(t, tt.expectedPlatform, gotPlatform) }) } } diff --git a/modules/pubmatic/openwrap/models/constants.go b/modules/pubmatic/openwrap/models/constants.go index c6261bdac41..0d0d56ba230 100755 --- a/modules/pubmatic/openwrap/models/constants.go +++ b/modules/pubmatic/openwrap/models/constants.go @@ -494,6 +494,7 @@ const ( //DisplayVersionInnerQuery = "DisplayVersionInnerQuery" //LiveVersionInnerQuery = "LiveVersionInnerQuery" //PMSlotToMappings = "GetPMSlotToMappings" + TestQuery = "TestQuery" ) // constants for owlogger Integration Type From d8123665d291b52d5f1c66f3758e5a93be91aa22 Mon Sep 17 00:00:00 2001 From: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Fri, 17 May 2024 14:35:39 +0530 Subject: [PATCH 6/6] OTT-1168: Introduce SeatNonBid in hookoutcome (#pbs-3416) (#676) --- .gitignore | 2 +- Makefile | 6 +- analytics/pubmatic/logger_test.go | 86 +- analytics/pubstack/pubstack_module_test.go | 2 +- endpoints/openrtb2/amp_auction.go | 60 +- endpoints/openrtb2/amp_auction_test.go | 416 ++++++-- endpoints/openrtb2/auction.go | 84 +- endpoints/openrtb2/auction_test.go | 909 +++++++++++++++++- endpoints/openrtb2/ctv_auction.go | 13 +- endpoints/openrtb2/test_utils.go | 77 ++ endpoints/openrtb2/video_auction.go | 6 +- endpoints/openrtb2/video_auction_test.go | 107 ++- exchange/auction_response.go | 9 +- exchange/entities/entities_ow.go | 31 + exchange/exchange.go | 76 +- exchange/exchange_ow.go | 19 +- exchange/exchange_ow_test.go | 222 ++--- exchange/exchange_test.go | 617 +++++------- ...e_validation_enforce_one_bid_rejected.json | 2 +- exchange/seat_non_bids.go | 76 -- exchange/seat_non_bids_test.go | 116 --- hooks/hookexecution/enricher_test.go | 17 +- hooks/hookexecution/execution.go | 1 + hooks/hookexecution/executor.go | 2 +- hooks/hookexecution/outcome.go | 18 +- hooks/hookstage/invocation.go | 4 +- .../pubmatic/openwrap/auctionresponsehook.go | 7 +- .../openwrap/auctionresponsehook_test.go | 111 --- .../pubmatic/openwrap/beforevalidationhook.go | 1 + .../openwrap/beforevalidationhook_test.go | 2 + modules/pubmatic/openwrap/cache/mock/mock.go | 47 +- .../openwrap/database/mysql/adunit_config.go | 1 - .../pubmatic/openwrap/metrics/mock/mock.go | 217 +++-- modules/pubmatic/openwrap/nonbids.go | 23 +- modules/pubmatic/openwrap/nonbids_test.go | 66 +- modules/pubmatic/openwrap/util.go | 1 - openrtb_ext/response.go | 23 +- openrtb_ext/seat_non_bids.go | 112 +++ openrtb_ext/seat_non_bids_test.go | 191 ++++ scripts/coverage.sh | 4 +- validate.sh | 3 +- 41 files changed, 2533 insertions(+), 1254 deletions(-) create mode 100644 exchange/entities/entities_ow.go delete mode 100644 exchange/seat_non_bids.go delete mode 100644 exchange/seat_non_bids_test.go create mode 100644 openrtb_ext/seat_non_bids.go create mode 100644 openrtb_ext/seat_non_bids_test.go diff --git a/.gitignore b/.gitignore index 51200f7c41d..b6f85c4f5f7 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,4 @@ analytics/filesystem/testFiles/ *.swp *.swo pbsimage -manual_build +manual_build \ No newline at end of file diff --git a/Makefile b/Makefile index 73faa5fa2fa..1019e9e973b 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ all: deps test build-modules build # deps will clean out the vendor directory and use go mod for a fresh install deps: GOPROXY="https://proxy.golang.org" go mod vendor -v && go mod tidy -v - + # test will ensure that all of our dependencies are available and run validate.sh test: deps # If there is no indentation, Make will treat it as a directive for itself; otherwise, it's regarded as a shell script. @@ -37,10 +37,10 @@ format: # formatcheck runs format for diagnostics, without modifying the code formatcheck: ./scripts/format.sh -f false - + mockgen: mockgeninstall mockgendb mockgencache mockgenmetrics mockgenlogger mockgenpublisherfeature -# export GOPATH=~/go ; GOBIN=~/go/bin; export PATH=$PATH:$GOBIN +# export GOPATH=~/go ; GOBIN=~/go/bin; export PATH=$PATH:$GOBIN mockgeninstall: go install github.com/golang/mock/mockgen@v1.6.0 diff --git a/analytics/pubmatic/logger_test.go b/analytics/pubmatic/logger_test.go index f4b935a1147..06aa005f336 100644 --- a/analytics/pubmatic/logger_test.go +++ b/analytics/pubmatic/logger_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" "github.com/prebid/prebid-server/v2/analytics" "github.com/prebid/prebid-server/v2/exchange" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" @@ -114,9 +115,9 @@ func TestConvertNonBidToBid(t *testing.T) { nonBid: openrtb_ext.NonBid{ StatusCode: int(exchange.ResponseRejectedBelowFloor), ImpId: "imp1", - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ADomain: []string{"abc.com"}, DealID: "d1", @@ -1059,9 +1060,9 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ ID: "bid-id-2", }, }, @@ -1141,9 +1142,9 @@ func TestGetPartnerRecordsByImpForDefaultBids(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ ID: "bid-id-1", }, }, @@ -1313,9 +1314,9 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowDealFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, }, }, @@ -1343,9 +1344,9 @@ func TestGetPartnerRecordsByImpForSeatNonBid(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", W: 10, @@ -1444,9 +1445,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -1509,10 +1510,10 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { NonBid: []openrtb_ext.NonBid{ { ImpId: "imp1", - StatusCode: int((exchange.ResponseRejectedBelowFloor)), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + StatusCode: int(exchange.ResponseRejectedBelowFloor), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -1575,10 +1576,10 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { NonBid: []openrtb_ext.NonBid{ { ImpId: "imp1", - StatusCode: int((exchange.ResponseRejectedBelowFloor)), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + StatusCode: int(exchange.ResponseRejectedBelowFloor), + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -1642,9 +1643,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", }, @@ -1702,9 +1703,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", }, @@ -1765,9 +1766,9 @@ func TestGetPartnerRecordsByImpForSeatNonBidForFloors(t *testing.T) { { ImpId: "imp1", StatusCode: int(exchange.ResponseRejectedBelowFloor), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", Floors: &openrtb_ext.ExtBidPrebidFloors{ @@ -2186,9 +2187,9 @@ func TestGetPartnerRecordsByImpForBidIDCollisions(t *testing.T) { { ImpId: "imp1", StatusCode: int(nbr.LossBidLostToDealBid), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ + Ext: openrtb_ext.ExtNonBid{ + Prebid: openrtb_ext.ExtNonBidPrebid{ + Bid: openrtb_ext.ExtNonBidPrebidBid{ Price: 10, ID: "bid-id-1", BidId: "uuid", @@ -2485,8 +2486,8 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { NonBid: []openrtb_ext.NonBid{ { ImpId: "imp1", - StatusCode: int(nbr.LossBidLostToDealBid), - Ext: openrtb_ext.NonBidExt{}, + StatusCode: int(exchange.ResponseRejectedBelowDealFloor), + Ext: openrtb_ext.ExtNonBid{}, }, }, }, @@ -2511,7 +2512,10 @@ func TestGetPartnerRecordsByImpForBidExtFailure(t *testing.T) { NetECPM: 0, GrossECPM: 0, OriginalCur: models.USD, - Nbr: nbr.LossBidLostToDealBid.Ptr(), + Nbr: func() *openrtb3.NoBidReason { + a := exchange.ResponseRejectedBelowDealFloor + return &a + }(), }, }, }, diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 911de4c6959..55d2ab19248 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -99,7 +99,7 @@ func TestNewModuleSuccess(t *testing.T) { { ImpId: "123", StatusCode: 34, - Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}}, + Ext: openrtb_ext.ExtNonBid{Prebid: openrtb_ext.ExtNonBidPrebid{Bid: openrtb_ext.ExtNonBidPrebidBid{}}}, }, }, }, diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index bf8661c0a8c..17cb6beb2e6 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -117,6 +117,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // We can respect timeouts more accurately if we note the *real* start time, and use it // to compute the auction timeout. start := time.Now() + seatNonBid := &openrtb_ext.NonBidCollection{} hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAmp, deps.metricsEngine) @@ -137,6 +138,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h activityControl := privacy.ActivityControl{} defer func() { + // if AmpObject.AuctionResponse is nil then collect nonbids from all stage outcomes and set it in the AmpObject.SeatNonBid + // Nil AmpObject.AuctionResponse indicates the occurrence of a fatal error. + if ao.AuctionResponse == nil { + seatNonBid.Append(getNonBidsFromStageOutcomes(hookExecutor.GetOutcomes())) + ao.SeatNonBid = seatNonBid.Get() + } deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) deps.analytics.LogAmpObject(&ao, activityControl) @@ -163,7 +170,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // Process reject after parsing amp request, so we can use reqWrapper. // There is no body for AMP requests, so we pass a nil body and ignore the return value. if rejectErr != nil { - labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, nil, labels, ao, nil) + labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, nil, labels, ao, nil, *seatNonBid) return } @@ -279,8 +286,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h var response *openrtb2.BidResponse if auctionResponse != nil { response = auctionResponse.BidResponse + seatNonBid.Append(auctionResponse.SeatNonBid) } - ao.SeatNonBid = auctionResponse.GetSeatNonBid() ao.AuctionResponse = response rejectErr, isRejectErr := hookexecution.CastRejectErr(err) if err != nil && !isRejectErr { @@ -300,15 +307,21 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h glog.Errorf("/openrtb2/amp Critical error: %v", err) ao.Status = http.StatusInternalServerError ao.Errors = append(ao.Errors, err) + if ao.AuctionResponse != nil { + // this check ensures that we collect nonBids from stageOutcomes only once. + // there could be a case where ao.AuctionResponse nil and reqWrapper.RebuildRequest returns error + seatNonBid.Append(getNonBidsFromStageOutcomes(hookExecutor.GetOutcomes())) + ao.SeatNonBid = seatNonBid.Get() + } return } if isRejectErr { - labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, account, labels, ao, errL) + labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, account, labels, ao, errL, *seatNonBid) return } - labels, ao = sendAmpResponse(w, hookExecutor, auctionResponse, reqWrapper, account, labels, ao, errL) + labels, ao = sendAmpResponse(w, hookExecutor, auctionResponse, reqWrapper, account, labels, ao, errL, *seatNonBid) } func rejectAmpRequest( @@ -320,12 +333,13 @@ func rejectAmpRequest( labels metrics.Labels, ao analytics.AmpObject, errs []error, + seatNonBid openrtb_ext.NonBidCollection, ) (metrics.Labels, analytics.AmpObject) { response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()} ao.AuctionResponse = response ao.Errors = append(ao.Errors, rejectErr) - return sendAmpResponse(w, hookExecutor, &exchange.AuctionResponse{BidResponse: response}, reqWrapper, account, labels, ao, errs) + return sendAmpResponse(w, hookExecutor, &exchange.AuctionResponse{BidResponse: response}, reqWrapper, account, labels, ao, errs, seatNonBid) } func sendAmpResponse( @@ -337,6 +351,7 @@ func sendAmpResponse( labels metrics.Labels, ao analytics.AmpObject, errs []error, + seatNonBid openrtb_ext.NonBidCollection, ) (metrics.Labels, analytics.AmpObject) { var response *openrtb2.BidResponse if auctionResponse != nil { @@ -364,6 +379,8 @@ func sendAmpResponse( glog.Errorf("/openrtb2/amp Critical error unpacking targets: %v", err) ao.Errors = append(ao.Errors, fmt.Errorf("Critical error while unpacking AMP targets: %v", err)) ao.Status = http.StatusInternalServerError + seatNonBid.Append(getNonBidsFromStageOutcomes(hookExecutor.GetOutcomes())) + ao.SeatNonBid = seatNonBid.Get() return labels, ao } for key, value := range bidExt.Prebid.Targeting { @@ -392,7 +409,7 @@ func sendAmpResponse( } // Now JSONify the targets for the AMP response. ampResponse := AmpResponse{Targeting: targets} - ao, ampResponse.ORTB2.Ext = getExtBidResponse(hookExecutor, auctionResponse, reqWrapper, account, ao, errs) + ao, ampResponse.ORTB2.Ext = getExtBidResponse(hookExecutor, auctionResponse, reqWrapper, account, ao, errs, seatNonBid) ao.AmpTargetingValues = targets @@ -418,6 +435,7 @@ func getExtBidResponse( account *config.Account, ao analytics.AmpObject, errs []error, + seatNonBid openrtb_ext.NonBidCollection, ) (analytics.AmpObject, openrtb_ext.ExtBidResponse) { var response *openrtb2.BidResponse if auctionResponse != nil { @@ -447,6 +465,7 @@ func getExtBidResponse( Warnings: warnings, } + stageOutcomes := hookExecutor.GetOutcomes() // add debug information if requested if reqWrapper != nil { if reqWrapper.Test == 1 && eRErr == nil { @@ -458,7 +477,6 @@ func getExtBidResponse( } } - stageOutcomes := hookExecutor.GetOutcomes() ao.HookExecutionOutcome = stageOutcomes modules, warns, err := hookexecution.GetModulesJSON(stageOutcomes, reqWrapper.BidRequest, account) if err != nil { @@ -474,8 +492,12 @@ func getExtBidResponse( } } - setSeatNonBid(&extBidResponse, reqWrapper, auctionResponse) - + // collect seatNonBid from all stage-outcomes and set in the response.ext.prebid + seatNonBid.Append(getNonBidsFromStageOutcomes(stageOutcomes)) + ao.SeatNonBid = seatNonBid.Get() + if returnAllBidStatus(reqWrapper) { + setSeatNonBid(&extBidResponse, ao.SeatNonBid) + } return ao, extBidResponse } @@ -851,23 +873,3 @@ func setTrace(req *openrtb2.BidRequest, value string) error { return nil } - -// setSeatNonBid populates bidresponse.ext.prebid.seatnonbid if bidrequest.ext.prebid.returnallbidstatus is true -func setSeatNonBid(finalExtBidResponse *openrtb_ext.ExtBidResponse, request *openrtb_ext.RequestWrapper, auctionResponse *exchange.AuctionResponse) bool { - if finalExtBidResponse == nil || auctionResponse == nil || request == nil { - return false - } - reqExt, err := request.GetRequestExt() - if err != nil { - return false - } - prebid := reqExt.GetPrebid() - if prebid == nil || !prebid.ReturnAllBidStatus { - return false - } - if finalExtBidResponse.Prebid == nil { - finalExtBidResponse.Prebid = &openrtb_ext.ExtResponsePrebid{} - } - finalExtBidResponse.Prebid.SeatNonBid = auctionResponse.GetSeatNonBid() - return true -} diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 340f1f0d999..40ae577ffaa 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -1413,8 +1413,11 @@ func (s formatOverrideSpec) execute(t *testing.T) { } type mockAmpExchange struct { - lastRequest *openrtb2.BidRequest - requestExt json.RawMessage + lastRequest *openrtb2.BidRequest + requestExt json.RawMessage + returnError bool + setBidRequestToNil bool + seatNonBid openrtb_ext.NonBidCollection } var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ @@ -1427,6 +1430,13 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi } func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { + if m.returnError { + return nil, hookexecution.RejectError{ + NBR: 1, + Stage: hooks.StageBidderRequest.String(), + Hook: hookexecution.HookID{ModuleCode: "foobar", HookImplCode: "foo"}, + } + } r := auctionRequest.BidRequestWrapper m.lastRequest = r.BidRequest @@ -1460,7 +1470,11 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *excha response.Ext = json.RawMessage(fmt.Sprintf(`{"debug": {"httpcalls": {}, "resolvedrequest": %s}}`, resolvedRequest)) } - return &exchange.AuctionResponse{BidResponse: response}, nil + if m.setBidRequestToNil { + auctionRequest.BidRequestWrapper.BidRequest = nil + } + + return &exchange.AuctionResponse{BidResponse: response, SeatNonBid: m.seatNonBid}, nil } type mockAmpExchangeWarnings struct{} @@ -1660,16 +1674,21 @@ func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.Activit func TestBuildAmpObject(t *testing.T) { testCases := []struct { - description string - inTagId string - exchange *mockAmpExchange - inStoredRequest json.RawMessage - expectedAmpObject *analytics.AmpObject + description string + inTagId string + exchange *mockAmpExchange + inStoredRequest json.RawMessage + planBuilder hooks.ExecutionPlanBuilder + returnErrorFromHoldAuction bool + setRequestToNil bool + seatNonBidFromHoldAuction openrtb_ext.NonBidCollection + expectedAmpObject *analytics.AmpObject }{ { description: "Stored Amp request with nil body. Only the error gets logged", inTagId: "test", inStoredRequest: nil, + planBuilder: hooks.EmptyPlanBuilder{}, expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: []error{fmt.Errorf("unexpected end of JSON input")}, @@ -1679,24 +1698,163 @@ func TestBuildAmpObject(t *testing.T) { description: "Stored Amp request with no imps that should return error. Only the error gets logged", inTagId: "test", inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[],"tmax":500}`), + planBuilder: hooks.EmptyPlanBuilder{}, expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: []error{fmt.Errorf("data for tag_id='test' does not define the required imp array")}, }, }, { - description: "Wrong tag_id, error gets logged", + description: "Wrong tag_id, error and seatnonbid gets logged", inTagId: "unknown", inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), + planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockSeatNonBidHook{})}, expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: []error{fmt.Errorf("unexpected end of JSON input")}, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "AmpObject should contain seatNonBid when holdAuction returns error", + inTagId: "test", + inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), + planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockSeatNonBidHook{})}, + returnErrorFromHoldAuction: true, + expectedAmpObject: &analytics.AmpObject{ + Status: http.StatusInternalServerError, + Errors: []error{ + fmt.Errorf("[Module foobar (hook: foo) rejected request with code 1 at bidder_request stage]"), + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Device: &openrtb2.Device{ + IP: "192.0.2.1", + }, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":1}`), + }, + Imp: []openrtb2.Imp{ + { + ID: "some-impression-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + Secure: func(val int8) *int8 { return &val }(1), //(*int8)(1), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}`), + }, + }, + AT: 1, + TMax: 500, + Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{}},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`), + }, + }, + Origin: "", + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "AmpObject should contain seatNonBid when RebuildRequest returns error after holdAuction", + inTagId: "test", + inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), + planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockSeatNonBidHook{})}, + setRequestToNil: true, + seatNonBidFromHoldAuction: getNonBids(map[string][]openrtb_ext.NonBidParams{"pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: 100}}}), + expectedAmpObject: &analytics.AmpObject{ + Status: http.StatusInternalServerError, + Errors: []error{ + fmt.Errorf("[Module foobar (hook: foo) rejected request with code 1 at bidder_request stage]"), + }, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Device: &openrtb2.Device{ + IP: "192.0.2.1", + }, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":1}`), + }, + Imp: []openrtb2.Imp{ + { + ID: "some-impression-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + Secure: func(val int8) *int8 { return &val }(1), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}`), + }, + }, + AT: 1, + TMax: 500, + Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{}},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`), + }, + }, + AuctionResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ + AdM: "", + Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), + }}, + Seat: "", + }}, + Ext: json.RawMessage(`{ "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`), + }, + Origin: "", + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, }, }, { description: "Valid stored Amp request, correct tag_id, a valid response should be logged", inTagId: "test", inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), + planBuilder: hooks.EmptyPlanBuilder{}, expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: nil, @@ -1753,6 +1911,7 @@ func TestBuildAmpObject(t *testing.T) { inTagId: "test", inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`), exchange: &mockAmpExchange{requestExt: json.RawMessage(`{ "prebid": {"targeting": { "test_key": "test_value", "hb_appnexus_pb": "9999" } }, "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`)}, + planBuilder: hooks.EmptyPlanBuilder{}, expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: nil, @@ -1814,9 +1973,13 @@ func TestBuildAmpObject(t *testing.T) { // Set up test, declare a new mock logger every time exchange := test.exchange if exchange == nil { - exchange = &mockAmpExchange{} + exchange = &mockAmpExchange{ + returnError: test.returnErrorFromHoldAuction, + setBidRequestToNil: test.setRequestToNil, + seatNonBid: test.seatNonBidFromHoldAuction, + } } - actualAmpObject, endpoint := ampObjectTestSetup(t, test.inTagId, test.inStoredRequest, false, exchange) + actualAmpObject, endpoint := ampObjectTestSetup(t, test.inTagId, test.inStoredRequest, false, exchange, test.planBuilder) // Run test endpoint(recorder, request, nil) @@ -1835,6 +1998,7 @@ func TestBuildAmpObject(t *testing.T) { assert.Equalf(t, test.expectedAmpObject.AuctionResponse, actualAmpObject.AuctionResponse, "Amp Object BidResponse doesn't match expected: %s\n", test.description) assert.Equalf(t, test.expectedAmpObject.AmpTargetingValues, actualAmpObject.AmpTargetingValues, "Amp Object AmpTargetingValues doesn't match expected: %s\n", test.description) assert.Equalf(t, test.expectedAmpObject.Origin, actualAmpObject.Origin, "Amp Object Origin field doesn't match expected: %s\n", test.description) + assert.Equalf(t, test.expectedAmpObject.SeatNonBid, actualAmpObject.SeatNonBid, "Amp Object SeatNonBid field doesn't match expected: %s\n", test.description) } } @@ -1890,13 +2054,13 @@ func TestIdGeneration(t *testing.T) { for _, test := range testCases { // Set up and run test - actualAmpObject, endpoint := ampObjectTestSetup(t, "test", test.givenInStoredRequest, test.givenGenerateRequestID, &mockAmpExchange{}) + actualAmpObject, endpoint := ampObjectTestSetup(t, "test", test.givenInStoredRequest, test.givenGenerateRequestID, &mockAmpExchange{}, hooks.EmptyPlanBuilder{}) endpoint(recorder, request, nil) assert.Equalf(t, test.expectedID, actualAmpObject.RequestWrapper.ID, "Bid Request ID is incorrect: %s\n", test.description) } } -func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMessage, generateRequestID bool, exchange *mockAmpExchange) (*analytics.AmpObject, httprouter.Handle) { +func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMessage, generateRequestID bool, exchange *mockAmpExchange, planBuilder hooks.ExecutionPlanBuilder) (*analytics.AmpObject, httprouter.Handle) { actualAmpObject := analytics.AmpObject{} logger := newMockLogger(&actualAmpObject, nil) @@ -1919,7 +2083,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}, + planBuilder, nil, ) return &actualAmpObject, endpoint @@ -2324,7 +2488,7 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { account := &config.Account{DebugAllow: true} reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request} - _, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) + labels, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil, openrtb_ext.NonBidCollection{}) assert.Equal(t, test.expectedErrors, ao.Errors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") @@ -2344,63 +2508,205 @@ func (e errorResponseWriter) Write(bytes []byte) (int, error) { func (e errorResponseWriter) WriteHeader(statusCode int) {} -func TestSetSeatNonBid(t *testing.T) { +func TestGetExtBidResponse(t *testing.T) { type args struct { - finalExtBidResponse *openrtb_ext.ExtBidResponse - request *openrtb_ext.RequestWrapper - auctionResponse *exchange.AuctionResponse + hookExecutor hookexecution.HookStageExecutor + auctionResponse *exchange.AuctionResponse + reqWrapper *openrtb_ext.RequestWrapper + account *config.Account + ao analytics.AmpObject + errs []error + seatNonBid openrtb_ext.NonBidCollection + } + type want struct { + respExt openrtb_ext.ExtBidResponse + ao analytics.AmpObject } tests := []struct { name string args args - want bool + want want }{ { - name: "nil-auctionResponse", - args: args{auctionResponse: nil}, - want: false, - }, - { - name: "nil-request", - args: args{auctionResponse: &exchange.AuctionResponse{}, request: nil}, - want: false, - }, - { - name: "invalid-req-ext", - args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`invalid json`)}}}, - want: false, - }, - { - name: "nil-prebid", - args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: nil}}}, - want: false, - }, - { - name: "returnallbidstatus-is-false", - args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : false}}`)}}}, - want: false, + name: "seatNonBid: returnallbidstatus is true and nonBids is empty", + args: args{ + hookExecutor: mockStageExecutor{ + outcomes: []hookexecution.StageOutcome{}, + }, + auctionResponse: &exchange.AuctionResponse{ + BidResponse: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{}`), + }, + }, + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"returnallbidstatus": true}}`), + }, + }, + }, + want: want{ + respExt: openrtb_ext.ExtBidResponse{ + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + }, + ao: analytics.AmpObject{ + SeatNonBid: nil, + }, + }, }, { - name: "finalExtBidResponse-is-nil", - args: args{finalExtBidResponse: nil}, - want: false, + name: "seatNonBid: returnallbidstatus is true and nonBids is present", + args: args{ + hookExecutor: mockStageExecutor{ + outcomes: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{"pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp"}, NonBidReason: 100}}}), + }, + }, + }, + }, + }, + }, + }, + auctionResponse: &exchange.AuctionResponse{ + BidResponse: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{}`), + }, + }, + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"returnallbidstatus": true}}`), + }, + }, + ao: analytics.AmpObject{}, + seatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: 100}}, + }), + }, + want: want{ + respExt: openrtb_ext.ExtBidResponse{ + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + Prebid: &openrtb_ext.ExtResponsePrebid{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, + ao: analytics.AmpObject{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, }, { - name: "returnallbidstatus-is-true-and-responseExt.Prebid-is-nil", - args: args{finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: nil}, auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : true}}`)}}}, - want: true, + name: "seatNonBid: returnallbidstatus is false and nonBids is present", + args: args{ + hookExecutor: mockStageExecutor{}, + auctionResponse: &exchange.AuctionResponse{ + BidResponse: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{}`), + }, + }, + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"returnallbidstatus": false}}`), + }, + }, + ao: analytics.AmpObject{}, + seatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: 100}}, + }), + }, + want: want{ + respExt: openrtb_ext.ExtBidResponse{ + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + }, + ao: analytics.AmpObject{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, }, { - name: "returnallbidstatus-is-true-and-responseExt.Prebid-is-not-nil", - args: args{finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: nil}, auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : true}}`)}}}, - want: true, + name: "seatNonBid: reqWrapper is nil and nonBids is present then AmpObject should contain seatnonbid", + args: args{ + hookExecutor: mockStageExecutor{ + outcomes: []hookexecution.StageOutcome{}, + }, + auctionResponse: &exchange.AuctionResponse{ + BidResponse: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{}`), + }, + }, + reqWrapper: nil, + seatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: 100}}, + }), + ao: analytics.AmpObject{}, + }, + want: want{ + respExt: openrtb_ext.ExtBidResponse{ + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + }, + ao: analytics.AmpObject{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := setSeatNonBid(tt.args.finalExtBidResponse, tt.args.request, tt.args.auctionResponse); got != tt.want { - t.Errorf("setSeatNonBid() = %v, want %v", got, tt.want) - } + ao, ext := getExtBidResponse(tt.args.hookExecutor, tt.args.auctionResponse, tt.args.reqWrapper, tt.args.account, tt.args.ao, tt.args.errs, tt.args.seatNonBid) + assert.Equal(t, tt.want.respExt, ext, "Found invalid bidResponseExt") + assert.Equal(t, tt.want.ao.SeatNonBid, ao.SeatNonBid, "Found invalid seatNonBid in ampObject") }) } } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 4e0b096dd81..4434876fbd0 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -165,6 +165,7 @@ 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() + seatNonBid := &openrtb_ext.NonBidCollection{} hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -184,6 +185,12 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activityControl := privacy.ActivityControl{} defer func() { + // if AuctionObject.Response is nil then collect nonbids from all stage outcomes and set it in the AuctionObject. + // Nil AuctionObject.Response indicates the occurrence of a fatal error. + if ao.Response == nil { + seatNonBid.Append(getNonBidsFromStageOutcomes(hookExecutor.GetOutcomes())) + ao.SeatNonBid = seatNonBid.Get() + } deps.metricsEngine.RecordRequest(labels) recordRejectedBids(labels.PubID, ao.SeatNonBid, deps.metricsEngine) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) @@ -199,7 +206,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil { ao.RequestWrapper = req - labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao) + labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao, seatNonBid) return } @@ -272,9 +279,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http var response *openrtb2.BidResponse if auctionResponse != nil { response = auctionResponse.BidResponse + seatNonBid.Append(auctionResponse.SeatNonBid) } ao.Response = response - ao.SeatNonBid = auctionResponse.GetSeatNonBid() rejectErr, isRejectErr := hookexecution.CastRejectErr(err) if err != nil && !isRejectErr { if errortypes.ReadCode(err) == errortypes.BadInputErrorCode { @@ -289,15 +296,11 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http ao.Errors = append(ao.Errors, err) return } else if isRejectErr { - labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao) + labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao, seatNonBid) return } - err = setSeatNonBidRaw(req, auctionResponse) - if err != nil { - glog.Errorf("Error setting seat non-bid: %v", err) - } - labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao) + labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao, seatNonBid) } // setSeatNonBidRaw is transitional function for setting SeatNonBid inside bidResponse.Ext @@ -305,18 +308,20 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http // 1. today exchange.HoldAuction prepares and marshals some piece of response.Ext which is then used by auction.go, amp_auction.go and video_auction.go // 2. As per discussion with Prebid Team we are planning to move away from - HoldAuction building openrtb2.BidResponse. instead respective auction modules will build this object // 3. So, we will need this method to do first, unmarshalling of response.Ext -func setSeatNonBidRaw(request *openrtb_ext.RequestWrapper, auctionResponse *exchange.AuctionResponse) error { - if auctionResponse == nil || auctionResponse.BidResponse == nil { +func setSeatNonBidRaw(request *openrtb_ext.RequestWrapper, response *openrtb2.BidResponse, nonBids []openrtb_ext.SeatNonBid) error { + if response == nil || !returnAllBidStatus(request) { return nil } + if response.Ext == nil { + response.Ext = json.RawMessage(`{}`) + } // unmarshalling is required here, until we are moving away from bidResponse.Ext, which is populated // by HoldAuction - response := auctionResponse.BidResponse respExt := &openrtb_ext.ExtBidResponse{} if err := jsonutil.Unmarshal(response.Ext, &respExt); err != nil { return err } - if setSeatNonBid(respExt, request, auctionResponse) { + if setSeatNonBid(respExt, nonBids) { if respExtJson, err := json.Marshal(respExt); err == nil { response.Ext = respExtJson return nil @@ -335,6 +340,7 @@ func rejectAuctionRequest( account *config.Account, labels metrics.Labels, ao analytics.AuctionObject, + seatNonBid *openrtb_ext.NonBidCollection, ) (metrics.Labels, analytics.AuctionObject) { response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()} if request != nil { @@ -349,7 +355,21 @@ func rejectAuctionRequest( ao.Response = response ao.Errors = append(ao.Errors, rejectErr) - return sendAuctionResponse(w, hookExecutor, response, request, account, labels, ao) + return sendAuctionResponse(w, hookExecutor, response, request, account, labels, ao, seatNonBid) +} + +func getNonBidsFromStageOutcomes(stageOutcomes []hookexecution.StageOutcome) openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + for _, stageOutcome := range stageOutcomes { + for _, groups := range stageOutcome.Groups { + for _, result := range groups.InvocationResults { + if result.Status == hookexecution.StatusSuccess { + seatNonBid.Append(result.SeatNonBid) + } + } + } + } + return seatNonBid } func sendAuctionResponse( @@ -360,13 +380,21 @@ func sendAuctionResponse( account *config.Account, labels metrics.Labels, ao analytics.AuctionObject, + seatNonBid *openrtb_ext.NonBidCollection, ) (metrics.Labels, analytics.AuctionObject) { hookExecutor.ExecuteAuctionResponseStage(response) + stageOutcomes := hookExecutor.GetOutcomes() + seatNonBid.Append(getNonBidsFromStageOutcomes(stageOutcomes)) + ao.SeatNonBid = seatNonBid.Get() + if response != nil { - stageOutcomes := hookExecutor.GetOutcomes() ao.HookExecutionOutcome = stageOutcomes UpdateResponseExtOW(w, response, ao) + err := setSeatNonBidRaw(ao.RequestWrapper, response, ao.SeatNonBid) + if err != nil { + glog.Errorf("Error setting seatNonBid in responseExt: %v", err) + } ext, warns, err := hookexecution.EnrichExtBidResponse(response.Ext, stageOutcomes, request, account) if err != nil { @@ -2581,3 +2609,31 @@ func (deps *endpointDeps) validateStoredBidRespAndImpExtBidders(prebid *openrtb_ func generateStoredBidResponseValidationError(impID string) error { return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) } + +// setSeatNonBid populates bidresponse.ext.prebid.seatnonbid +func setSeatNonBid(finalExtBidResponse *openrtb_ext.ExtBidResponse, seatNonBid []openrtb_ext.SeatNonBid) bool { + if finalExtBidResponse == nil || len(seatNonBid) == 0 { + return false + } + if finalExtBidResponse.Prebid == nil { + finalExtBidResponse.Prebid = &openrtb_ext.ExtResponsePrebid{} + } + finalExtBidResponse.Prebid.SeatNonBid = seatNonBid + return true +} + +// returnAllBidStatus function returns the value of bidrequest.ext.prebid.returnallbidstatus flag +func returnAllBidStatus(request *openrtb_ext.RequestWrapper) bool { + if request == nil { + return false + } + reqExt, err := request.GetRequestExt() + if err != nil { + return false + } + prebid := reqExt.GetPrebid() + if prebid == nil { + return false + } + return prebid.ReturnAllBidStatus +} diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index c008a819512..a22b6ee5c65 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -5807,7 +5807,7 @@ func TestValidResponseAfterExecutingStages(t *testing.T) { } } -func TestSendAuctionResponse_LogsErrors(t *testing.T) { +func TestSendAuctionResponse(t *testing.T) { hookExecutor := &mockStageExecutor{ outcomes: []hookexecution.StageOutcome{ { @@ -5824,6 +5824,14 @@ func TestSendAuctionResponse_LogsErrors(t *testing.T) { Status: hookexecution.StatusSuccess, Action: hookexecution.ActionNone, Warnings: []string{"warning message"}, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(exchange.ResponseRejectedCategoryMappingInvalid), + }, + }, + }), }, }, }, @@ -5831,50 +5839,134 @@ func TestSendAuctionResponse_LogsErrors(t *testing.T) { }, }, } - testCases := []struct { - description string - expectedErrors []error - expectedStatus int - request *openrtb2.BidRequest - response *openrtb2.BidResponse - hookExecutor hookexecution.HookStageExecutor + description string + expectedAuctionObject analytics.AuctionObject + expectedResponseBody string + request *openrtb2.BidRequest + response *openrtb2.BidResponse + hookExecutor hookexecution.HookStageExecutor + auctionObject analytics.AuctionObject }{ { description: "Error logged if hook enrichment fails", - expectedErrors: []error{ - errors.New("Failed to enrich Bid Response with hook debug information: Invalid JSON Document"), - errors.New("/openrtb2/auction Failed to send response: json: error calling MarshalJSON for type json.RawMessage: invalid character '.' looking for beginning of value"), + expectedAuctionObject: analytics.AuctionObject{ + Errors: []error{ + errors.New("Failed to enrich Bid Response with hook debug information: Invalid JSON Document"), + errors.New("/openrtb2/auction Failed to send response: json: error calling MarshalJSON for type json.RawMessage: invalid character '.' looking for beginning of value"), + }, + Status: 0, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedCategoryMappingInvalid), + }, + }, + Seat: "pubmatic", + }, + }, }, - expectedStatus: 0, - request: &openrtb2.BidRequest{ID: "some-id", Test: 1}, - response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("...")}, - hookExecutor: hookExecutor, + expectedResponseBody: "", + request: &openrtb2.BidRequest{ID: "some-id", Test: 1}, + response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("...")}, + hookExecutor: hookExecutor, + auctionObject: analytics.AuctionObject{}, }, { description: "Error logged if hook enrichment returns warnings", - expectedErrors: []error{ - errors.New("Value is not a string: 1"), - errors.New("Value is not a boolean: active"), + expectedAuctionObject: analytics.AuctionObject{ + Errors: []error{ + errors.New("Value is not a string: 1"), + errors.New("Value is not a boolean: active"), + }, + Status: 0, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedCategoryMappingInvalid), + }, + }, + Seat: "pubmatic", + }, + }, + }, + expectedResponseBody: "{\"id\":\"some-id\",\"ext\":{\"prebid\":{\"modules\":{\"warnings\":{\"foobar\":{\"foo\":[\"warning message\"]}}}}}}\n", + request: &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)}, + response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")}, + hookExecutor: hookExecutor, + auctionObject: analytics.AuctionObject{}, + }, + { + description: "Response should contain seatNonBid if returnallbidstatus is true", + expectedAuctionObject: analytics.AuctionObject{ + Errors: nil, + Status: 0, + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedCategoryMappingInvalid), + }, + }, + Seat: "pubmatic", + }, + }, + }, + expectedResponseBody: "{\"id\":\"some-id\",\"ext\":{\"prebid\":{\"modules\":{\"warnings\":{\"foobar\":{\"foo\":[\"warning message\"]}}}," + + "\"seatnonbid\":[{\"nonbid\":[{\"impid\":\"imp1\",\"statuscode\":303,\"ext\":{\"prebid\":{\"bid\":{}}}}],\"seat\":\"pubmatic\",\"ext\":null}]}}}\n", + request: &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`"returnallbidstatus": true}}`)}, + response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")}, + hookExecutor: hookExecutor, + auctionObject: analytics.AuctionObject{ + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": true}}`), + }, + }, }, - expectedStatus: 0, - request: &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)}, - response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")}, - hookExecutor: hookExecutor, + }, + { + description: "Expect seatNonBid in auctionObject even if response is nil", + expectedAuctionObject: analytics.AuctionObject{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: int(exchange.ResponseRejectedCategoryMappingInvalid), + }, + }, + Seat: "pubmatic", + }, + }, + }, + expectedResponseBody: "null\n", + request: &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": true, "trace":" 1"}}`)}, + response: nil, + hookExecutor: hookExecutor, + auctionObject: analytics.AuctionObject{}, }, } - for _, test := range testCases { t.Run(test.description, func(t *testing.T) { writer := httptest.NewRecorder() labels := metrics.Labels{} - ao := analytics.AuctionObject{} account := &config.Account{DebugAllow: true} + if test.auctionObject.RequestWrapper != nil { + test.auctionObject.RequestWrapper.RebuildRequest() + } - _, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) + _, ao := sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, test.auctionObject, &openrtb_ext.NonBidCollection{}) - assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") - assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") + assert.Equal(t, test.expectedAuctionObject.Errors, ao.Errors, "Invalid errors.") + assert.Equal(t, test.expectedAuctionObject.Status, ao.Status, "Invalid HTTP response status.") + assert.Equal(t, test.expectedResponseBody, writer.Body.String(), "Invalid response body.") + assert.Equal(t, test.expectedAuctionObject.SeatNonBid, ao.SeatNonBid, "Invalid seatNonBid present in auctionObject.") }) } } @@ -6015,46 +6107,112 @@ func (e mockStageExecutor) GetOutcomes() []hookexecution.StageOutcome { func TestSetSeatNonBidRaw(t *testing.T) { type args struct { - request *openrtb_ext.RequestWrapper - auctionResponse *exchange.AuctionResponse + request *openrtb_ext.RequestWrapper + response *openrtb2.BidResponse + nonBids []openrtb_ext.SeatNonBid + } + type want struct { + error bool + response *openrtb2.BidResponse } tests := []struct { - name string - args args - wantErr bool + name string + args args + want want }{ { - name: "nil-auctionResponse", - args: args{auctionResponse: nil}, - wantErr: false, + name: "nil response", + args: args{response: nil}, + want: want{ + error: false, + response: nil, + }, }, { - name: "nil-bidResponse", - args: args{auctionResponse: &exchange.AuctionResponse{BidResponse: nil}}, - wantErr: false, + name: "returnallbidstatus false", + args: args{response: &openrtb2.BidResponse{}, + request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : false }}`)}}}, + want: want{ + error: false, + response: &openrtb2.BidResponse{}, + }, }, { - name: "invalid-response.Ext", - args: args{auctionResponse: &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{Ext: []byte(`invalid_json`)}}}, - wantErr: true, + name: "invalid responseExt", + args: args{ + request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}}, + response: &openrtb2.BidResponse{Ext: []byte(`{invalid}`)}, + nonBids: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 1, + }, + }, + }, + }, + }, + want: want{ + error: true, + response: &openrtb2.BidResponse{Ext: []byte(`{invalid}`)}, + }, }, { - name: "update-seatnonbid-in-ext", + name: "returnallbistatus is true, update seatnonbid in nil responseExt", args: args{ - request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}}, - auctionResponse: &exchange.AuctionResponse{ - ExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{SeatNonBid: []openrtb_ext.SeatNonBid{}}}, - BidResponse: &openrtb2.BidResponse{Ext: []byte(`{}`)}, + request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}}, + response: &openrtb2.BidResponse{Ext: nil}, + nonBids: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 1, + }, + }, + }, + }, + }, + want: want{ + error: false, + response: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp","statuscode":1,"ext":{"prebid":{"bid":{}}}}],"seat":"pubmatic","ext":null}]}}`), + }, + }, + }, + { + name: "returnallbidstatus is true, update seatnonbid in non-nil responseExt", + args: args{ + request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}}, + response: &openrtb2.BidResponse{Ext: []byte(`{}`)}, + nonBids: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 1, + }, + }, + }, + }, + }, + want: want{ + error: false, + response: &openrtb2.BidResponse{ + Ext: json.RawMessage(`{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp","statuscode":1,"ext":{"prebid":{"bid":{}}}}],"seat":"pubmatic","ext":null}]}}`), }, }, - wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := setSeatNonBidRaw(tt.args.request, tt.args.auctionResponse); (err != nil) != tt.wantErr { - t.Errorf("setSeatNonBidRaw() error = %v, wantErr %v", err, tt.wantErr) - } + err := setSeatNonBidRaw(tt.args.request, tt.args.response, tt.args.nonBids) + assert.Equal(t, err != nil, tt.want.error, "mismatched error.") + assert.Equal(t, tt.args.response, tt.want.response, "mismatched bidResponse.") }) } } @@ -6507,3 +6665,654 @@ func TestValidateRequestCookieDeprecation(t *testing.T) { assert.Equal(t, test.wantCDep, deviceExt.GetCDep()) } } + +func TestGetNonBidsFromStageOutcomes(t *testing.T) { + tests := []struct { + name string + stageOutcomes []hookexecution.StageOutcome + expectedNonBids openrtb_ext.NonBidCollection + }{ + { + name: "nil groups", + stageOutcomes: []hookexecution.StageOutcome{ + { + Groups: nil, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{}), + }, + { + name: "nil and empty invocation results", + stageOutcomes: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: nil, + }, + { + InvocationResults: []hookexecution.HookOutcome{}, + }, + }, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{}), + }, + { + name: "single nonbid with failure hookoutcome status", + stageOutcomes: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusExecutionFailure, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{}), + }, + { + name: "single nonbid with success hookoutcome status", + stageOutcomes: []hookexecution.StageOutcome{ + { + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + { + name: "seatNonBid from multi stage outcomes", + stageOutcomes: []hookexecution.StageOutcome{ + { + Stage: hooks.StageAllProcessedBidResponses.String(), + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + { + Stage: hooks.StageBidderRequest.String(), + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "appnexus": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "appnexus": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + { + name: "seatNonBid for same seat from multi stage outcomes", + stageOutcomes: []hookexecution.StageOutcome{ + { + Stage: hooks.StageAllProcessedBidResponses.String(), + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + { + Stage: hooks.StageBidderRequest.String(), + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp2"}, + NonBidReason: 100, + }, + }, + }), + }, + }, + }, + }, + }, + }, + expectedNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: 100, + }, + { + Bid: &openrtb2.Bid{ImpID: "imp2"}, + NonBidReason: 100, + }, + }, + }), + }, + { + name: "multi group outcomes with empty nonbids", + stageOutcomes: []hookexecution.StageOutcome{ + { + Stage: hooks.StageAllProcessedBidResponses.String(), + Groups: []hookexecution.GroupOutcome{ + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: openrtb_ext.NonBidCollection{}, + }, + }, + }, + { + InvocationResults: []hookexecution.HookOutcome{ + { + Status: hookexecution.StatusSuccess, + SeatNonBid: openrtb_ext.NonBidCollection{}, + }, + }, + }, + }, + }, + }, + expectedNonBids: openrtb_ext.NonBidCollection{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nonBids := getNonBidsFromStageOutcomes(tt.stageOutcomes) + assert.Equal(t, nonBids, tt.expectedNonBids, "getNonBidsFromStageOutcomes returned incorrect nonBids") + }) + } +} + +// getNonBids is utility function which forms NonBidCollection from NonBidParams input +func getNonBids(bidParamsMap map[string][]openrtb_ext.NonBidParams) openrtb_ext.NonBidCollection { + nonBids := openrtb_ext.NonBidCollection{} + for bidder, bidParams := range bidParamsMap { + for _, bidParam := range bidParams { + nonBid := openrtb_ext.NewNonBid(bidParam) + nonBids.AddBid(nonBid, bidder) + } + } + return nonBids +} + +func TestSeatNonBidInAuction(t *testing.T) { + type args struct { + bidRequest openrtb2.BidRequest + seatNonBidFromHoldAuction openrtb_ext.NonBidCollection + errorFromHoldAuction error + rejectRawAuctionHook bool + errorFromHook error + } + type want struct { + statusCode int + body string + seatNonBid []openrtb_ext.SeatNonBid + } + testCases := []struct { + description string + args args + want want + }{ + { + description: "request parsing failed, auctionObject should contain seatNonBid", + args: args{ + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + }, + }, + }, + }, + want: want{ + body: "Invalid request: request.imp[0].ext is required\n", + statusCode: 400, + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "auctionObject and bidResponseExt should contain seatNonBid when returnallbidstatus is true", + args: args{ + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + Ext: json.RawMessage(`{"prebid": {"bidder":{"pubmatic":{"publisherid":1234}}}}`), + }, + }, + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": true}}`), + }, + }, + want: want{ + statusCode: 200, + body: `{"id":"","seatbid":[{"bid":[{"id":"","impid":"","price":0,"adm":""}]}],"ext":{"prebid":` + + `{"seatnonbid":[{"nonbid":[{"impid":"imp","statuscode":100,"ext":{"prebid":{"bid":{}}}}],"seat":"pubmatic","ext":null}]}}}` + "\n", + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "auctionObject should contain seatNonBid from both holdAuction and hookOutcomes", + args: args{ + seatNonBidFromHoldAuction: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "appnexus": { + { + Bid: &openrtb2.Bid{ImpID: "imp"}, + NonBidReason: 100, + }, + }, + }), + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + Ext: json.RawMessage(`{"prebid": {"bidder":{"pubmatic":{"publisherid":1234}}}}`), + }, + }, + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": false}}`), + }, + }, + want: want{ + statusCode: 200, + body: `{"id":"","seatbid":[{"bid":[{"id":"","impid":"","price":0,"adm":""}]}]}` + "\n", + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + { + Seat: "appnexus", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "hookexecutor returns hook-reject error after parseRequest, seatNonBid should be present in auctionObject and bidResponseExt", + args: args{ + rejectRawAuctionHook: true, + errorFromHook: &hookexecution.RejectError{Stage: hooks.StageEntrypoint.String(), NBR: 5}, + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + Ext: json.RawMessage(`{"prebid": {"bidder":{"pubmatic":{"publisherid":1234}}}}`), + }, + }, + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": true}}`), + }, + }, + want: want{ + statusCode: 200, + body: `{"id":"id","nbr":10,"ext":{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp","statuscode":100,` + + `"ext":{"prebid":{"bid":{}}}}],"seat":"pubmatic","ext":null}]}}}` + "\n", + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "holdAuction returns hookRejection error, seatNonBid should be present in auctionObject and bidResponseExt", + args: args{ + errorFromHoldAuction: &hookexecution.RejectError{Stage: hooks.StageAllProcessedBidResponses.String(), NBR: 5}, + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + Ext: json.RawMessage(`{"prebid": {"bidder":{"pubmatic":{"publisherid":1234}}}}`), + }, + }, + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": true}}`), + }, + }, + want: want{ + statusCode: 200, + body: `{"id":"id","nbr":5,"ext":{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp","statuscode":100,"ext":{"prebid":{"bid":{}}}}],"seat":"pubmatic","ext":null}]}}}` + "\n", + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "holdAuction returns non-hookRejection error, seatNonBid should be present in auctionObject", + args: args{ + errorFromHoldAuction: errors.New("any-error"), + bidRequest: openrtb2.BidRequest{ + ID: "id", + Site: &openrtb2.Site{ + ID: "site-1", + }, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + W: openrtb2.Int64Ptr(100), + H: openrtb2.Int64Ptr(100), + }, + Ext: json.RawMessage(`{"prebid": {"bidder":{"pubmatic":{"publisherid":1234}}}}`), + }, + }, + Ext: json.RawMessage(`{"prebid": {"returnallbidstatus": true}}`), + }, + }, + want: want{ + statusCode: 500, + body: `Critical error while running the auction: any-error`, + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + reqBody, _ := json.Marshal(test.args.bidRequest) + mockAnalytics := mockAnalyticsModule{} + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &mockExchange{seatNonBid: test.args.seatNonBidFromHoldAuction, returnError: test.args.errorFromHoldAuction}, + mockBidderParamValidator{}, + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + &metricsConfig.NilMetricsEngine{}, + &mockAnalytics, + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + empty_fetcher.EmptyFetcher{}, + mockPlanBuilder{ + entrypointPlan: makePlan[hookstage.Entrypoint](mockSeatNonBidHook{}), + rawAuctionPlan: makePlan[hookstage.RawAuctionRequest]( + mockSeatNonBidHook{ + rejectRawAuctionHook: test.args.rejectRawAuctionHook, + returnError: test.args.errorFromHook, + }, + ), + }, + nil, + openrtb_ext.NormalizeBidderName, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(string(reqBody))) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + assert.Equal(t, test.want.statusCode, recorder.Result().StatusCode, "mismatched status code.") + assert.Equal(t, test.want.body, recorder.Body.String(), "mismatched response body.") + assert.ElementsMatch(t, test.want.seatNonBid, mockAnalytics.auctionObjects[0].SeatNonBid, "mismatched seat-non-bids.") + }) + } +} + +func TestSetSeatNonBid(t *testing.T) { + type args struct { + finalExtBidResponse *openrtb_ext.ExtBidResponse + seatNonBid []openrtb_ext.SeatNonBid + } + type want struct { + setSeatNonBid bool + finalExtBidResponse *openrtb_ext.ExtBidResponse + } + tests := []struct { + name string + args args + want want + }{ + { + name: "nil seatNonBid", + args: args{seatNonBid: nil, finalExtBidResponse: &openrtb_ext.ExtBidResponse{}}, + want: want{ + setSeatNonBid: false, + finalExtBidResponse: &openrtb_ext.ExtBidResponse{}, + }, + }, + { + name: "empty seatNonBid", + args: args{seatNonBid: []openrtb_ext.SeatNonBid{}, finalExtBidResponse: &openrtb_ext.ExtBidResponse{}}, + want: want{ + setSeatNonBid: false, + finalExtBidResponse: &openrtb_ext.ExtBidResponse{}, + }, + }, + { + name: "finalExtBidResponse is nil", + args: args{finalExtBidResponse: nil}, + want: want{ + setSeatNonBid: false, + finalExtBidResponse: nil, + }, + }, + { + name: "finalExtBidResponse prebid is non-nil", + args: args{seatNonBid: []openrtb_ext.SeatNonBid{{Seat: "pubmatic", NonBid: []openrtb_ext.NonBid{{ImpId: "imp1", StatusCode: 100}}}}, + finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{}}}, + want: want{ + setSeatNonBid: true, + finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }}, + }, + }, + { + name: "finalExtBidResponse prebid is nil", + args: args{finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: nil}, seatNonBid: []openrtb_ext.SeatNonBid{{Seat: "pubmatic", NonBid: []openrtb_ext.NonBid{{ImpId: "imp1", StatusCode: 100}}}}}, + want: want{ + setSeatNonBid: true, + finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{ + SeatNonBid: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + }, + Seat: "pubmatic", + }, + }, + }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := setSeatNonBid(tt.args.finalExtBidResponse, tt.args.seatNonBid) + assert.Equal(t, tt.want.setSeatNonBid, got, "setSeatNonBid returned invalid value") + assert.Equal(t, tt.want.finalExtBidResponse, tt.args.finalExtBidResponse, "setSeatNonBid incorrectly updated finalExtBidResponse") + }) + } +} diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index 06bd47cda7f..b4ac7212e30 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -121,6 +121,7 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R var response *openrtb2.BidResponse var err error var errL []error + seatNonBid := &openrtb_ext.NonBidCollection{} ao := analytics.AuctionObject{ Status: http.StatusOK, @@ -252,12 +253,16 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R ao.Errors = append(ao.Errors, err) return } - ao.SeatNonBid = auctionResponse.GetSeatNonBid() - err = setSeatNonBidRaw(ao.RequestWrapper, auctionResponse) + + response = auctionResponse.BidResponse + seatNonBid.Append(auctionResponse.SeatNonBid) + seatNonBid.Append(getNonBidsFromStageOutcomes(hookExecutor.GetOutcomes())) // append seatNonBids available in hook-stage-outcomes + ao.SeatNonBid = seatNonBid.Get() + // add seatNonBids in response.Ext based on 'returnallbidstatus' flag + err = setSeatNonBidRaw(ao.RequestWrapper, response, ao.SeatNonBid) if err != nil { - glog.Errorf("Error setting seat non-bid: %v", err) + util.JLogf("Error setting seatNonBid in responseExt: %v", err) //TODO: REMOVE LOG } - response = auctionResponse.BidResponse util.JLogf("BidResponse", response) //TODO: REMOVE LOG if deps.isAdPodRequest { diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index 9ad0cf83fe2..00f365ec3bc 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -847,9 +847,15 @@ func (cf mockStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) // mockExchange implements the Exchange interface type mockExchange struct { lastRequest *openrtb2.BidRequest + seatNonBid openrtb_ext.NonBidCollection + returnError error } func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { + if m.returnError != nil { + return nil, m.returnError + } + r := auctionRequest.BidRequestWrapper m.lastRequest = r.BidRequest return &exchange.AuctionResponse{ @@ -860,6 +866,7 @@ func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest *exchange }}, }}, }, + SeatNonBid: m.seatNonBid, }, nil } @@ -1530,6 +1537,76 @@ func (m mockRejectionHook) HandleRawBidderResponseHook( return result, nil } +// mockSeatNonBidHook can be used to return seatNonBid from hook stage +type mockSeatNonBidHook struct { + rejectEntrypointHook bool + rejectRawAuctionHook bool + rejectProcessedAuctionHook bool + rejectBidderRequestHook bool + rejectRawBidderResponseHook bool + returnError error +} + +func (m mockSeatNonBidHook) HandleEntrypointHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + if m.rejectEntrypointHook { + return hookstage.HookResult[hookstage.EntrypointPayload]{NbrCode: 10, Reject: true}, m.returnError + } + result := hookstage.HookResult[hookstage.EntrypointPayload]{} + result.SeatNonBid = openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{Bid: &openrtb2.Bid{ImpID: "imp"}, NonBidReason: 100}) + result.SeatNonBid.AddBid(nonBid, "pubmatic") + + return result, m.returnError +} + +func (m mockSeatNonBidHook) HandleRawAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.RawAuctionRequestPayload, +) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + if m.rejectRawAuctionHook { + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{NbrCode: 10, Reject: true}, m.returnError + } + return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: false, NbrCode: 0}, m.returnError +} + +func (m mockSeatNonBidHook) HandleProcessedAuctionHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + _ hookstage.ProcessedAuctionRequestPayload, +) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) { + if m.rejectProcessedAuctionHook { + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{NbrCode: 10, Reject: true}, m.returnError + } + return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: 0}, m.returnError +} + +func (m mockSeatNonBidHook) HandleBidderRequestHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + payload hookstage.BidderRequestPayload, +) (hookstage.HookResult[hookstage.BidderRequestPayload], error) { + if m.rejectBidderRequestHook { + return hookstage.HookResult[hookstage.BidderRequestPayload]{NbrCode: 10, Reject: true}, m.returnError + } + return hookstage.HookResult[hookstage.BidderRequestPayload]{}, m.returnError +} + +func (m mockSeatNonBidHook) HandleRawBidderResponseHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + payload hookstage.RawBidderResponsePayload, +) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) { + if m.rejectRawBidderResponseHook { + return hookstage.HookResult[hookstage.RawBidderResponsePayload]{NbrCode: 10, Reject: true}, m.returnError + } + return hookstage.HookResult[hookstage.RawBidderResponsePayload]{}, m.returnError +} + var entryPointHookUpdateWithErrors = hooks.HookWrapper[hookstage.Entrypoint]{ Module: "foobar", Code: "foo", diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index fed79dd4a5d..c245e3fc2c1 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -122,6 +122,7 @@ func NewVideoEndpoint( func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { start := time.Now() + seatNonBid := &openrtb_ext.NonBidCollection{} vo := analytics.VideoObject{ Status: http.StatusOK, Errors: make([]error, 0), @@ -333,9 +334,10 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re var response *openrtb2.BidResponse if auctionResponse != nil { response = auctionResponse.BidResponse + seatNonBid.Append(auctionResponse.SeatNonBid) } vo.Response = response - vo.SeatNonBid = auctionResponse.GetSeatNonBid() + vo.SeatNonBid = seatNonBid.Get() if err != nil { errL := []error{err} handleError(&labels, w, errL, &vo, &debugLog) @@ -350,7 +352,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } if bidReq.Test == 1 { - err = setSeatNonBidRaw(bidReqWrapper, auctionResponse) + err = setSeatNonBidRaw(bidReqWrapper, response, vo.SeatNonBid) if err != nil { glog.Errorf("Error setting seat non-bid: %v", err) } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index caa152c1f99..56f56075bf4 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1363,6 +1363,7 @@ func (cf mockVideoStoredReqFetcher) FetchResponses(ctx context.Context, ids []st type mockExchangeVideo struct { lastRequest *openrtb2.BidRequest cache *mockCacheClient + seatNonBid openrtb_ext.NonBidCollection } func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { @@ -1397,7 +1398,9 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r *exchange.Auction {ID: "16", ImpID: "5_2", Ext: ext}, }, }}, - }}, nil + }, + SeatNonBid: m.seatNonBid}, nil + } type mockExchangeAppendBidderNames struct { @@ -1472,3 +1475,105 @@ func readVideoTestFile(t *testing.T, filename string) string { return string(getRequestPayload(t, requestData)) } + +func TestSeatNonBidInVideoAuction(t *testing.T) { + bidRequest := openrtb_ext.BidRequestVideo{ + Test: 1, + StoredRequestId: "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + PodConfig: openrtb_ext.PodConfig{ + DurationRangeSec: []int{30, 50}, + RequireExactDuration: true, + Pods: []openrtb_ext.Pod{ + {PodId: 1, AdPodDurationSec: 30, ConfigId: "fba10607-0c12-43d1-ad07-b8a513bc75d6"}, + }, + }, + App: &openrtb2.App{Bundle: "pbs.com"}, + Video: &openrtb2.Video{ + MIMEs: []string{"mp4"}, + Protocols: []adcom1.MediaCreativeSubtype{1}, + }, + } + + type args struct { + nonBidsFromHoldAuction openrtb_ext.NonBidCollection + } + type want struct { + seatNonBid []openrtb_ext.SeatNonBid + } + testCases := []struct { + description string + args args + want want + }{ + { + description: "holdAuction returns seatNonBid", + args: args{ + nonBidsFromHoldAuction: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ImpID: "imp"}, + NonBidReason: 100, + }, + }, + }), + }, + want: want{ + seatNonBid: []openrtb_ext.SeatNonBid{ + { + Seat: "pubmatic", + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp", + StatusCode: 100, + }, + }, + }, + }, + }, + }, + { + description: "holdAuction does not return seatNonBid", + args: args{ + nonBidsFromHoldAuction: openrtb_ext.NonBidCollection{}, + }, + want: want{ + seatNonBid: nil, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + ex := &mockExchangeVideo{seatNonBid: test.args.nonBidsFromHoldAuction} + analyticsModule := mockAnalyticsModule{} + deps := &endpointDeps{ + fakeUUIDGenerator{}, + ex, + mockBidderParamValidator{}, + &mockVideoStoredReqFetcher{}, + &mockVideoStoredReqFetcher{}, + &mockAccountFetcher{data: mockVideoAccountData}, + &config.Configuration{MaxRequestSize: maxSize}, + &metricsConfig.NilMetricsEngine{}, + &analyticsModule, + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + ex.cache, + regexp.MustCompile(`[<>]`), + hardcodedResponseIPValidator{response: true}, + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, + } + + reqBody, _ := json.Marshal(bidRequest) + req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(string(reqBody))) + recorder := httptest.NewRecorder() + deps.VideoAuctionEndpoint(recorder, req, nil) + + assert.Equal(t, test.want.seatNonBid, analyticsModule.videoObjects[0].SeatNonBid, "mismatched seatnonbid.") + }) + } +} diff --git a/exchange/auction_response.go b/exchange/auction_response.go index 1d0747b0fe5..b38318a75e4 100644 --- a/exchange/auction_response.go +++ b/exchange/auction_response.go @@ -9,12 +9,5 @@ import ( type AuctionResponse struct { *openrtb2.BidResponse ExtBidResponse *openrtb_ext.ExtBidResponse -} - -// GetSeatNonBid returns array of seat non-bid if present. nil otherwise -func (ar *AuctionResponse) GetSeatNonBid() []openrtb_ext.SeatNonBid { - if ar != nil && ar.ExtBidResponse != nil && ar.ExtBidResponse.Prebid != nil { - return ar.ExtBidResponse.Prebid.SeatNonBid - } - return nil + SeatNonBid openrtb_ext.NonBidCollection } diff --git a/exchange/entities/entities_ow.go b/exchange/entities/entities_ow.go new file mode 100644 index 00000000000..1a8d9645fa2 --- /dev/null +++ b/exchange/entities/entities_ow.go @@ -0,0 +1,31 @@ +package entities + +import "github.com/prebid/prebid-server/v2/openrtb_ext" + +// GetNonBidParamsFromPbsOrtbBid function returns NonBidParams from PbsOrtbBid +func GetNonBidParamsFromPbsOrtbBid(bid *PbsOrtbBid, seat string) openrtb_ext.NonBidParams { + adapterCode := seat + if bid.AlternateBidderCode != "" { + adapterCode = string(openrtb_ext.BidderName(bid.AlternateBidderCode)) + } + if bid.BidMeta == nil { + bid.BidMeta = &openrtb_ext.ExtBidPrebidMeta{} + } + bid.BidMeta.AdapterCode = adapterCode + return openrtb_ext.NonBidParams{ + Bid: bid.Bid, + OriginalBidCPM: bid.OriginalBidCPM, + OriginalBidCur: bid.OriginalBidCur, + DealPriority: bid.DealPriority, + DealTierSatisfied: bid.DealTierSatisfied, + GeneratedBidID: bid.GeneratedBidID, + TargetBidderCode: bid.TargetBidderCode, + OriginalBidCPMUSD: bid.OriginalBidCPMUSD, + BidMeta: bid.BidMeta, + BidType: bid.BidType, + BidTargets: bid.BidTargets, + BidVideo: bid.BidVideo, + BidEvents: bid.BidEvents, + BidFloors: bid.BidFloors, + } +} diff --git a/exchange/exchange.go b/exchange/exchange.go index c5ded5b62ee..02afe83a620 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -250,6 +250,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog return nil, err } + // seatNonBid will store nonbids collected inside HoldAuction function, it will not contain the nonbids from stageOutcomes + seatNonBid := openrtb_ext.NonBidCollection{} + // ensure prebid object always exists requestExtPrebid := requestExt.GetPrebid() if requestExtPrebid == nil { @@ -389,13 +392,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime } - var seatNonBids = nonBids{} - if anyBidsReturned { var rejectedBids []*entities.PbsOrtbSeatBid adapterBids, rejectedBids = applyBidPriceThreshold(adapterBids, r.Account, conversions) if len(rejectedBids) > 0 { - e.updateSeatNonBidsPriceThreshold(&seatNonBids, rejectedBids) + e.updateSeatNonBidsPriceThreshold(&seatNonBid, rejectedBids) } } @@ -423,7 +424,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if len(rejectedBids) > 0 { // Record rejected bid count at account level e.me.RecordRejectedBidsForAccount(r.PubID) - updateSeatNonBidsFloors(&seatNonBids, rejectedBids) + updateSeatNonBidsFloors(&seatNonBid, rejectedBids) } if responseDebugAllow { @@ -438,7 +439,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } } - adapterBids, rejections := applyAdvertiserBlocking(r, adapterBids, &seatNonBids) + adapterBids, rejections := applyAdvertiserBlocking(r, adapterBids, &seatNonBid) // add advertiser blocking specific errors for _, message := range rejections { errs = append(errs, errors.New(message)) @@ -447,7 +448,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog //If includebrandcategory is present in ext then CE feature is on. if requestExtPrebid.Targeting != nil && requestExtPrebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBid) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -545,18 +546,18 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog e.bidValidationEnforcement.SetBannerCreativeMaxSize(r.Account.Validations) // Build the response - bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs, &seatNonBids) + bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs, &seatNonBid) bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute) bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt) if err != nil { return nil, err } - bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBids) return &AuctionResponse{ BidResponse: bidResponse, ExtBidResponse: bidResponseExt, + SeatNonBid: seatNonBid, }, nil } @@ -985,7 +986,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb_ext.RequestWrapper, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error, seatNonBids *nonBids) *openrtb2.BidResponse { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb_ext.RequestWrapper, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error, seatNonBids *openrtb_ext.NonBidCollection) *openrtb2.BidResponse { bidResponse := new(openrtb2.BidResponse) bidResponse.ID = bidRequest.ID @@ -1020,7 +1021,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *nonBids) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *openrtb_ext.NonBidCollection) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) { bidRequest := r.BidRequestWrapper.BidRequest res := make(map[string]string) @@ -1083,6 +1084,8 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open duration = bid.BidVideo.Duration category = bid.BidVideo.PrimaryCategory } + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, seatBid.Seat) + nonBidParams.NonBidReason = int(ResponseRejectedCategoryMappingInvalid) if brandCatExt.WithCategory && category == "" { bidIabCat := bid.Bid.Cat @@ -1091,7 +1094,8 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open //on receiving bids from adapters if no unique IAB category is returned or if no ad server category is returned discard the bid bidsToRemove = append(bidsToRemove, bidInd) rejections = updateRejections(rejections, bidID, "Bid did not contain a category") - seatNonBids.addBid(bid, int(ResponseRejectedInvalidCategoryMapping), string(bidderName)) + nonBid := openrtb_ext.NewNonBid(nonBidParams) + seatNonBids.AddBid(nonBid, string(bidderName)) continue } if translateCategories { @@ -1101,7 +1105,10 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open //TODO: add metrics //if mapping required but no mapping file is found then discard the bid bidsToRemove = append(bidsToRemove, bidInd) - seatNonBids.addBid(bid, int(ResponseRejectedInvalidCategoryMapping), string(seatBid.Seat)) + nonBidParams.NonBidReason = int(ResponseRejectedCategoryMappingInvalid) + nonBid := openrtb_ext.NewNonBid(nonBidParams) + seatNonBids.AddBid(nonBid, string(seatBid.Seat)) + reason := fmt.Sprintf("Category mapping file for primary ad server: '%s', publisher: '%s' not found", primaryAdServer, publisher) rejections = updateRejections(rejections, bidID, reason) continue @@ -1119,7 +1126,9 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open newDur, err := findDurationRange(duration, targeting.DurationRangeSec) if err != nil { bidsToRemove = append(bidsToRemove, bidInd) - seatNonBids.addBid(bid, int(ResponseRejectedInvalidCategoryMapping), string(seatBid.Seat)) + nonBidParams.NonBidReason = int(ResponseRejectedCategoryMappingInvalid) + nonBid := openrtb_ext.NewNonBid(nonBidParams) + seatNonBids.AddBid(nonBid, string(seatBid.Seat)) rejections = updateRejections(rejections, bidID, err.Error()) continue } @@ -1171,7 +1180,8 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open if dupe.bidderName == bidderName { // An older bid from the current bidder bidsToRemove = append(bidsToRemove, dupe.bidIndex) - seatNonBids.addBid(bid, int(ResponseRejectedInvalidCategoryMapping), string(seatBid.Seat)) + nonBid := openrtb_ext.NewNonBid(nonBidParams) + seatNonBids.AddBid(nonBid, string(seatBid.Seat)) rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { // An older bid from a different seatBid we've already finished with @@ -1192,7 +1202,9 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open } else { // Remove this bid bidsToRemove = append(bidsToRemove, bidInd) - seatNonBids.addBid(bid, int(ResponseRejectedInvalidCategoryMapping), string(seatBid.Seat)) + nonBid := openrtb_ext.NewNonBid(nonBidParams) + + seatNonBids.AddBid(nonBid, string(seatBid.Seat)) rejections = updateRejections(rejections, bidID, "Bid was deduplicated") continue } @@ -1221,7 +1233,6 @@ func applyCategoryMapping(ctx context.Context, r *AuctionRequest, targeting open for _, seatBidInd := range seatBidsToRemove { seatBids[seatBidInd].Bids = nil } - return res, seatBids, rejections, nil } @@ -1344,7 +1355,7 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*en // Return an openrtb seatBid for a bidder // buildBidResponse is responsible for ensuring nil bid seatbids are not included -func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string, seatNonBids *nonBids) *openrtb2.SeatBid { +func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string, seatNonBids *openrtb_ext.NonBidCollection) *openrtb2.SeatBid { seatBid := &openrtb2.SeatBid{ Seat: adapter.String(), Group: 0, // Prebid cannot support roadblocking @@ -1359,7 +1370,7 @@ func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter open return seatBid } -func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, seatNonBids *nonBids) ([]openrtb2.Bid, []error) { +func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, seatNonBids *openrtb_ext.NonBidCollection) ([]openrtb2.Bid, []error) { result := make([]openrtb2.Bid, 0, len(bids)) errs := make([]error, 0, 1) @@ -1370,13 +1381,16 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea Message: fmt.Sprintf("bid rejected: %s", err.Error()), } bidResponseExt.Warnings[adapter] = append(bidResponseExt.Warnings[adapter], dsaMessage) - - seatNonBids.addBid(bid, int(ResponseRejectedGeneral), adapter.String()) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, adapter.String()) + nonBidParams.NonBidReason = int(ResponseRejectedGeneral) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), adapter.String()) continue // Don't add bid to result } if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationEnforce && bid.BidType == openrtb_ext.BidTypeBanner { if !e.validateBannerCreativeSize(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.BannerCreativeMaxSize) { - seatNonBids.addBid(bid, int(ResponseRejectedCreativeSizeNotAllowed), adapter.String()) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, adapter.String()) + nonBidParams.NonBidReason = int(ResponseRejectedCreativeSizeNotAllowed) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), adapter.String()) continue // Don't add bid to result } } else if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationWarn && bid.BidType == openrtb_ext.BidTypeBanner { @@ -1385,7 +1399,9 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea if _, ok := impExtInfoMap[bid.Bid.ImpID]; ok { if e.bidValidationEnforcement.SecureMarkup == config.ValidationEnforce && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) { if !e.validateBidAdM(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.SecureMarkup) { - seatNonBids.addBid(bid, int(ResponseRejectedCreativeNotSecure), adapter.String()) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, adapter.String()) + nonBidParams.NonBidReason = int(ResponseRejectedCreativeNotSecure) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), adapter.String()) continue // Don't add bid to result } } else if e.bidValidationEnforcement.SecureMarkup == config.ValidationWarn && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) { @@ -1693,19 +1709,3 @@ func setErrorMessageSecureMarkup(validationType string) string { } return "" } - -// setSeatNonBid adds SeatNonBids within bidResponse.Ext.Prebid.SeatNonBid -func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBids) *openrtb_ext.ExtBidResponse { - if len(seatNonBids.seatNonBidsMap) == 0 { - return bidResponseExt - } - if bidResponseExt == nil { - bidResponseExt = &openrtb_ext.ExtBidResponse{} - } - if bidResponseExt.Prebid == nil { - bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{} - } - - bidResponseExt.Prebid.SeatNonBid = seatNonBids.get() - return bidResponseExt -} diff --git a/exchange/exchange_ow.go b/exchange/exchange_ow.go index d316b1b8de3..9e9023d1cb7 100644 --- a/exchange/exchange_ow.go +++ b/exchange/exchange_ow.go @@ -97,7 +97,7 @@ func normalizeDomain(domain string) (string, error) { // applyAdvertiserBlocking rejects the bids of blocked advertisers mentioned in req.badv // the rejection is currently only applicable to vast tag bidders. i.e. not for ortb bidders // it returns seatbids containing valid bids and rejections containing rejected bid.id with reason -func applyAdvertiserBlocking(r *AuctionRequest, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, seatNonBids *nonBids) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string) { +func applyAdvertiserBlocking(r *AuctionRequest, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, seatNonBids *openrtb_ext.NonBidCollection) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string) { bidRequest := r.BidRequestWrapper.BidRequest rejections := []string{} nBadvs := []string{} @@ -141,7 +141,10 @@ func applyAdvertiserBlocking(r *AuctionRequest, seatBids map[openrtb_ext.BidderN } if rejectBid { // Add rejected bid in seatNonBid. - seatNonBids.addBid(bid, int(ResponseRejectedCreativeAdvertiserBlocking), seatBid.Seat) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(bid, seatBid.Seat) + nonBidParams.NonBidReason = int(ResponseRejectedCreativeAdvertiserBlocking) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), seatBid.Seat) + // reject the bid. bid belongs to blocked advertisers list seatBid.Bids = append(seatBid.Bids[:bidIndex], seatBid.Bids[bidIndex+1:]...) rejections = updateRejections(rejections, bid.Bid.ID, fmt.Sprintf("Bid (From '%s') belongs to blocked advertiser '%s'", bidderName, bAdv)) @@ -223,14 +226,16 @@ func recordPartnerTimeout(ctx context.Context, pubID, aliasBidder string) { } // updateSeatNonBidsFloors updates seatnonbid with rejectedBids due to floors -func updateSeatNonBidsFloors(seatNonBids *nonBids, rejectedBids []*entities.PbsOrtbSeatBid) { +func updateSeatNonBidsFloors(seatNonBids *openrtb_ext.NonBidCollection, rejectedBids []*entities.PbsOrtbSeatBid) { for _, pbsRejSeatBid := range rejectedBids { for _, pbsRejBid := range pbsRejSeatBid.Bids { var rejectionReason = ResponseRejectedBelowFloor if pbsRejBid.Bid.DealID != "" { rejectionReason = ResponseRejectedBelowDealFloor } - seatNonBids.addBid(pbsRejBid, int(rejectionReason), pbsRejSeatBid.Seat) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(pbsRejBid, pbsRejSeatBid.Seat) + nonBidParams.NonBidReason = int(rejectionReason) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), pbsRejSeatBid.Seat) } } } @@ -342,10 +347,12 @@ func logBidsAbovePriceThreshold(rejectedBids []*entities.PbsOrtbSeatBid) { } } -func (e exchange) updateSeatNonBidsPriceThreshold(seatNonBids *nonBids, rejectedBids []*entities.PbsOrtbSeatBid) { +func (e exchange) updateSeatNonBidsPriceThreshold(seatNonBids *openrtb_ext.NonBidCollection, rejectedBids []*entities.PbsOrtbSeatBid) { for _, pbsRejSeatBid := range rejectedBids { for _, pbsRejBid := range pbsRejSeatBid.Bids { - seatNonBids.addBid(pbsRejBid, int(ResponseRejectedBidPriceTooHigh), pbsRejSeatBid.Seat) + nonBidParams := entities.GetNonBidParamsFromPbsOrtbBid(pbsRejBid, pbsRejSeatBid.Seat) + nonBidParams.NonBidReason = int(ResponseRejectedBidPriceTooHigh) + seatNonBids.AddBid(openrtb_ext.NewNonBid(nonBidParams), pbsRejSeatBid.Seat) } } } diff --git a/exchange/exchange_ow_test.go b/exchange/exchange_ow_test.go index 3ec5637ba1b..1593e96e647 100644 --- a/exchange/exchange_ow_test.go +++ b/exchange/exchange_ow_test.go @@ -33,7 +33,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { type want struct { rejectedBidIds []string validBidCountPerSeat map[string]int - expectedseatNonBids nonBids + expectedseatNonBids openrtb_ext.NonBidCollection } tests := []struct { name string @@ -89,36 +89,28 @@ func TestApplyAdvertiserBlocking(t *testing.T) { }, }, want: want{ - expectedseatNonBids: nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ + expectedseatNonBids: getNonBids( + map[string][]openrtb_ext.NonBidParams{ "": { { - StatusCode: int(ResponseRejectedCreativeAdvertiserBlocking), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "reject_b.a.com.a.com.b.c.d.a.com", - ADomain: []string{"b.a.com.a.com.b.c.d.a.com"}, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, + NonBidReason: int(ResponseRejectedCreativeAdvertiserBlocking), + Bid: &openrtb2.Bid{ + ID: "reject_b.a.com.a.com.b.c.d.a.com", + ADomain: []string{"b.a.com.a.com.b.c.d.a.com"}, }, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, }, { - StatusCode: int(ResponseRejectedCreativeAdvertiserBlocking), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "a.com_bid", - ADomain: []string{"a.com"}, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, + NonBidReason: int(ResponseRejectedCreativeAdvertiserBlocking), + Bid: &openrtb2.Bid{ + ID: "a.com_bid", + ADomain: []string{"a.com"}, }, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, }, }, }, - }, + ), rejectedBidIds: []string{"a.com_bid", "reject_b.a.com.a.com.b.c.d.a.com"}, validBidCountPerSeat: map[string]int{ "vast_tag_bidder": 3, @@ -147,7 +139,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tab_bidder_1": 2, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -179,7 +171,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "tag_bidder_1": 0, // expect 0 bids. i.e. all bids are rejected "rtb_bidder_1": 2, // no bid must be rejected }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -203,7 +195,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tag_adaptor_1": 1, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -235,7 +227,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "tag_bidder_1": 2, "rtb_bidder_1": 2, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -267,7 +259,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "tag_bidder_1": 2, "rtb_bidder_1": 2, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -292,7 +284,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { want: want{ rejectedBidIds: []string{"bid_1_of_blocked_adv", "bid_2_of_blocked_adv"}, validBidCountPerSeat: map[string]int{"my_adapter": 1}, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { name: "multiple_badv", @@ -334,7 +326,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "tag_adapter_2": 0, "rtb_adapter_1": 1, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { name: "multiple_adomain", @@ -376,7 +368,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "tag_adapter_2": 0, "rtb_adapter_1": 1, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { name: "case_insensitive_badv", // case of domain not matters @@ -400,7 +392,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tag_adapter_1": 0, // expect all bids are rejected as belongs to blocked advertiser }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -425,7 +417,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tag_adapter_1": 0, // expect all bids are rejected as belongs to blocked advertiser }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -461,7 +453,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { "block_bidder": 0, "rtb_non_block_bidder": 4, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -488,7 +480,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "block_bidder": 1, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { name: "only_domain_test", // do not expect bid rejection. edu is valid domain @@ -514,7 +506,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tag_bidder": 3, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, { @@ -540,7 +532,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { validBidCountPerSeat: map[string]int{ "tag_bidder": 2, }, - expectedseatNonBids: nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, }, } @@ -560,7 +552,7 @@ func TestApplyAdvertiserBlocking(t *testing.T) { seatBids[adaptor.BidderName] = sbids } - seatNonBids := nonBids{} + seatNonBids := openrtb_ext.NonBidCollection{} // applyAdvertiserBlocking internally uses tagBidders from (adapter_map.go) // not testing alias here @@ -594,13 +586,17 @@ func TestApplyAdvertiserBlocking(t *testing.T) { continue // advertiser blocking is currently enabled only for tag bidders } - sort.Slice(seatNonBids.seatNonBidsMap[sBid.Seat], func(i, j int) bool { - return seatNonBids.seatNonBidsMap[sBid.Seat][i].Ext.Prebid.Bid.ID > seatNonBids.seatNonBidsMap[sBid.Seat][j].Ext.Prebid.Bid.ID + seatNonBidsMap := seatNonBids.GetSeatNonBidMap() + + sort.Slice(seatNonBidsMap[sBid.Seat], func(i, j int) bool { + return seatNonBidsMap[sBid.Seat][i].Ext.Prebid.Bid.ID > seatNonBidsMap[sBid.Seat][j].Ext.Prebid.Bid.ID }) - sort.Slice(tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat], func(i, j int) bool { - return tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat][i].Ext.Prebid.Bid.ID > tt.want.expectedseatNonBids.seatNonBidsMap[sBid.Seat][j].Ext.Prebid.Bid.ID + + expectedSeatNonBids := tt.want.expectedseatNonBids.GetSeatNonBidMap() + sort.Slice(expectedSeatNonBids[sBid.Seat], func(i, j int) bool { + return expectedSeatNonBids[sBid.Seat][i].Ext.Prebid.Bid.ID > expectedSeatNonBids[sBid.Seat][j].Ext.Prebid.Bid.ID }) - assert.Equal(t, tt.want.expectedseatNonBids.seatNonBidsMap, seatNonBids.seatNonBidsMap, "SeatNonBids not matching") + assert.Equal(t, expectedSeatNonBids, seatNonBidsMap, "SeatNonBids not matching") for _, bid := range sBid.Bids { if nil != bid.Bid.ADomain { @@ -1453,21 +1449,21 @@ func TestGetPriceBucketStringOW(t *testing.T) { func Test_updateSeatNonBidsFloors(t *testing.T) { type args struct { - seatNonBids *nonBids + seatNonBids *openrtb_ext.NonBidCollection rejectedBids []*entities.PbsOrtbSeatBid } tests := []struct { name string args args - expectedseatNonBids *nonBids + expectedseatNonBids openrtb_ext.NonBidCollection }{ { name: "nil rejectedBids", args: args{ rejectedBids: nil, - seatNonBids: &nonBids{}, + seatNonBids: &openrtb_ext.NonBidCollection{}, }, - expectedseatNonBids: &nonBids{}, + expectedseatNonBids: openrtb_ext.NonBidCollection{}, }, { name: "floors one rejectedBids in seatnonbid", @@ -1490,37 +1486,31 @@ func Test_updateSeatNonBidsFloors(t *testing.T) { Seat: "pubmatic", }, }, - seatNonBids: &nonBids{}, + seatNonBids: &openrtb_ext.NonBidCollection{}, }, - expectedseatNonBids: &nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - { - StatusCode: 301, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, + expectedseatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ + ID: "bid1", }, - { - StatusCode: 304, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid2", - DealID: "deal1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, + NonBidReason: 301, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", + }, + NonBidReason: 304, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, }, }, - }, + }), }, { name: "floors two rejectedBids in seatnonbid", @@ -1559,68 +1549,58 @@ func Test_updateSeatNonBidsFloors(t *testing.T) { Seat: "appnexus", }, }, - seatNonBids: &nonBids{}, + seatNonBids: &openrtb_ext.NonBidCollection{}, }, - expectedseatNonBids: &nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - { - StatusCode: 301, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, + expectedseatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ + "pubmatic": { + { + Bid: &openrtb2.Bid{ + ID: "bid1", }, - { - StatusCode: 304, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid2", - DealID: "deal1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, + NonBidReason: 301, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, }, - "appnexus": { - { - StatusCode: 301, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "appnexus"}, - }, - }, - }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", }, - { - StatusCode: 304, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - ID: "bid2", - DealID: "deal1", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "appnexus"}, - }, - }, - }, + NonBidReason: 304, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, }, }, - }, + "appnexus": { + { + Bid: &openrtb2.Bid{ + ID: "bid1", + }, + NonBidReason: 301, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "appnexus", + }, + }, + { + Bid: &openrtb2.Bid{ + ID: "bid2", + DealID: "deal1", + }, + NonBidReason: 304, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "appnexus", + }, + }, + }, + }), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { updateSeatNonBidsFloors(tt.args.seatNonBids, tt.args.rejectedBids) - assert.Equal(t, tt.expectedseatNonBids, tt.args.seatNonBids) + assert.Equal(t, tt.expectedseatNonBids, *tt.args.seatNonBids) }) } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 5449086936a..cd3ef3ea078 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -170,7 +170,7 @@ func TestCharacterEscape(t *testing.T) { var errList []error // 4) Build bid response - bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList, &nonBids{}) + bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList, &openrtb_ext.NonBidCollection{}) // 5) Assert we have no errors and one '&' character as we are supposed to if len(errList) > 0 { @@ -1343,7 +1343,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error // 4) Build bid response - bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList, &nonBids{}) + bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList, &openrtb_ext.NonBidCollection{}) expectedBidResponse := &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ @@ -1433,7 +1433,7 @@ func TestBidReturnsCreative(t *testing.T) { //Run tests for _, test := range testCases { - resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, &openrtb_ext.RequestWrapper{}, nil, "", "", &nonBids{}) + resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, &openrtb_ext.RequestWrapper{}, nil, "", "", &openrtb_ext.NonBidCollection{}) assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description) assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description) @@ -1718,7 +1718,7 @@ func TestBidResponseCurrency(t *testing.T) { } // Run tests for i := range testCases { - actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList, &nonBids{}) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList, &openrtb_ext.NonBidCollection{}) assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext)) } } @@ -1785,7 +1785,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { json.RawMessage(`{"imp_passthrough_val":1}`)} expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` - actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList, &nonBids{}) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList, &openrtb_ext.NonBidCollection{}) resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext) assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect") @@ -2223,10 +2223,10 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { aucResponse, err := ex.HoldAuction(ctx, auctionRequest, debugLog) var bid *openrtb2.BidResponse - var bidExt *openrtb_ext.ExtBidResponse + var seatnonbid *openrtb_ext.NonBidCollection if aucResponse != nil { bid = aucResponse.BidResponse - bidExt = aucResponse.ExtBidResponse + seatnonbid = &aucResponse.SeatNonBid } if len(spec.Response.Error) > 0 && spec.Response.Bids == nil { if err.Error() != spec.Response.Error { @@ -2324,8 +2324,8 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } assert.Equal(t, expectedBidRespExt.Errors, actualBidRespExt.Errors, "Expected errors from response ext do not match") } - if expectedBidRespExt.Prebid != nil { - assert.ElementsMatch(t, expectedBidRespExt.Prebid.SeatNonBid, bidExt.Prebid.SeatNonBid, "Expected seatNonBids from response ext do not match") + if len(spec.Response.SeatNonBids) > 0 { + assert.ElementsMatch(t, seatnonbid.Get(), spec.Response.SeatNonBids, "Expected seatNonBids from response ext do not match") } } @@ -2594,37 +2594,28 @@ func TestCategoryMapping(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - expectedseatNonBids := nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "": { - { - ImpId: "imp_id4", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 40.0000, - Cat: cats4, - W: 1, - H: 1, - OriginalBidCPM: 40, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 40, - - ID: "bid_id4", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 30, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, - }, - }, + expectedseatNonBids := getNonBids(map[string][]openrtb_ext.NonBidParams{ + "": { + { + Bid: &openrtb2.Bid{ImpID: "imp_id4", + ID: "bid_id4", + Price: 40.0000, + Cat: cats4, + W: 1, + H: 1, + }, + NonBidReason: int(ResponseRejectedCategoryMappingInvalid), + OriginalBidCPM: 40, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "", VASTTagID: ""}, + OriginalBidCPMUSD: 40, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, }, }, - } - seatNonBids := nonBids{} + }) + seatNonBids := openrtb_ext.NonBidCollection{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") @@ -2684,8 +2675,9 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - expectedseatNonBids := nonBids{} - seatNonBids := nonBids{} + expectedseatNonBids := openrtb_ext.NonBidCollection{} + seatNonBids := openrtb_ext.NonBidCollection{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") @@ -2706,7 +2698,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - r := AuctionRequest{ + r := &AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{}, }, @@ -2745,39 +2737,29 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - expectedseatNonBids := nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "": { - { - ImpId: "imp_id3", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 30.0000, - Cat: cats3, - W: 1, - H: 1, - OriginalBidCPM: 30, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 30, - - ID: "bid_id3", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 30, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, - }, - }, + expectedseatNonBids := getNonBids(map[string][]openrtb_ext.NonBidParams{ + "": { + { + Bid: &openrtb2.Bid{ImpID: "imp_id3", + ID: "bid_id3", + Price: 30.0000, + Cat: cats3, + W: 1, + H: 1, + }, + NonBidReason: int(ResponseRejectedCategoryMappingInvalid), + OriginalBidCPM: 30, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "", VASTTagID: ""}, + OriginalBidCPMUSD: 30, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, }, }, - } + }) + seatNonBids := openrtb_ext.NonBidCollection{} - seatNonBids := nonBids{} - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2863,8 +2845,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - expectedseatNonBids := nonBids{} - seatNonbids := nonBids{} + expectedseatNonBids := openrtb_ext.NonBidCollection{} + seatNonbids := openrtb_ext.NonBidCollection{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonbids) assert.Equal(t, nil, err, "Category mapping error should be empty") @@ -2949,7 +2932,7 @@ func TestCategoryDedupe(t *testing.T) { }, } deduplicateGenerator := fakeBooleanGenerator{value: tt.dedupeGeneratorValue} - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &openrtb_ext.NonBidCollection{}) assert.Nil(t, err) assert.Equal(t, 3, len(rejections)) @@ -3023,7 +3006,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &openrtb_ext.NonBidCollection{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -3092,8 +3075,10 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - expectedseatNonBids := nonBids{} - seatNonBids := nonBids{} + expectedseatNonBids := openrtb_ext.NonBidCollection{} + + seatNonBids := openrtb_ext.NonBidCollection{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.NoError(t, err, "Category mapping error should be empty") @@ -3153,9 +3138,9 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 + expectedseatNonBids := openrtb_ext.NonBidCollection{} + seatNonBids := openrtb_ext.NonBidCollection{} - expectedseatNonBids := nonBids{} - seatNonBids := nonBids{} bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) assert.NoError(t, err, "Category mapping error should be empty") @@ -3191,13 +3176,13 @@ func TestBidRejectionErrors(t *testing.T) { bidderName := openrtb_ext.BidderName("appnexus") testCases := []struct { - description string - reqExt openrtb_ext.ExtRequest - bids []*openrtb2.Bid - duration int - expectedRejections []string - expectedCatDur string - expectedseatNonBids nonBids + description string + reqExt openrtb_ext.ExtRequest + bids []*openrtb2.Bid + duration int + expectedRejections []string + expectedCatDur string + expectedSeatNonBid openrtb_ext.NonBidCollection }{ { description: "Bid should be rejected due to not containing a category", @@ -3209,36 +3194,25 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: Bid did not contain a category", }, - expectedseatNonBids: nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "appnexus": { - { - ImpId: "imp_id1", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 10.0000, - Cat: []string{}, - W: 1, - H: 1, - OriginalBidCPM: 10, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 10, - - ID: "bid_id1", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 30, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "appnexus"}, - }, - }, - }, - }, + expectedSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10, W: 1, H: 1, Cat: []string{}}, + NonBidReason: 303, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + PrimaryCategory: "", + VASTTagID: "", }, - }, - }, + OriginalBidCPMUSD: 10, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, + }) + seatNonBid.AddBid(nonBid, "appnexus") + return seatNonBid + }(), }, { description: "Bid should be rejected due to missing category mapping file", @@ -3250,36 +3224,25 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: Category mapping file for primary ad server: 'dfp', publisher: 'some_publisher' not found", }, - expectedseatNonBids: nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "": { - { - ImpId: "imp_id1", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 10.0000, - Cat: []string{"IAB1-1"}, - W: 1, - H: 1, - OriginalBidCPM: 10, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 10, - - ID: "bid_id1", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 30, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, - }, - }, + expectedSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, W: 1, H: 1, Cat: []string{"IAB1-1"}}, + NonBidReason: 303, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + PrimaryCategory: "", + VASTTagID: "", }, - }, - }, + OriginalBidCPMUSD: 10, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, + }) + seatNonBid.AddBid(nonBid, "") + return seatNonBid + }(), }, { description: "Bid should be rejected due to duration exceeding maximum", @@ -3291,36 +3254,25 @@ func TestBidRejectionErrors(t *testing.T) { expectedRejections: []string{ "bid rejected [bid ID: bid_id1] reason: bid duration exceeds maximum allowed", }, - expectedseatNonBids: nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "": { - { - ImpId: "imp_id1", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 10.0000, - Cat: []string{"IAB1-1"}, - W: 1, - H: 1, - OriginalBidCPM: 10, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 10, - - ID: "bid_id1", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 70, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, - }, - }, + expectedSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, W: 1, H: 1, Cat: []string{"IAB1-1"}}, + NonBidReason: 303, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 70, + PrimaryCategory: "", + VASTTagID: "", }, - }, - }, + OriginalBidCPMUSD: 10, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, + }) + seatNonBid.AddBid(nonBid, "") + return seatNonBid + }(), }, { description: "Bid should be rejected due to duplicate bid", @@ -3334,36 +3286,25 @@ func TestBidRejectionErrors(t *testing.T) { "bid rejected [bid ID: bid_id1] reason: Bid was deduplicated", }, expectedCatDur: "10.00_VideoGames_30s", - expectedseatNonBids: nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "": { - { - ImpId: "imp_id1", - StatusCode: int(ResponseRejectedInvalidCategoryMapping), - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Price: 10.0000, - Cat: []string{"IAB1-1"}, - W: 1, - H: 1, - OriginalBidCPM: 10, - OriginalBidCur: "USD", - OriginalBidCPMUSD: 10, - - ID: "bid_id1", - Type: openrtb_ext.BidTypeVideo, - Video: &openrtb_ext.ExtBidPrebidVideo{ - Duration: 30, - }, - Meta: &openrtb_ext.ExtBidPrebidMeta{}, - }, - }, - }, - }, + expectedSeatNonBid: func() openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, W: 1, H: 1, Cat: []string{"IAB1-1"}}, + NonBidReason: 303, + OriginalBidCPM: 10, + OriginalBidCur: "USD", + BidType: "video", + BidVideo: &openrtb_ext.ExtBidPrebidVideo{ + Duration: 30, + PrimaryCategory: "", + VASTTagID: "", }, - }, - }, + OriginalBidCPMUSD: 10, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{}, + }) + seatNonBid.AddBid(nonBid, "") + return seatNonBid + }(), }, } @@ -3374,16 +3315,15 @@ func TestBidRejectionErrors(t *testing.T) { Bid: bid, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: "", OriginalBidCPMUSD: 10.0000} innerBids = append(innerBids, ¤tBid) } - - seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"} - - adapterBids[bidderName] = &seatBid r := &AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{}, }, } - seatNonBids := nonBids{} + seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"} + + adapterBids[bidderName] = &seatBid + seatNonBids := openrtb_ext.NonBidCollection{} bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) if len(test.expectedCatDur) > 0 { @@ -3398,7 +3338,7 @@ func TestBidRejectionErrors(t *testing.T) { assert.Empty(t, err, "Category mapping error should be empty") assert.Equal(t, test.expectedRejections, rejections, test.description) - assert.Equal(t, test.expectedseatNonBids, seatNonBids, "Rejected Bids did not match for %v", test.description) + assert.Equal(t, test.expectedSeatNonBid, seatNonBids, "SeatNonBid doesn't match") } } @@ -3454,7 +3394,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, _, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &openrtb_ext.NonBidCollection{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -3543,9 +3483,10 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - expectedseatNonBids := nonBids{} - seatNonbids := nonBids{} - _, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeBooleanGenerator{value: true}, &seatNonbids) + expectedseatNonBids := openrtb_ext.NonBidCollection{} + seatNonBids := openrtb_ext.NonBidCollection{} + + _, adapterBids, rejections, err := applyCategoryMapping(nil, r, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeBooleanGenerator{value: true}, &seatNonBids) assert.NoError(t, err, "Category mapping error should be empty") @@ -3569,7 +3510,7 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned") assert.Len(t, rejections, 2, "2 bids should be de-duplicated") - assert.Equal(t, expectedseatNonBids, seatNonbids, "Bid Rejections not matching") + assert.Equal(t, expectedseatNonBids, seatNonBids, "Bid Rejections not matching") if firstBidderIndicator { assert.Len(t, adapterBids[bidderNameApn1].Bids, 2) assert.Len(t, adapterBids[bidderNameApn2].Bids, 0) @@ -5008,7 +4949,7 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids []*entities.PbsOrtbBid givenSeat openrtb_ext.BidderName expectedNumOfBids int - expectedNonBids *nonBids + expectedNonBids *openrtb_ext.NonBidCollection expectedNumDebugErrors int expectedNumDebugWarnings int }{ @@ -5019,22 +4960,18 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{Ext: json.RawMessage(`{"dsa": {"adrender":1}}`)}}, {Bid: &openrtb2.Bid{}}}, givenSeat: "pubmatic", expectedNumOfBids: 1, - expectedNonBids: &nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - { - StatusCode: 300, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, - }, + expectedNonBids: func() *openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{}, + NonBidReason: 300, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, - }, - }, + }) + seatNonBid.AddBid(nonBid, "pubmatic") + return &seatNonBid + }(), expectedNumDebugWarnings: 1, }, { @@ -5043,25 +4980,19 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 1, - expectedNonBids: &nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - { - StatusCode: 351, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - W: 200, - H: 200, - Type: "banner", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, - }, + expectedNonBids: func() *openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{W: 200, H: 200}, + NonBidReason: 351, + BidType: openrtb_ext.BidTypeBanner, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, - }, - }, + }) + seatNonBid.AddBid(nonBid, "pubmatic") + return &seatNonBid + }(), expectedNumDebugErrors: 1, }, { @@ -5070,7 +5001,7 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 2, - expectedNonBids: &nonBids{}, + expectedNonBids: &openrtb_ext.NonBidCollection{}, expectedNumDebugErrors: 1, }, { @@ -5079,24 +5010,19 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 1, - expectedNonBids: &nonBids{ - seatNonBidsMap: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - { - ImpId: "1", - StatusCode: 352, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{ - Bid: openrtb_ext.NonBidObject{ - Type: "banner", - Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "pubmatic"}, - }, - }, - }, - }, + expectedNonBids: func() *openrtb_ext.NonBidCollection { + seatNonBid := openrtb_ext.NonBidCollection{} + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{ + Bid: &openrtb2.Bid{ImpID: "1"}, + NonBidReason: 352, + BidType: openrtb_ext.BidTypeBanner, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + AdapterCode: "pubmatic", }, - }, - }, + }) + seatNonBid.AddBid(nonBid, "pubmatic") + return &seatNonBid + }(), expectedNumDebugErrors: 1, }, { @@ -5105,7 +5031,7 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 2, - expectedNonBids: &nonBids{}, + expectedNonBids: &openrtb_ext.NonBidCollection{}, expectedNumDebugErrors: 1, }, { @@ -5114,7 +5040,7 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid"}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 2, - expectedNonBids: &nonBids{}, + expectedNonBids: &openrtb_ext.NonBidCollection{}, }, { name: "Creative_size_validation_skipped,_Adm_Validation_enforced,_one_of_two_bids_has_invalid_dimensions", @@ -5122,7 +5048,7 @@ func TestMakeBidWithValidation(t *testing.T) { givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}}, givenSeat: "pubmatic", expectedNumOfBids: 2, - expectedNonBids: &nonBids{}, + expectedNonBids: &openrtb_ext.NonBidCollection{}, }, } @@ -5171,7 +5097,7 @@ func TestMakeBidWithValidation(t *testing.T) { } e.bidValidationEnforcement = test.givenValidations sampleBids := test.givenBids - nonBids := &nonBids{} + nonBids := &openrtb_ext.NonBidCollection{} resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, true, ImpExtInfoMap, bidRequest, bidExtResponse, test.givenSeat, "", nonBids) assert.Equal(t, 0, len(resultingErrs)) @@ -5776,9 +5702,10 @@ type exchangeRequest struct { } type exchangeResponse struct { - Bids *openrtb2.BidResponse `json:"bids"` - Error string `json:"error,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` + Bids *openrtb2.BidResponse `json:"bids"` + Error string `json:"error,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` + SeatNonBids []openrtb_ext.SeatNonBid `json:"seatnonbids,omitempty"` } type exchangeServer struct { @@ -6273,42 +6200,80 @@ func TestSelectNewDuration(t *testing.T) { } } -func TestSetSeatNonBid(t *testing.T) { - type args struct { - bidResponseExt *openrtb_ext.ExtBidResponse - seatNonBids nonBids +func TestBidsToUpdate(t *testing.T) { + type testInput struct { + multiBid map[string]openrtb_ext.ExtMultiBid + bidder string } - tests := []struct { - name string - args args - want *openrtb_ext.ExtBidResponse + testCases := []struct { + desc string + in testInput + expected int }{ { - name: "empty-seatNonBidsMap", - args: args{seatNonBids: nonBids{}, bidResponseExt: nil}, - want: nil, + desc: "Empty multibid map. Expect openrtb_ext.DefaultBidLimit", + in: testInput{}, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "Empty bidder. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, + }, + }, + expected: openrtb_ext.DefaultBidLimit, }, { - name: "nil-bidResponseExt", - args: args{seatNonBids: nonBids{seatNonBidsMap: map[string][]openrtb_ext.NonBid{"key": nil}}, bidResponseExt: nil}, - want: &openrtb_ext.ExtBidResponse{ - Prebid: &openrtb_ext.ExtResponsePrebid{ - SeatNonBid: []openrtb_ext.SeatNonBid{{ - Seat: "key", - }}, + desc: "bidder finds a match in multibid map but TargetBidderCodePrefix is empty. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, }, + bidder: "appnexus", }, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "multibid element with non-empty TargetBidderCodePrefix matches bidder. Expect MaxBids value", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + TargetBidderCodePrefix: "aPrefix", + }, + }, + bidder: "appnexus", + }, + expected: 2, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := setSeatNonBid(tt.args.bidResponseExt, tt.args.seatNonBids); !reflect.DeepEqual(got, tt.want) { - t.Errorf("setSeatNonBid() = %v, want %v", got, tt.want) - } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + assert.Equal(t, tc.expected, bidsToUpdate(tc.in.multiBid, tc.in.bidder), tc.desc) }) } } +// getNonBids is utility function which forms NonBidCollection from NonBidParams input +func getNonBids(bidParamsMap map[string][]openrtb_ext.NonBidParams) openrtb_ext.NonBidCollection { + nonBids := openrtb_ext.NonBidCollection{} + for bidder, bidParams := range bidParamsMap { + for _, bidParam := range bidParams { + nonBid := openrtb_ext.NewNonBid(bidParam) + nonBids.AddBid(nonBid, bidder) + } + } + return nonBids +} + func TestBuildMultiBidMap(t *testing.T) { type testCase struct { desc string @@ -6500,65 +6465,3 @@ func TestBuildMultiBidMap(t *testing.T) { } } } - -func TestBidsToUpdate(t *testing.T) { - type testInput struct { - multiBid map[string]openrtb_ext.ExtMultiBid - bidder string - } - testCases := []struct { - desc string - in testInput - expected int - }{ - { - desc: "Empty multibid map. Expect openrtb_ext.DefaultBidLimit", - in: testInput{}, - expected: openrtb_ext.DefaultBidLimit, - }, - { - desc: "Empty bidder. Expect openrtb_ext.DefaultBidLimit", - in: testInput{ - multiBid: map[string]openrtb_ext.ExtMultiBid{ - "appnexus": { - Bidder: "appnexus", - MaxBids: ptrutil.ToPtr(2), - }, - }, - }, - expected: openrtb_ext.DefaultBidLimit, - }, - { - desc: "bidder finds a match in multibid map but TargetBidderCodePrefix is empty. Expect openrtb_ext.DefaultBidLimit", - in: testInput{ - multiBid: map[string]openrtb_ext.ExtMultiBid{ - "appnexus": { - Bidder: "appnexus", - MaxBids: ptrutil.ToPtr(2), - }, - }, - bidder: "appnexus", - }, - expected: openrtb_ext.DefaultBidLimit, - }, - { - desc: "multibid element with non-empty TargetBidderCodePrefix matches bidder. Expect MaxBids value", - in: testInput{ - multiBid: map[string]openrtb_ext.ExtMultiBid{ - "appnexus": { - Bidder: "appnexus", - MaxBids: ptrutil.ToPtr(2), - TargetBidderCodePrefix: "aPrefix", - }, - }, - bidder: "appnexus", - }, - expected: 2, - }, - } - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - assert.Equal(t, tc.expected, bidsToUpdate(tc.in.multiBid, tc.in.bidder), tc.desc) - }) - } -} diff --git a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json index f82b7a56c0f..f86cbbde377 100644 --- a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json +++ b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json @@ -211,7 +211,7 @@ "type": "banner", "meta": { "adaptercode":"appnexus" - } + } } } } diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go deleted file mode 100644 index ee990105c1d..00000000000 --- a/exchange/seat_non_bids.go +++ /dev/null @@ -1,76 +0,0 @@ -package exchange - -import ( - "github.com/prebid/prebid-server/v2/exchange/entities" - "github.com/prebid/prebid-server/v2/openrtb_ext" -) - -type nonBids struct { - seatNonBidsMap map[string][]openrtb_ext.NonBid -} - -// addBid is not thread safe as we are initializing and writing to map -func (snb *nonBids) addBid(bid *entities.PbsOrtbBid, nonBidReason int, seat string) { - if bid == nil || bid.Bid == nil { - return - } - if snb.seatNonBidsMap == nil { - snb.seatNonBidsMap = make(map[string][]openrtb_ext.NonBid) - } - if bid.BidMeta == nil { - bid.BidMeta = &openrtb_ext.ExtBidPrebidMeta{} - } - adapterCode := seat - if bid.AlternateBidderCode != "" { - adapterCode = string(openrtb_ext.BidderName(bid.AlternateBidderCode)) - } - bid.BidMeta.AdapterCode = adapterCode - - nonBid := openrtb_ext.NonBid{ - ImpId: bid.Bid.ImpID, - StatusCode: nonBidReason, - Ext: openrtb_ext.NonBidExt{ - Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{ - Price: bid.Bid.Price, - ADomain: bid.Bid.ADomain, - CatTax: bid.Bid.CatTax, - Cat: bid.Bid.Cat, - DealID: bid.Bid.DealID, - W: bid.Bid.W, - H: bid.Bid.H, - Dur: bid.Bid.Dur, - MType: bid.Bid.MType, - OriginalBidCPM: bid.OriginalBidCPM, - OriginalBidCur: bid.OriginalBidCur, - - //OW specific - ID: bid.Bid.ID, - DealPriority: bid.DealPriority, - DealTierSatisfied: bid.DealTierSatisfied, - Meta: bid.BidMeta, - Targeting: bid.BidTargets, - Type: bid.BidType, - Video: bid.BidVideo, - BidId: bid.GeneratedBidID, - Floors: bid.BidFloors, - OriginalBidCPMUSD: bid.OriginalBidCPMUSD, - }}, - }, - } - - snb.seatNonBidsMap[seat] = append(snb.seatNonBidsMap[seat], nonBid) -} - -func (snb *nonBids) get() []openrtb_ext.SeatNonBid { - if snb == nil { - return nil - } - var seatNonBid []openrtb_ext.SeatNonBid - for seat, nonBids := range snb.seatNonBidsMap { - seatNonBid = append(seatNonBid, openrtb_ext.SeatNonBid{ - Seat: seat, - NonBid: nonBids, - }) - } - return seatNonBid -} diff --git a/exchange/seat_non_bids_test.go b/exchange/seat_non_bids_test.go deleted file mode 100644 index 4436716ce42..00000000000 --- a/exchange/seat_non_bids_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package exchange - -import ( - "testing" - - "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v2/exchange/entities" - "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestSeatNonBidsAdd(t *testing.T) { - type fields struct { - seatNonBidsMap map[string][]openrtb_ext.NonBid - } - type args struct { - bid *entities.PbsOrtbBid - nonBidReason int - seat string - } - tests := []struct { - name string - fields fields - args args - want map[string][]openrtb_ext.NonBid - }{ - { - name: "nil-seatNonBidsMap", - fields: fields{seatNonBidsMap: nil}, - args: args{}, - want: nil, - }, - { - name: "nil-seatNonBidsMap-with-bid-object", - fields: fields{seatNonBidsMap: nil}, - args: args{bid: &entities.PbsOrtbBid{Bid: &openrtb2.Bid{}}, seat: "bidder1"}, - want: sampleSeatNonBidMap("bidder1", 1), - }, - { - name: "multiple-nonbids-for-same-seat", - fields: fields{seatNonBidsMap: sampleSeatNonBidMap("bidder2", 1)}, - args: args{bid: &entities.PbsOrtbBid{Bid: &openrtb2.Bid{}}, seat: "bidder2"}, - want: sampleSeatNonBidMap("bidder2", 2), - }, - { - name: "multiple-nonbids-without-meta", - fields: fields{seatNonBidsMap: sampleSeatNonBidMap("bidder2", 1)}, - args: args{bid: &entities.PbsOrtbBid{Bid: &openrtb2.Bid{}}, seat: "bidder2"}, - want: sampleSeatNonBidMap("bidder2", 2), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - snb := &nonBids{ - seatNonBidsMap: tt.fields.seatNonBidsMap, - } - snb.addBid(tt.args.bid, tt.args.nonBidReason, tt.args.seat) - assert.Equalf(t, tt.want, snb.seatNonBidsMap, "expected seatNonBidsMap not nil") - }) - } -} - -func TestSeatNonBidsGet(t *testing.T) { - type fields struct { - snb *nonBids - } - tests := []struct { - name string - fields fields - want []openrtb_ext.SeatNonBid - }{ - { - name: "get-seat-nonbids", - fields: fields{&nonBids{sampleSeatNonBidMap("bidder1", 2)}}, - want: sampleSeatBids("bidder1", 2), - }, - { - name: "nil-seat-nonbids", - fields: fields{nil}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.fields.snb.get(); !assert.Equal(t, tt.want, got) { - t.Errorf("seatNonBids.get() = %v, want %v", got, tt.want) - } - }) - } -} - -var sampleSeatNonBidMap = func(seat string, nonBidCount int) map[string][]openrtb_ext.NonBid { - nonBids := make([]openrtb_ext.NonBid, 0) - for i := 0; i < nonBidCount; i++ { - nonBids = append(nonBids, openrtb_ext.NonBid{ - Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: seat}}}}, - }) - } - return map[string][]openrtb_ext.NonBid{ - seat: nonBids, - } -} - -var sampleSeatBids = func(seat string, nonBidCount int) []openrtb_ext.SeatNonBid { - seatNonBids := make([]openrtb_ext.SeatNonBid, 0) - seatNonBid := openrtb_ext.SeatNonBid{ - Seat: seat, - NonBid: make([]openrtb_ext.NonBid, 0), - } - for i := 0; i < nonBidCount; i++ { - seatNonBid.NonBid = append(seatNonBid.NonBid, openrtb_ext.NonBid{ - Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{Meta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: seat}}}}, - }) - } - seatNonBids = append(seatNonBids, seatNonBid) - return seatNonBids -} diff --git a/hooks/hookexecution/enricher_test.go b/hooks/hookexecution/enricher_test.go index 4f962dea279..95da909c1f7 100644 --- a/hooks/hookexecution/enricher_test.go +++ b/hooks/hookexecution/enricher_test.go @@ -31,14 +31,15 @@ type GroupOutcomeTest struct { type HookOutcomeTest struct { ExecutionTime - AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"` - HookID HookID `json:"hook_id"` - Status Status `json:"status"` - Action Action `json:"action"` - Message string `json:"message"` - DebugMessages []string `json:"debug_messages"` - Errors []string `json:"errors"` - Warnings []string `json:"warnings"` + AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"` + HookID HookID `json:"hook_id"` + Status Status `json:"status"` + Action Action `json:"action"` + Message string `json:"message"` + DebugMessages []string `json:"debug_messages"` + Errors []string `json:"errors"` + Warnings []string `json:"warnings"` + SeatNonBid openrtb_ext.NonBidCollection `json:"seatnonbid"` } func TestEnrichBidResponse(t *testing.T) { diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go index 03500bb5f0e..6bdc2d09816 100644 --- a/hooks/hookexecution/execution.go +++ b/hooks/hookexecution/execution.go @@ -193,6 +193,7 @@ func handleHookResponse[P any]( Warnings: hr.Result.Warnings, DebugMessages: hr.Result.DebugMessages, AnalyticsTags: hr.Result.AnalyticsTags, + SeatNonBid: hr.Result.SeatNonBid, ExecutionTime: ExecutionTime{ExecutionTimeMillis: hr.ExecutionTime}, } diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index a353e9000dc..0ba466acc8d 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -386,6 +386,6 @@ func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[ope func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {} -func (executor *EmptyHookExecutor) ExecuteBeforeRequestValidationStage(_ *openrtb2.BidRequest) *RejectError { +func (executor EmptyHookExecutor) ExecuteBeforeRequestValidationStage(_ *openrtb2.BidRequest) *RejectError { return nil } diff --git a/hooks/hookexecution/outcome.go b/hooks/hookexecution/outcome.go index ff8bf1e973e..fbfc03c5ac5 100644 --- a/hooks/hookexecution/outcome.go +++ b/hooks/hookexecution/outcome.go @@ -4,6 +4,7 @@ import ( "time" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Status indicates the result of hook execution. @@ -77,14 +78,15 @@ type GroupOutcome struct { type HookOutcome struct { // ExecutionTime is the execution time of a specific hook without applying its result. ExecutionTime - AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"` - HookID HookID `json:"hook_id"` - Status Status `json:"status"` - Action Action `json:"action"` - Message string `json:"message"` // arbitrary string value returned from hook execution - DebugMessages []string `json:"debug_messages,omitempty"` - Errors []string `json:"-"` - Warnings []string `json:"-"` + AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"` + HookID HookID `json:"hook_id"` + Status Status `json:"status"` + Action Action `json:"action"` + Message string `json:"message"` // arbitrary string value returned from hook execution + DebugMessages []string `json:"debug_messages,omitempty"` + Errors []string `json:"-"` + Warnings []string `json:"-"` + SeatNonBid openrtb_ext.NonBidCollection `json:"-"` } // HookID points to the specific hook defined by the hook execution plan. diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index d6adb506066..5254cfbae36 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // HookResult represents the result of execution the concrete hook instance. @@ -16,7 +17,8 @@ type HookResult[T any] struct { Warnings []string DebugMessages []string AnalyticsTags hookanalytics.Analytics - ModuleContext ModuleContext // holds values that the module wants to pass to itself at later stages + ModuleContext ModuleContext // holds values that the module wants to pass to itself at later stages + SeatNonBid openrtb_ext.NonBidCollection // holds list of seatnonbid rejected by hook } // ModuleInvocationContext holds data passed to the module hook during invocation. diff --git a/modules/pubmatic/openwrap/auctionresponsehook.go b/modules/pubmatic/openwrap/auctionresponsehook.go index 7c546a11b9b..1cf613c2289 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook.go +++ b/modules/pubmatic/openwrap/auctionresponsehook.go @@ -269,11 +269,7 @@ func (m OpenWrap) handleAuctionResponseHook( } } - // add seat-non-bids in the bidresponse only request.ext.prebid.returnallbidstatus is true - if rctx.ReturnAllBidStatus { - rctx.SeatNonBids = prepareSeatNonBids(rctx) - addSeatNonBidsInResponseExt(rctx, &responseExt) - } + result.SeatNonBid = prepareSeatNonBids(rctx) if rctx.Debug { rCtxBytes, _ := json.Marshal(rctx) @@ -327,7 +323,6 @@ func (m OpenWrap) handleAuctionResponseHook( // TODO: move debug here // result.ChangeSet.AddMutation(func(ap hookstage.AuctionResponsePayload) (hookstage.AuctionResponsePayload, error) { // }, hookstage.MutationUpdate, "response-body-with-sshb-format") - return result, nil } diff --git a/modules/pubmatic/openwrap/auctionresponsehook_test.go b/modules/pubmatic/openwrap/auctionresponsehook_test.go index 5f75bf056b8..c61e8f8dd82 100644 --- a/modules/pubmatic/openwrap/auctionresponsehook_test.go +++ b/modules/pubmatic/openwrap/auctionresponsehook_test.go @@ -20,117 +20,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSeatNonBidsInHandleAuctionResponseHook(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - type args struct { - ctx context.Context - moduleCtx hookstage.ModuleInvocationContext - payload hookstage.AuctionResponsePayload - } - - type want struct { - bidResponseExt json.RawMessage - err error - } - - tests := []struct { - name string - args args - want want - getMetricsEngine func() *mock_metrics.MockMetricsEngine - }{ - { - name: "returnallbidstatus_true", - args: args{ - ctx: nil, - moduleCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - "rctx": models.RequestCtx{ - StartTime: time.Now().UnixMilli(), - ReturnAllBidStatus: true, - ImpBidCtx: map[string]models.ImpCtx{ - "imp1": {}, - }, - AdapterThrottleMap: map[string]struct{}{ - "pubmatic": {}, - }, - PubIDStr: "5890", - }, - }, - }, - payload: hookstage.AuctionResponsePayload{ - BidResponse: &openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{}, - }, - }, - }, - getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { - mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) - mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) - mockEngine.EXPECT().RecordNobidErrPrebidServerResponse("5890") - return mockEngine - }, - want: want{ - bidResponseExt: json.RawMessage(`{"prebid":{"seatnonbid":[{"nonbid":[{"impid":"imp1","statuscode":504,"ext":{"prebid":{"bid":{"id":""}}}}],"seat":"pubmatic","ext":null}]},"matchedimpression":{}}`), - }, - }, - { - name: "returnallbidstatus_false", - args: args{ - ctx: nil, - moduleCtx: hookstage.ModuleInvocationContext{ - ModuleContext: hookstage.ModuleContext{ - "rctx": models.RequestCtx{ - StartTime: time.Now().UnixMilli(), - ReturnAllBidStatus: false, - ImpBidCtx: map[string]models.ImpCtx{ - "imp1": {}, - }, - AdapterThrottleMap: map[string]struct{}{ - "pubmatic": {}, - }, - PubIDStr: "5890", - }, - }, - }, - payload: hookstage.AuctionResponsePayload{ - BidResponse: &openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{}, - }, - }, - }, - getMetricsEngine: func() (me *mock_metrics.MockMetricsEngine) { - mockEngine := mock_metrics.NewMockMetricsEngine(ctrl) - mockEngine.EXPECT().RecordPublisherResponseTimeStats("5890", gomock.Any()) - mockEngine.EXPECT().RecordNobidErrPrebidServerResponse("5890") - return mockEngine - }, - want: want{ - bidResponseExt: json.RawMessage(`{"matchedimpression":{}}`), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - o := OpenWrap{ - metricEngine: tt.getMetricsEngine(), - } - hookResult, err := o.handleAuctionResponseHook(tt.args.ctx, tt.args.moduleCtx, tt.args.payload) - assert.Equal(t, tt.want.err, err, tt.name) - mutations := hookResult.ChangeSet.Mutations() - assert.NotEmpty(t, mutations, tt.name) - for _, mut := range mutations { - result, err := mut.Apply(tt.args.payload) - assert.Nil(t, err, tt.name) - assert.Equal(t, tt.want.bidResponseExt, result.BidResponse.Ext, tt.name) - } - }) - } -} - func TestNonBRCodesInHandleAuctionResponseHook(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index 095850b2e50..50d8c457338 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -165,6 +165,7 @@ func (m OpenWrap) handleBeforeValidationHook( var allPartnersThrottledFlag bool rCtx.AdapterThrottleMap, allPartnersThrottledFlag = GetAdapterThrottleMap(rCtx.PartnerConfigMap) + if allPartnersThrottledFlag { result.NbrCode = int(nbr.AllPartnerThrottled) result.Errors = append(result.Errors, "All adapters throttled") diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index e9506cac09b..3671c92de62 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -3247,6 +3247,7 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { assert.Equal(t, tt.wantErr, err != nil) assert.Equal(t, tt.want.Reject, got.Reject) assert.Equal(t, tt.want.NbrCode, got.NbrCode) + assert.Equal(t, tt.want.SeatNonBid, got.SeatNonBid) for i := 0; i < len(got.DebugMessages); i++ { gotDebugMessage, _ := json.Marshal(got.DebugMessages[i]) wantDebugMessage, _ := json.Marshal(tt.want.DebugMessages[i]) @@ -4031,6 +4032,7 @@ func TestImpBidCtx_handleBeforeValidationHook(t *testing.T) { }, }, nil) mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{}) + //prometheus metrics mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "1234") mockEngine.EXPECT().RecordBadRequests(rctx.Endpoint, getPubmaticErrorCode(nbr.InternalError)) diff --git a/modules/pubmatic/openwrap/cache/mock/mock.go b/modules/pubmatic/openwrap/cache/mock/mock.go index e7f1cf2bd05..8679e1f5792 100644 --- a/modules/pubmatic/openwrap/cache/mock/mock.go +++ b/modules/pubmatic/openwrap/cache/mock/mock.go @@ -5,38 +5,37 @@ package mock_cache import ( - reflect "reflect" - gomock "github.com/golang/mock/gomock" openrtb2 "github.com/prebid/openrtb/v20/openrtb2" models "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" adunitconfig "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" + reflect "reflect" ) -// MockCache is a mock of Cache interface. +// MockCache is a mock of Cache interface type MockCache struct { ctrl *gomock.Controller recorder *MockCacheMockRecorder } -// MockCacheMockRecorder is the mock recorder for MockCache. +// MockCacheMockRecorder is the mock recorder for MockCache type MockCacheMockRecorder struct { mock *MockCache } -// NewMockCache creates a new mock instance. +// NewMockCache creates a new mock instance func NewMockCache(ctrl *gomock.Controller) *MockCache { mock := &MockCache{ctrl: ctrl} mock.recorder = &MockCacheMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockCache) EXPECT() *MockCacheMockRecorder { return m.recorder } -// Get mocks base method. +// Get mocks base method func (m *MockCache) Get(arg0 string) (interface{}, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0) @@ -45,13 +44,13 @@ func (m *MockCache) Get(arg0 string) (interface{}, bool) { return ret0, ret1 } -// Get indicates an expected call of Get. +// Get indicates an expected call of Get func (mr *MockCacheMockRecorder) Get(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCache)(nil).Get), arg0) } -// GetAdunitConfigFromCache mocks base method. +// GetAdunitConfigFromCache mocks base method func (m *MockCache) GetAdunitConfigFromCache(arg0 *openrtb2.BidRequest, arg1, arg2, arg3 int) *adunitconfig.AdUnitConfig { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAdunitConfigFromCache", arg0, arg1, arg2, arg3) @@ -59,13 +58,13 @@ func (m *MockCache) GetAdunitConfigFromCache(arg0 *openrtb2.BidRequest, arg1, ar return ret0 } -// GetAdunitConfigFromCache indicates an expected call of GetAdunitConfigFromCache. +// GetAdunitConfigFromCache indicates an expected call of GetAdunitConfigFromCache func (mr *MockCacheMockRecorder) GetAdunitConfigFromCache(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAdunitConfigFromCache", reflect.TypeOf((*MockCache)(nil).GetAdunitConfigFromCache), arg0, arg1, arg2, arg3) } -// GetFSCThresholdPerDSP mocks base method. +// GetFSCThresholdPerDSP mocks base method func (m *MockCache) GetFSCThresholdPerDSP() (map[int]int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetFSCThresholdPerDSP") @@ -74,13 +73,13 @@ func (m *MockCache) GetFSCThresholdPerDSP() (map[int]int, error) { return ret0, ret1 } -// GetFSCThresholdPerDSP indicates an expected call of GetFSCThresholdPerDSP. +// GetFSCThresholdPerDSP indicates an expected call of GetFSCThresholdPerDSP func (mr *MockCacheMockRecorder) GetFSCThresholdPerDSP() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFSCThresholdPerDSP", reflect.TypeOf((*MockCache)(nil).GetFSCThresholdPerDSP)) } -// GetMappingsFromCacheV25 mocks base method. +// GetMappingsFromCacheV25 mocks base method func (m *MockCache) GetMappingsFromCacheV25(arg0 models.RequestCtx, arg1 int) map[string]models.SlotMapping { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMappingsFromCacheV25", arg0, arg1) @@ -88,13 +87,13 @@ func (m *MockCache) GetMappingsFromCacheV25(arg0 models.RequestCtx, arg1 int) ma return ret0 } -// GetMappingsFromCacheV25 indicates an expected call of GetMappingsFromCacheV25. +// GetMappingsFromCacheV25 indicates an expected call of GetMappingsFromCacheV25 func (mr *MockCacheMockRecorder) GetMappingsFromCacheV25(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMappingsFromCacheV25", reflect.TypeOf((*MockCache)(nil).GetMappingsFromCacheV25), arg0, arg1) } -// GetPartnerConfigMap mocks base method. +// GetPartnerConfigMap mocks base method func (m *MockCache) GetPartnerConfigMap(arg0, arg1, arg2 int) (map[int]map[string]string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPartnerConfigMap", arg0, arg1, arg2) @@ -103,13 +102,13 @@ func (m *MockCache) GetPartnerConfigMap(arg0, arg1, arg2 int) (map[int]map[strin return ret0, ret1 } -// GetPartnerConfigMap indicates an expected call of GetPartnerConfigMap. +// GetPartnerConfigMap indicates an expected call of GetPartnerConfigMap func (mr *MockCacheMockRecorder) GetPartnerConfigMap(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPartnerConfigMap", reflect.TypeOf((*MockCache)(nil).GetPartnerConfigMap), arg0, arg1, arg2) } -// GetPublisherFeatureMap mocks base method. +// GetPublisherFeatureMap mocks base method func (m *MockCache) GetPublisherFeatureMap() (map[int]map[int]models.FeatureData, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPublisherFeatureMap") @@ -118,13 +117,13 @@ func (m *MockCache) GetPublisherFeatureMap() (map[int]map[int]models.FeatureData return ret0, ret1 } -// GetPublisherFeatureMap indicates an expected call of GetPublisherFeatureMap. +// GetPublisherFeatureMap indicates an expected call of GetPublisherFeatureMap func (mr *MockCacheMockRecorder) GetPublisherFeatureMap() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublisherFeatureMap", reflect.TypeOf((*MockCache)(nil).GetPublisherFeatureMap)) } -// GetPublisherVASTTagsFromCache mocks base method. +// GetPublisherVASTTagsFromCache mocks base method func (m *MockCache) GetPublisherVASTTagsFromCache(arg0 int) map[int]*models.VASTTag { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPublisherVASTTagsFromCache", arg0) @@ -132,13 +131,13 @@ func (m *MockCache) GetPublisherVASTTagsFromCache(arg0 int) map[int]*models.VAST return ret0 } -// GetPublisherVASTTagsFromCache indicates an expected call of GetPublisherVASTTagsFromCache. +// GetPublisherVASTTagsFromCache indicates an expected call of GetPublisherVASTTagsFromCache func (mr *MockCacheMockRecorder) GetPublisherVASTTagsFromCache(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublisherVASTTagsFromCache", reflect.TypeOf((*MockCache)(nil).GetPublisherVASTTagsFromCache), arg0) } -// GetSlotToHashValueMapFromCacheV25 mocks base method. +// GetSlotToHashValueMapFromCacheV25 mocks base method func (m *MockCache) GetSlotToHashValueMapFromCacheV25(arg0 models.RequestCtx, arg1 int) models.SlotMappingInfo { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSlotToHashValueMapFromCacheV25", arg0, arg1) @@ -146,19 +145,19 @@ func (m *MockCache) GetSlotToHashValueMapFromCacheV25(arg0 models.RequestCtx, ar return ret0 } -// GetSlotToHashValueMapFromCacheV25 indicates an expected call of GetSlotToHashValueMapFromCacheV25. +// GetSlotToHashValueMapFromCacheV25 indicates an expected call of GetSlotToHashValueMapFromCacheV25 func (mr *MockCacheMockRecorder) GetSlotToHashValueMapFromCacheV25(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSlotToHashValueMapFromCacheV25", reflect.TypeOf((*MockCache)(nil).GetSlotToHashValueMapFromCacheV25), arg0, arg1) } -// Set mocks base method. +// Set mocks base method func (m *MockCache) Set(arg0 string, arg1 interface{}) { m.ctrl.T.Helper() m.ctrl.Call(m, "Set", arg0, arg1) } -// Set indicates an expected call of Set. +// Set indicates an expected call of Set func (mr *MockCacheMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockCache)(nil).Set), arg0, arg1) diff --git a/modules/pubmatic/openwrap/database/mysql/adunit_config.go b/modules/pubmatic/openwrap/database/mysql/adunit_config.go index d3a27c2edaa..9254c4ac215 100644 --- a/modules/pubmatic/openwrap/database/mysql/adunit_config.go +++ b/modules/pubmatic/openwrap/database/mysql/adunit_config.go @@ -58,6 +58,5 @@ func (db *mySqlDB) GetAdunitConfig(profileID, displayVersion int) (*adunitconfig if _, ok := adunitConfig.Config["default"]; !ok { adunitConfig.Config["default"] = &adunitconfig.AdConfig{} } - return adunitConfig, err } diff --git a/modules/pubmatic/openwrap/metrics/mock/mock.go b/modules/pubmatic/openwrap/metrics/mock/mock.go index c80cb38b990..1d600c020cd 100644 --- a/modules/pubmatic/openwrap/metrics/mock/mock.go +++ b/modules/pubmatic/openwrap/metrics/mock/mock.go @@ -5,55 +5,54 @@ package mock_metrics import ( - reflect "reflect" - time "time" - gomock "github.com/golang/mock/gomock" metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" + reflect "reflect" + time "time" ) -// MockMetricsEngine is a mock of MetricsEngine interface. +// MockMetricsEngine is a mock of MetricsEngine interface type MockMetricsEngine struct { ctrl *gomock.Controller recorder *MockMetricsEngineMockRecorder } -// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine. +// MockMetricsEngineMockRecorder is the mock recorder for MockMetricsEngine type MockMetricsEngineMockRecorder struct { mock *MockMetricsEngine } -// NewMockMetricsEngine creates a new mock instance. +// NewMockMetricsEngine creates a new mock instance func NewMockMetricsEngine(ctrl *gomock.Controller) *MockMetricsEngine { mock := &MockMetricsEngine{ctrl: ctrl} mock.recorder = &MockMetricsEngineMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockMetricsEngine) EXPECT() *MockMetricsEngineMockRecorder { return m.recorder } -// RecordAdPodGeneratedImpressionsCount mocks base method. +// RecordAdPodGeneratedImpressionsCount mocks base method func (m *MockMetricsEngine) RecordAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdPodGeneratedImpressionsCount", arg0, arg1) } -// RecordAdPodGeneratedImpressionsCount indicates an expected call of RecordAdPodGeneratedImpressionsCount. +// RecordAdPodGeneratedImpressionsCount indicates an expected call of RecordAdPodGeneratedImpressionsCount func (mr *MockMetricsEngineMockRecorder) RecordAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodGeneratedImpressionsCount), arg0, arg1) } -// RecordAdPodImpressionYield mocks base method. +// RecordAdPodImpressionYield mocks base method func (m *MockMetricsEngine) RecordAdPodImpressionYield(arg0, arg1 int, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordAdPodImpressionYield", arg0, arg1, arg2) } -// RecordAdPodImpressionYield indicates an expected call of RecordAdPodImpressionYield. +// RecordAdPodImpressionYield indicates an expected call of RecordAdPodImpressionYield func (mr *MockMetricsEngineMockRecorder) RecordAdPodImpressionYield(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordAdPodImpressionYield", reflect.TypeOf((*MockMetricsEngine)(nil).RecordAdPodImpressionYield), arg0, arg1, arg2) @@ -101,151 +100,151 @@ func (m *MockMetricsEngine) RecordBadRequests(arg0 string, arg1 int) { m.ctrl.Call(m, "RecordBadRequests", arg0, arg1) } -// RecordBadRequests indicates an expected call of RecordBadRequests. +// RecordBadRequests indicates an expected call of RecordBadRequests func (mr *MockMetricsEngineMockRecorder) RecordBadRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBadRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBadRequests), arg0, arg1) } -// RecordBidResponseByDealCountInHB mocks base method. +// RecordBidResponseByDealCountInHB mocks base method func (m *MockMetricsEngine) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBidResponseByDealCountInHB", arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInHB indicates an expected call of RecordBidResponseByDealCountInHB. +// RecordBidResponseByDealCountInHB indicates an expected call of RecordBidResponseByDealCountInHB func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInHB(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInHB", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInHB), arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInPBS mocks base method. +// RecordBidResponseByDealCountInPBS mocks base method func (m *MockMetricsEngine) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBidResponseByDealCountInPBS", arg0, arg1, arg2, arg3) } -// RecordBidResponseByDealCountInPBS indicates an expected call of RecordBidResponseByDealCountInPBS. +// RecordBidResponseByDealCountInPBS indicates an expected call of RecordBidResponseByDealCountInPBS func (mr *MockMetricsEngineMockRecorder) RecordBidResponseByDealCountInPBS(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBidResponseByDealCountInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBidResponseByDealCountInPBS), arg0, arg1, arg2, arg3) } -// RecordBids mocks base method. +// RecordBids mocks base method func (m *MockMetricsEngine) RecordBids(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordBids", arg0, arg1, arg2, arg3) } -// RecordBids indicates an expected call of RecordBids. +// RecordBids indicates an expected call of RecordBids func (mr *MockMetricsEngineMockRecorder) RecordBids(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBids", reflect.TypeOf((*MockMetricsEngine)(nil).RecordBids), arg0, arg1, arg2, arg3) } -// RecordCTVHTTPMethodRequests mocks base method. +// RecordCTVHTTPMethodRequests mocks base method func (m *MockMetricsEngine) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVHTTPMethodRequests", arg0, arg1, arg2) } -// RecordCTVHTTPMethodRequests indicates an expected call of RecordCTVHTTPMethodRequests. +// RecordCTVHTTPMethodRequests indicates an expected call of RecordCTVHTTPMethodRequests func (mr *MockMetricsEngineMockRecorder) RecordCTVHTTPMethodRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVHTTPMethodRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVHTTPMethodRequests), arg0, arg1, arg2) } -// RecordCTVInvalidReasonCount mocks base method. +// RecordCTVInvalidReasonCount mocks base method func (m *MockMetricsEngine) RecordCTVInvalidReasonCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVInvalidReasonCount", arg0, arg1) } -// RecordCTVInvalidReasonCount indicates an expected call of RecordCTVInvalidReasonCount. +// RecordCTVInvalidReasonCount indicates an expected call of RecordCTVInvalidReasonCount func (mr *MockMetricsEngineMockRecorder) RecordCTVInvalidReasonCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVInvalidReasonCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVInvalidReasonCount), arg0, arg1) } -// RecordCTVReqCountWithAdPod mocks base method. +// RecordCTVReqCountWithAdPod mocks base method func (m *MockMetricsEngine) RecordCTVReqCountWithAdPod(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqCountWithAdPod", arg0, arg1) } -// RecordCTVReqCountWithAdPod indicates an expected call of RecordCTVReqCountWithAdPod. +// RecordCTVReqCountWithAdPod indicates an expected call of RecordCTVReqCountWithAdPod func (mr *MockMetricsEngineMockRecorder) RecordCTVReqCountWithAdPod(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqCountWithAdPod", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqCountWithAdPod), arg0, arg1) } -// RecordCTVReqImpsWithDbConfigCount mocks base method. +// RecordCTVReqImpsWithDbConfigCount mocks base method func (m *MockMetricsEngine) RecordCTVReqImpsWithDbConfigCount(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqImpsWithDbConfigCount", arg0) } -// RecordCTVReqImpsWithDbConfigCount indicates an expected call of RecordCTVReqImpsWithDbConfigCount. +// RecordCTVReqImpsWithDbConfigCount indicates an expected call of RecordCTVReqImpsWithDbConfigCount func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithDbConfigCount(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithDbConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithDbConfigCount), arg0) } -// RecordCTVReqImpsWithReqConfigCount mocks base method. +// RecordCTVReqImpsWithReqConfigCount mocks base method func (m *MockMetricsEngine) RecordCTVReqImpsWithReqConfigCount(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVReqImpsWithReqConfigCount", arg0) } -// RecordCTVReqImpsWithReqConfigCount indicates an expected call of RecordCTVReqImpsWithReqConfigCount. +// RecordCTVReqImpsWithReqConfigCount indicates an expected call of RecordCTVReqImpsWithReqConfigCount func (mr *MockMetricsEngineMockRecorder) RecordCTVReqImpsWithReqConfigCount(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVReqImpsWithReqConfigCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVReqImpsWithReqConfigCount), arg0) } -// RecordCTVRequests mocks base method. +// RecordCTVRequests mocks base method func (m *MockMetricsEngine) RecordCTVRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCTVRequests", arg0, arg1) } -// RecordCTVRequests indicates an expected call of RecordCTVRequests. +// RecordCTVRequests indicates an expected call of RecordCTVRequests func (mr *MockMetricsEngineMockRecorder) RecordCTVRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCTVRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCTVRequests), arg0, arg1) } -// RecordCacheErrorRequests mocks base method. +// RecordCacheErrorRequests mocks base method func (m *MockMetricsEngine) RecordCacheErrorRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCacheErrorRequests", arg0, arg1, arg2) } -// RecordCacheErrorRequests indicates an expected call of RecordCacheErrorRequests. +// RecordCacheErrorRequests indicates an expected call of RecordCacheErrorRequests func (mr *MockMetricsEngineMockRecorder) RecordCacheErrorRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCacheErrorRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCacheErrorRequests), arg0, arg1, arg2) } -// RecordCtvUaAccuracy mocks base method. +// RecordCtvUaAccuracy mocks base method func (m *MockMetricsEngine) RecordCtvUaAccuracy(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordCtvUaAccuracy", arg0, arg1) } -// RecordCtvUaAccuracy indicates an expected call of RecordCtvUaAccuracy. +// RecordCtvUaAccuracy indicates an expected call of RecordCtvUaAccuracy func (mr *MockMetricsEngineMockRecorder) RecordCtvUaAccuracy(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCtvUaAccuracy", reflect.TypeOf((*MockMetricsEngine)(nil).RecordCtvUaAccuracy), arg0, arg1) } -// RecordDBQueryFailure mocks base method. +// RecordDBQueryFailure mocks base method func (m *MockMetricsEngine) RecordDBQueryFailure(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordDBQueryFailure", arg0, arg1, arg2) } -// RecordDBQueryFailure indicates an expected call of RecordDBQueryFailure. +// RecordDBQueryFailure indicates an expected call of RecordDBQueryFailure func (mr *MockMetricsEngineMockRecorder) RecordDBQueryFailure(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordDBQueryFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordDBQueryFailure), arg0, arg1, arg2) @@ -263,385 +262,385 @@ func (mr *MockMetricsEngineMockRecorder) RecordGetProfileDataTime(arg0 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordGetProfileDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordGetProfileDataTime), arg0) } -// RecordImpDisabledViaConfigStats mocks base method. +// RecordImpDisabledViaConfigStats mocks base method func (m *MockMetricsEngine) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordImpDisabledViaConfigStats", arg0, arg1, arg2) } -// RecordImpDisabledViaConfigStats indicates an expected call of RecordImpDisabledViaConfigStats. +// RecordImpDisabledViaConfigStats indicates an expected call of RecordImpDisabledViaConfigStats func (mr *MockMetricsEngineMockRecorder) RecordImpDisabledViaConfigStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordImpDisabledViaConfigStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordImpDisabledViaConfigStats), arg0, arg1, arg2) } -// RecordInjectTrackerErrorCount mocks base method. +// RecordInjectTrackerErrorCount mocks base method func (m *MockMetricsEngine) RecordInjectTrackerErrorCount(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordInjectTrackerErrorCount", arg0, arg1, arg2) } -// RecordInjectTrackerErrorCount indicates an expected call of RecordInjectTrackerErrorCount. +// RecordInjectTrackerErrorCount indicates an expected call of RecordInjectTrackerErrorCount func (mr *MockMetricsEngineMockRecorder) RecordInjectTrackerErrorCount(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInjectTrackerErrorCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInjectTrackerErrorCount), arg0, arg1, arg2) } -// RecordInvalidCreativeStats mocks base method. +// RecordInvalidCreativeStats mocks base method func (m *MockMetricsEngine) RecordInvalidCreativeStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordInvalidCreativeStats", arg0, arg1) } -// RecordInvalidCreativeStats indicates an expected call of RecordInvalidCreativeStats. +// RecordInvalidCreativeStats indicates an expected call of RecordInvalidCreativeStats func (mr *MockMetricsEngineMockRecorder) RecordInvalidCreativeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordInvalidCreativeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordInvalidCreativeStats), arg0, arg1) } -// RecordLurlBatchSent mocks base method. +// RecordLurlBatchSent mocks base method func (m *MockMetricsEngine) RecordLurlBatchSent(arg0 metrics.LurlBatchStatusLabels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLurlBatchSent", arg0) } -// RecordLurlBatchSent indicates an expected call of RecordLurlBatchSent. +// RecordLurlBatchSent indicates an expected call of RecordLurlBatchSent func (mr *MockMetricsEngineMockRecorder) RecordLurlBatchSent(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlBatchSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlBatchSent), arg0) } -// RecordLurlSent mocks base method. +// RecordLurlSent mocks base method func (m *MockMetricsEngine) RecordLurlSent(arg0 metrics.LurlStatusLabels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordLurlSent", arg0) } -// RecordLurlSent indicates an expected call of RecordLurlSent. +// RecordLurlSent indicates an expected call of RecordLurlSent func (mr *MockMetricsEngineMockRecorder) RecordLurlSent(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordLurlSent", reflect.TypeOf((*MockMetricsEngine)(nil).RecordLurlSent), arg0) } -// RecordNobidErrPrebidServerRequests mocks base method. +// RecordNobidErrPrebidServerRequests mocks base method func (m *MockMetricsEngine) RecordNobidErrPrebidServerRequests(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordNobidErrPrebidServerRequests", arg0, arg1) } -// RecordNobidErrPrebidServerRequests indicates an expected call of RecordNobidErrPrebidServerRequests. +// RecordNobidErrPrebidServerRequests indicates an expected call of RecordNobidErrPrebidServerRequests func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerRequests), arg0, arg1) } -// RecordNobidErrPrebidServerResponse mocks base method. +// RecordNobidErrPrebidServerResponse mocks base method func (m *MockMetricsEngine) RecordNobidErrPrebidServerResponse(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordNobidErrPrebidServerResponse", arg0) } -// RecordNobidErrPrebidServerResponse indicates an expected call of RecordNobidErrPrebidServerResponse. +// RecordNobidErrPrebidServerResponse indicates an expected call of RecordNobidErrPrebidServerResponse func (mr *MockMetricsEngineMockRecorder) RecordNobidErrPrebidServerResponse(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordNobidErrPrebidServerResponse", reflect.TypeOf((*MockMetricsEngine)(nil).RecordNobidErrPrebidServerResponse), arg0) } -// RecordOWServerPanic mocks base method. +// RecordOWServerPanic mocks base method func (m *MockMetricsEngine) RecordOWServerPanic(arg0, arg1, arg2, arg3 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordOWServerPanic", arg0, arg1, arg2, arg3) } -// RecordOWServerPanic indicates an expected call of RecordOWServerPanic. +// RecordOWServerPanic indicates an expected call of RecordOWServerPanic func (mr *MockMetricsEngineMockRecorder) RecordOWServerPanic(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOWServerPanic", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOWServerPanic), arg0, arg1, arg2, arg3) } -// RecordOpenWrapServerPanicStats mocks base method. +// RecordOpenWrapServerPanicStats mocks base method func (m *MockMetricsEngine) RecordOpenWrapServerPanicStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordOpenWrapServerPanicStats", arg0, arg1) } -// RecordOpenWrapServerPanicStats indicates an expected call of RecordOpenWrapServerPanicStats. +// RecordOpenWrapServerPanicStats indicates an expected call of RecordOpenWrapServerPanicStats func (mr *MockMetricsEngineMockRecorder) RecordOpenWrapServerPanicStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordOpenWrapServerPanicStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordOpenWrapServerPanicStats), arg0, arg1) } -// RecordPBSAuctionRequestsStats mocks base method. +// RecordPBSAuctionRequestsStats mocks base method func (m *MockMetricsEngine) RecordPBSAuctionRequestsStats() { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPBSAuctionRequestsStats") } -// RecordPBSAuctionRequestsStats indicates an expected call of RecordPBSAuctionRequestsStats. +// RecordPBSAuctionRequestsStats indicates an expected call of RecordPBSAuctionRequestsStats func (mr *MockMetricsEngineMockRecorder) RecordPBSAuctionRequestsStats() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPBSAuctionRequestsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPBSAuctionRequestsStats)) } -// RecordPartnerConfigErrors mocks base method. +// RecordPartnerConfigErrors mocks base method func (m *MockMetricsEngine) RecordPartnerConfigErrors(arg0, arg1, arg2 string, arg3 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerConfigErrors", arg0, arg1, arg2, arg3) } -// RecordPartnerConfigErrors indicates an expected call of RecordPartnerConfigErrors. +// RecordPartnerConfigErrors indicates an expected call of RecordPartnerConfigErrors func (mr *MockMetricsEngineMockRecorder) RecordPartnerConfigErrors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerConfigErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerConfigErrors), arg0, arg1, arg2, arg3) } -// RecordPartnerResponseErrors mocks base method. +// RecordPartnerResponseErrors mocks base method func (m *MockMetricsEngine) RecordPartnerResponseErrors(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerResponseErrors", arg0, arg1, arg2) } -// RecordPartnerResponseErrors indicates an expected call of RecordPartnerResponseErrors. +// RecordPartnerResponseErrors indicates an expected call of RecordPartnerResponseErrors func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseErrors(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseErrors", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseErrors), arg0, arg1, arg2) } -// RecordPartnerResponseTimeStats mocks base method. +// RecordPartnerResponseTimeStats mocks base method func (m *MockMetricsEngine) RecordPartnerResponseTimeStats(arg0, arg1 string, arg2 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerResponseTimeStats", arg0, arg1, arg2) } -// RecordPartnerResponseTimeStats indicates an expected call of RecordPartnerResponseTimeStats. +// RecordPartnerResponseTimeStats indicates an expected call of RecordPartnerResponseTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPartnerResponseTimeStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerResponseTimeStats), arg0, arg1, arg2) } -// RecordPartnerTimeoutInPBS mocks base method. +// RecordPartnerTimeoutInPBS mocks base method func (m *MockMetricsEngine) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerTimeoutInPBS", arg0, arg1, arg2) } -// RecordPartnerTimeoutInPBS indicates an expected call of RecordPartnerTimeoutInPBS. +// RecordPartnerTimeoutInPBS indicates an expected call of RecordPartnerTimeoutInPBS func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutInPBS(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutInPBS", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutInPBS), arg0, arg1, arg2) } -// RecordPartnerTimeoutRequests mocks base method. +// RecordPartnerTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordPartnerTimeoutRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPartnerTimeoutRequests", arg0, arg1, arg2) } -// RecordPartnerTimeoutRequests indicates an expected call of RecordPartnerTimeoutRequests. +// RecordPartnerTimeoutRequests indicates an expected call of RecordPartnerTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordPartnerTimeoutRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPartnerTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPartnerTimeoutRequests), arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerReqStats mocks base method. +// RecordPlatformPublisherPartnerReqStats mocks base method func (m *MockMetricsEngine) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPlatformPublisherPartnerReqStats", arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerReqStats indicates an expected call of RecordPlatformPublisherPartnerReqStats. +// RecordPlatformPublisherPartnerReqStats indicates an expected call of RecordPlatformPublisherPartnerReqStats func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerReqStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerReqStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerReqStats), arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerResponseStats mocks base method. +// RecordPlatformPublisherPartnerResponseStats mocks base method func (m *MockMetricsEngine) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPlatformPublisherPartnerResponseStats", arg0, arg1, arg2) } -// RecordPlatformPublisherPartnerResponseStats indicates an expected call of RecordPlatformPublisherPartnerResponseStats. +// RecordPlatformPublisherPartnerResponseStats indicates an expected call of RecordPlatformPublisherPartnerResponseStats func (mr *MockMetricsEngineMockRecorder) RecordPlatformPublisherPartnerResponseStats(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPlatformPublisherPartnerResponseStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPlatformPublisherPartnerResponseStats), arg0, arg1, arg2) } -// RecordPreProcessingTimeStats mocks base method. +// RecordPreProcessingTimeStats mocks base method func (m *MockMetricsEngine) RecordPreProcessingTimeStats(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPreProcessingTimeStats", arg0, arg1) } -// RecordPreProcessingTimeStats indicates an expected call of RecordPreProcessingTimeStats. +// RecordPreProcessingTimeStats indicates an expected call of RecordPreProcessingTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPreProcessingTimeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPreProcessingTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPreProcessingTimeStats), arg0, arg1) } -// RecordPrebidTimeoutRequests mocks base method. +// RecordPrebidTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordPrebidTimeoutRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPrebidTimeoutRequests", arg0, arg1) } -// RecordPrebidTimeoutRequests indicates an expected call of RecordPrebidTimeoutRequests. +// RecordPrebidTimeoutRequests indicates an expected call of RecordPrebidTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordPrebidTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPrebidTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPrebidTimeoutRequests), arg0, arg1) } -// RecordPublisherInvalidProfileImpressions mocks base method. +// RecordPublisherInvalidProfileImpressions mocks base method func (m *MockMetricsEngine) RecordPublisherInvalidProfileImpressions(arg0, arg1 string, arg2 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherInvalidProfileImpressions", arg0, arg1, arg2) } -// RecordPublisherInvalidProfileImpressions indicates an expected call of RecordPublisherInvalidProfileImpressions. +// RecordPublisherInvalidProfileImpressions indicates an expected call of RecordPublisherInvalidProfileImpressions func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileImpressions(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileImpressions", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileImpressions), arg0, arg1, arg2) } -// RecordPublisherInvalidProfileRequests mocks base method. +// RecordPublisherInvalidProfileRequests mocks base method func (m *MockMetricsEngine) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherInvalidProfileRequests", arg0, arg1, arg2) } -// RecordPublisherInvalidProfileRequests indicates an expected call of RecordPublisherInvalidProfileRequests. +// RecordPublisherInvalidProfileRequests indicates an expected call of RecordPublisherInvalidProfileRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherInvalidProfileRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherInvalidProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherInvalidProfileRequests), arg0, arg1, arg2) } -// RecordPublisherPartnerNoCookieStats mocks base method. +// RecordPublisherPartnerNoCookieStats mocks base method func (m *MockMetricsEngine) RecordPublisherPartnerNoCookieStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherPartnerNoCookieStats", arg0, arg1) } -// RecordPublisherPartnerNoCookieStats indicates an expected call of RecordPublisherPartnerNoCookieStats. +// RecordPublisherPartnerNoCookieStats indicates an expected call of RecordPublisherPartnerNoCookieStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherPartnerNoCookieStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherPartnerNoCookieStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherPartnerNoCookieStats), arg0, arg1) } -// RecordPublisherProfileRequests mocks base method. +// RecordPublisherProfileRequests mocks base method func (m *MockMetricsEngine) RecordPublisherProfileRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherProfileRequests", arg0, arg1) } -// RecordPublisherProfileRequests indicates an expected call of RecordPublisherProfileRequests. +// RecordPublisherProfileRequests indicates an expected call of RecordPublisherProfileRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherProfileRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherProfileRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherProfileRequests), arg0, arg1) } -// RecordPublisherRequests mocks base method. +// RecordPublisherRequests mocks base method func (m *MockMetricsEngine) RecordPublisherRequests(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherRequests", arg0, arg1, arg2) } -// RecordPublisherRequests indicates an expected call of RecordPublisherRequests. +// RecordPublisherRequests indicates an expected call of RecordPublisherRequests func (mr *MockMetricsEngineMockRecorder) RecordPublisherRequests(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherRequests), arg0, arg1, arg2) } -// RecordPublisherResponseEncodingErrorStats mocks base method. +// RecordPublisherResponseEncodingErrorStats mocks base method func (m *MockMetricsEngine) RecordPublisherResponseEncodingErrorStats(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherResponseEncodingErrorStats", arg0) } -// RecordPublisherResponseEncodingErrorStats indicates an expected call of RecordPublisherResponseEncodingErrorStats. +// RecordPublisherResponseEncodingErrorStats indicates an expected call of RecordPublisherResponseEncodingErrorStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseEncodingErrorStats(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseEncodingErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseEncodingErrorStats), arg0) } -// RecordPublisherResponseTimeStats mocks base method. +// RecordPublisherResponseTimeStats mocks base method func (m *MockMetricsEngine) RecordPublisherResponseTimeStats(arg0 string, arg1 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherResponseTimeStats", arg0, arg1) } -// RecordPublisherResponseTimeStats indicates an expected call of RecordPublisherResponseTimeStats. +// RecordPublisherResponseTimeStats indicates an expected call of RecordPublisherResponseTimeStats func (mr *MockMetricsEngineMockRecorder) RecordPublisherResponseTimeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherResponseTimeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherResponseTimeStats), arg0, arg1) } -// RecordPublisherWrapperLoggerFailure mocks base method. +// RecordPublisherWrapperLoggerFailure mocks base method func (m *MockMetricsEngine) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordPublisherWrapperLoggerFailure", arg0, arg1, arg2) } -// RecordPublisherWrapperLoggerFailure indicates an expected call of RecordPublisherWrapperLoggerFailure. +// RecordPublisherWrapperLoggerFailure indicates an expected call of RecordPublisherWrapperLoggerFailure func (mr *MockMetricsEngineMockRecorder) RecordPublisherWrapperLoggerFailure(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordPublisherWrapperLoggerFailure", reflect.TypeOf((*MockMetricsEngine)(nil).RecordPublisherWrapperLoggerFailure), arg0, arg1, arg2) } -// RecordReqImpsWithContentCount mocks base method. +// RecordReqImpsWithContentCount mocks base method func (m *MockMetricsEngine) RecordReqImpsWithContentCount(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordReqImpsWithContentCount", arg0, arg1) } -// RecordReqImpsWithContentCount indicates an expected call of RecordReqImpsWithContentCount. +// RecordReqImpsWithContentCount indicates an expected call of RecordReqImpsWithContentCount func (mr *MockMetricsEngineMockRecorder) RecordReqImpsWithContentCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordReqImpsWithContentCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordReqImpsWithContentCount), arg0, arg1) } -// RecordRequest mocks base method. +// RecordRequest mocks base method func (m *MockMetricsEngine) RecordRequest(arg0 metrics.Labels) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequest", arg0) } -// RecordRequest indicates an expected call of RecordRequest. +// RecordRequest indicates an expected call of RecordRequest func (mr *MockMetricsEngineMockRecorder) RecordRequest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequest", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequest), arg0) } -// RecordRequestAdPodGeneratedImpressionsCount mocks base method. +// RecordRequestAdPodGeneratedImpressionsCount mocks base method func (m *MockMetricsEngine) RecordRequestAdPodGeneratedImpressionsCount(arg0 int, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequestAdPodGeneratedImpressionsCount", arg0, arg1) } -// RecordRequestAdPodGeneratedImpressionsCount indicates an expected call of RecordRequestAdPodGeneratedImpressionsCount. +// RecordRequestAdPodGeneratedImpressionsCount indicates an expected call of RecordRequestAdPodGeneratedImpressionsCount func (mr *MockMetricsEngineMockRecorder) RecordRequestAdPodGeneratedImpressionsCount(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestAdPodGeneratedImpressionsCount", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestAdPodGeneratedImpressionsCount), arg0, arg1) } -// RecordRequestTime mocks base method. +// RecordRequestTime mocks base method func (m *MockMetricsEngine) RecordRequestTime(arg0 string, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordRequestTime", arg0, arg1) } -// RecordRequestTime indicates an expected call of RecordRequestTime. +// RecordRequestTime indicates an expected call of RecordRequestTime func (mr *MockMetricsEngineMockRecorder) RecordRequestTime(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordRequestTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordRequestTime), arg0, arg1) } -// RecordSSTimeoutRequests mocks base method. +// RecordSSTimeoutRequests mocks base method func (m *MockMetricsEngine) RecordSSTimeoutRequests(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordSSTimeoutRequests", arg0, arg1) } -// RecordSSTimeoutRequests indicates an expected call of RecordSSTimeoutRequests. +// RecordSSTimeoutRequests indicates an expected call of RecordSSTimeoutRequests func (mr *MockMetricsEngineMockRecorder) RecordSSTimeoutRequests(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSSTimeoutRequests", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSSTimeoutRequests), arg0, arg1) @@ -659,25 +658,25 @@ func (mr *MockMetricsEngineMockRecorder) RecordSendLoggerDataTime(arg0 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordSendLoggerDataTime", reflect.TypeOf((*MockMetricsEngine)(nil).RecordSendLoggerDataTime), arg0) } -// RecordStatsKeyCTVPrebidFailedImpression mocks base method. +// RecordStatsKeyCTVPrebidFailedImpression mocks base method func (m *MockMetricsEngine) RecordStatsKeyCTVPrebidFailedImpression(arg0 int, arg1, arg2 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordStatsKeyCTVPrebidFailedImpression", arg0, arg1, arg2) } -// RecordStatsKeyCTVPrebidFailedImpression indicates an expected call of RecordStatsKeyCTVPrebidFailedImpression. +// RecordStatsKeyCTVPrebidFailedImpression indicates an expected call of RecordStatsKeyCTVPrebidFailedImpression func (mr *MockMetricsEngineMockRecorder) RecordStatsKeyCTVPrebidFailedImpression(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordStatsKeyCTVPrebidFailedImpression", reflect.TypeOf((*MockMetricsEngine)(nil).RecordStatsKeyCTVPrebidFailedImpression), arg0, arg1, arg2) } -// RecordUidsCookieNotPresentErrorStats mocks base method. +// RecordUidsCookieNotPresentErrorStats mocks base method func (m *MockMetricsEngine) RecordUidsCookieNotPresentErrorStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordUidsCookieNotPresentErrorStats", arg0, arg1) } -// RecordUidsCookieNotPresentErrorStats indicates an expected call of RecordUidsCookieNotPresentErrorStats. +// RecordUidsCookieNotPresentErrorStats indicates an expected call of RecordUidsCookieNotPresentErrorStats func (mr *MockMetricsEngineMockRecorder) RecordUidsCookieNotPresentErrorStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordUidsCookieNotPresentErrorStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordUidsCookieNotPresentErrorStats), arg0, arg1) @@ -737,31 +736,31 @@ func (m *MockMetricsEngine) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 st m.ctrl.Call(m, "RecordVideoImpDisabledViaConnTypeStats", arg0, arg1) } -// RecordVideoImpDisabledViaConnTypeStats indicates an expected call of RecordVideoImpDisabledViaConnTypeStats. +// RecordVideoImpDisabledViaConnTypeStats indicates an expected call of RecordVideoImpDisabledViaConnTypeStats func (mr *MockMetricsEngineMockRecorder) RecordVideoImpDisabledViaConnTypeStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoImpDisabledViaConnTypeStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoImpDisabledViaConnTypeStats), arg0, arg1) } -// RecordVideoInstlImpsStats mocks base method. +// RecordVideoInstlImpsStats mocks base method func (m *MockMetricsEngine) RecordVideoInstlImpsStats(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RecordVideoInstlImpsStats", arg0, arg1) } -// RecordVideoInstlImpsStats indicates an expected call of RecordVideoInstlImpsStats. +// RecordVideoInstlImpsStats indicates an expected call of RecordVideoInstlImpsStats func (mr *MockMetricsEngineMockRecorder) RecordVideoInstlImpsStats(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordVideoInstlImpsStats", reflect.TypeOf((*MockMetricsEngine)(nil).RecordVideoInstlImpsStats), arg0, arg1) } -// Shutdown mocks base method. +// Shutdown mocks base method func (m *MockMetricsEngine) Shutdown() { m.ctrl.T.Helper() m.ctrl.Call(m, "Shutdown") } -// Shutdown indicates an expected call of Shutdown. +// Shutdown indicates an expected call of Shutdown func (mr *MockMetricsEngineMockRecorder) Shutdown() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockMetricsEngine)(nil).Shutdown)) diff --git a/modules/pubmatic/openwrap/nonbids.go b/modules/pubmatic/openwrap/nonbids.go index b63a912f2f9..0c44e54a8fa 100644 --- a/modules/pubmatic/openwrap/nonbids.go +++ b/modules/pubmatic/openwrap/nonbids.go @@ -1,6 +1,7 @@ package openwrap import ( + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" @@ -8,27 +9,27 @@ import ( // prepareSeatNonBids forms the rctx.SeatNonBids map from rctx values // currently, this function prepares and returns nonbids for partner-throttle and slot-not-mapped errors -func prepareSeatNonBids(rctx models.RequestCtx) map[string][]openrtb_ext.NonBid { +// prepareSeatNonBids forms the rctx.SeatNonBids map from rctx values +// currently, this function prepares and returns nonbids for partner-throttle and slot-not-mapped errors +func prepareSeatNonBids(rctx models.RequestCtx) openrtb_ext.NonBidCollection { - seatNonBids := make(map[string][]openrtb_ext.NonBid, 0) + var seatNonBid openrtb_ext.NonBidCollection for impID, impCtx := range rctx.ImpBidCtx { // seat-non-bid for partner-throttled error for bidder := range rctx.AdapterThrottleMap { - seatNonBids[bidder] = append(seatNonBids[bidder], openrtb_ext.NonBid{ - ImpId: impID, - StatusCode: int(nbr.RequestBlockedPartnerThrottle), - }) + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{Bid: &openrtb2.Bid{ImpID: impID}, NonBidReason: int(nbr.RequestBlockedPartnerThrottle)}) + seatNonBid.AddBid(nonBid, bidder) + } + // seat-non-bid for slot-not-mapped error // Note : Throttled partner will not be a part of impCtx.NonMapped for bidder := range impCtx.NonMapped { - seatNonBids[bidder] = append(seatNonBids[bidder], openrtb_ext.NonBid{ - ImpId: impID, - StatusCode: int(nbr.RequestBlockedSlotNotMapped), - }) + nonBid := openrtb_ext.NewNonBid(openrtb_ext.NonBidParams{Bid: &openrtb2.Bid{ImpID: impID}, NonBidReason: int(nbr.RequestBlockedSlotNotMapped)}) + seatNonBid.AddBid(nonBid, bidder) } } - return seatNonBids + return seatNonBid } // addSeatNonBidsInResponseExt adds the rctx.SeatNonBids in the response-ext diff --git a/modules/pubmatic/openwrap/nonbids_test.go b/modules/pubmatic/openwrap/nonbids_test.go index ac7573a0ef9..b0c7217244d 100644 --- a/modules/pubmatic/openwrap/nonbids_test.go +++ b/modules/pubmatic/openwrap/nonbids_test.go @@ -3,6 +3,7 @@ package openwrap import ( "testing" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" "github.com/prebid/prebid-server/v2/openrtb_ext" @@ -17,7 +18,7 @@ func TestPrepareSeatNonBids(t *testing.T) { tests := []struct { name string args args - seatNonBids map[string][]openrtb_ext.NonBid + seatNonBids openrtb_ext.NonBidCollection }{ { name: "empty_impbidctx", @@ -26,7 +27,7 @@ func TestPrepareSeatNonBids(t *testing.T) { SeatNonBids: make(map[string][]openrtb_ext.NonBid), }, }, - seatNonBids: make(map[string][]openrtb_ext.NonBid), + seatNonBids: openrtb_ext.NonBidCollection{}, }, { name: "empty_seatnonbids", @@ -40,7 +41,7 @@ func TestPrepareSeatNonBids(t *testing.T) { SeatNonBids: make(map[string][]openrtb_ext.NonBid), }, }, - seatNonBids: make(map[string][]openrtb_ext.NonBid), + seatNonBids: openrtb_ext.NonBidCollection{}, }, { name: "partner_throttled_nonbids", @@ -57,14 +58,7 @@ func TestPrepareSeatNonBids(t *testing.T) { SeatNonBids: map[string][]openrtb_ext.NonBid{}, }, }, - seatNonBids: map[string][]openrtb_ext.NonBid{ - "pubmatic": { - openrtb_ext.NonBid{ - ImpId: "imp1", - StatusCode: int(nbr.RequestBlockedPartnerThrottle), - }, - }, - }, + seatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{"pubmatic": {{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: int(nbr.RequestBlockedPartnerThrottle)}}}), }, { name: "slot_not_mapped_nonbids", @@ -88,20 +82,22 @@ func TestPrepareSeatNonBids(t *testing.T) { }, }, }, - seatNonBids: map[string][]openrtb_ext.NonBid{ + seatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ "pubmatic": { { - ImpId: "imp1", - StatusCode: int(nbr.RequestBlockedSlotNotMapped), + Bid: &openrtb2.Bid{ + ImpID: "imp1", + }, + NonBidReason: int(nbr.RequestBlockedSlotNotMapped), }, }, "appnexus": { { - ImpId: "imp1", - StatusCode: int(nbr.RequestBlockedSlotNotMapped), + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(nbr.RequestBlockedSlotNotMapped), }, }, - }, + }), }, { name: "slot_not_mapped_plus_partner_throttled_nonbids", @@ -113,42 +109,33 @@ func TestPrepareSeatNonBids(t *testing.T) { "pubmatic": {}, }, }, - "imp2": {}, }, AdapterThrottleMap: map[string]struct{}{ "appnexus": {}, }, }, }, - seatNonBids: map[string][]openrtb_ext.NonBid{ + seatNonBids: getNonBids(map[string][]openrtb_ext.NonBidParams{ "pubmatic": { { - ImpId: "imp1", - StatusCode: int(nbr.RequestBlockedSlotNotMapped), + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(nbr.RequestBlockedSlotNotMapped), }, }, "appnexus": { { - ImpId: "imp2", - StatusCode: int(nbr.RequestBlockedPartnerThrottle), - }, - { - ImpId: "imp1", - StatusCode: int(nbr.RequestBlockedPartnerThrottle), + Bid: &openrtb2.Bid{ImpID: "imp1"}, + NonBidReason: int(nbr.RequestBlockedPartnerThrottle), }, }, - }, + }), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - seatNonBids := prepareSeatNonBids(tt.args.rctx) - assert.Equal(t, len(seatNonBids), len(tt.seatNonBids)) - for k, v := range seatNonBids { - // ignore order of elements in slice while comparing - assert.ElementsMatch(t, v, tt.seatNonBids[k], tt.name) - } + got := prepareSeatNonBids(tt.args.rctx) + assert.Equal(t, tt.seatNonBids, got, "mismatched seatnonbids") }) } } @@ -826,3 +813,14 @@ func TestAddLostToDealBidNonBRCode(t *testing.T) { }) } } + +func getNonBids(bidParamsMap map[string][]openrtb_ext.NonBidParams) openrtb_ext.NonBidCollection { + nonBids := openrtb_ext.NonBidCollection{} + for bidder, bidParams := range bidParamsMap { + for _, bidParam := range bidParams { + nonBid := openrtb_ext.NewNonBid(bidParam) + nonBids.AddBid(nonBid, bidder) + } + } + return nonBids +} diff --git a/modules/pubmatic/openwrap/util.go b/modules/pubmatic/openwrap/util.go index a4fd1206090..4e597e4b8f2 100644 --- a/modules/pubmatic/openwrap/util.go +++ b/modules/pubmatic/openwrap/util.go @@ -277,7 +277,6 @@ func getPubmaticErrorCode(standardNBR openrtb3.NoBidReason) int { case nbr.InternalError: return 17 // ErrInvalidImpression - } return -1 diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 7b6eacf2830..f3700799ea6 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -112,10 +112,10 @@ const ( UserSyncPixel UserSyncType = "pixel" ) -// NonBidObject is subset of Bid object with exact json signature +// ExtNonBidPrebidBid is subset of Bid object with exact json signature +// defined at https://github.com/prebid/openrtb/blob/v19.0.0/openrtb2/bid.go // It also contains the custom fields -type NonBidObject struct { - // SubSet +type ExtNonBidPrebidBid struct { Price float64 `json:"price,omitempty"` ADomain []string `json:"adomain,omitempty"` CatTax adcom1.CategoryTaxonomy `json:"cattax,omitempty"` @@ -131,7 +131,7 @@ type NonBidObject struct { OriginalBidCur string `json:"origbidcur,omitempty"` //OW specific fields - ID string `json:"id"` + ID string `json:"id,omitempty"` // need to check DealPriority int `json:"dealpriority,omitempty"` DealTierSatisfied bool `json:"dealtiersatisfied,omitempty"` Meta *ExtBidPrebidMeta `json:"meta,omitempty"` @@ -143,22 +143,21 @@ type NonBidObject struct { OriginalBidCPMUSD float64 `json:"origbidcpmusd,omitempty"` } -// ExtResponseNonBidPrebid represents bidresponse.ext.prebid.seatnonbid[].nonbid[].ext -type ExtResponseNonBidPrebid struct { - Bid NonBidObject `json:"bid"` +// ExtNonBidPrebid represents bidresponse.ext.prebid.seatnonbid[].nonbid[].ext +type ExtNonBidPrebid struct { + Bid ExtNonBidPrebidBid `json:"bid"` } -type NonBidExt struct { - Prebid ExtResponseNonBidPrebid `json:"prebid"` - IsAdPod *bool `json:"-"` // OW specific Flag to determine if it is Ad-Pod specific nonbid - +type ExtNonBid struct { + Prebid ExtNonBidPrebid `json:"prebid"` + IsAdPod *bool `json:"-"` // OW specific Flag to determine if it is Ad-Pod specific nonbid } // NonBid represnts the Non Bid Reason (statusCode) for given impression ID type NonBid struct { ImpId string `json:"impid"` StatusCode int `json:"statuscode"` - Ext NonBidExt `json:"ext"` + Ext ExtNonBid `json:"ext"` } // SeatNonBid is collection of NonBid objects with seat information diff --git a/openrtb_ext/seat_non_bids.go b/openrtb_ext/seat_non_bids.go new file mode 100644 index 00000000000..ff00e5a0992 --- /dev/null +++ b/openrtb_ext/seat_non_bids.go @@ -0,0 +1,112 @@ +package openrtb_ext + +import "github.com/prebid/openrtb/v20/openrtb2" + +// NonBidCollection contains the map of seat with list of nonBids +type NonBidCollection struct { + seatNonBidsMap map[string][]NonBid +} + +// NonBidParams contains the fields that are required to form the nonBid object +type NonBidParams struct { + Bid *openrtb2.Bid + NonBidReason int + OriginalBidCPM float64 + OriginalBidCur string + DealPriority int + DealTierSatisfied bool + GeneratedBidID string + TargetBidderCode string + OriginalBidCPMUSD float64 + BidMeta *ExtBidPrebidMeta + BidType BidType + BidTargets map[string]string + BidVideo *ExtBidPrebidVideo + BidEvents *ExtBidPrebidEvents + BidFloors *ExtBidPrebidFloors +} + +// NewNonBid creates the NonBid object from NonBidParams and return it +func NewNonBid(bidParams NonBidParams) NonBid { + if bidParams.Bid == nil { + bidParams.Bid = &openrtb2.Bid{} + } + return NonBid{ + ImpId: bidParams.Bid.ImpID, + StatusCode: bidParams.NonBidReason, + Ext: ExtNonBid{ + Prebid: ExtNonBidPrebid{Bid: ExtNonBidPrebidBid{ + Price: bidParams.Bid.Price, + ADomain: bidParams.Bid.ADomain, + CatTax: bidParams.Bid.CatTax, + Cat: bidParams.Bid.Cat, + DealID: bidParams.Bid.DealID, + W: bidParams.Bid.W, + H: bidParams.Bid.H, + Dur: bidParams.Bid.Dur, + MType: bidParams.Bid.MType, + OriginalBidCPM: bidParams.OriginalBidCPM, + OriginalBidCur: bidParams.OriginalBidCur, + + //OW specific + ID: bidParams.Bid.ID, + DealPriority: bidParams.DealPriority, + DealTierSatisfied: bidParams.DealTierSatisfied, + Meta: bidParams.BidMeta, + Targeting: bidParams.BidTargets, + Type: bidParams.BidType, + Video: bidParams.BidVideo, + BidId: bidParams.GeneratedBidID, + Floors: bidParams.BidFloors, + OriginalBidCPMUSD: bidParams.OriginalBidCPMUSD, + }}, + }, + } +} + +// AddBid adds the nonBid into the map against the respective seat. +// Note: This function is not a thread safe. +func (snb *NonBidCollection) AddBid(nonBid NonBid, seat string) { + if snb.seatNonBidsMap == nil { + snb.seatNonBidsMap = make(map[string][]NonBid) + } + snb.seatNonBidsMap[seat] = append(snb.seatNonBidsMap[seat], nonBid) +} + +// Append functions appends the NonBids from the input instance into the current instance's seatNonBidsMap, creating the map if needed. +// Note: This function is not a thread safe. +func (snb *NonBidCollection) Append(nonbid NonBidCollection) { + if snb == nil || len(nonbid.seatNonBidsMap) == 0 { + return + } + if snb.seatNonBidsMap == nil { + snb.seatNonBidsMap = make(map[string][]NonBid, len(nonbid.seatNonBidsMap)) + } + for seat, nonBids := range nonbid.seatNonBidsMap { + snb.seatNonBidsMap[seat] = append(snb.seatNonBidsMap[seat], nonBids...) + } +} + +// Get function converts the internal seatNonBidsMap to standard openrtb seatNonBid structure and returns it +func (snb *NonBidCollection) Get() []SeatNonBid { + if snb == nil { + return nil + } + + // seatNonBid := make([]SeatNonBid, len(snb.seatNonBidsMap)) + var seatNonBid []SeatNonBid + for seat, nonBids := range snb.seatNonBidsMap { + seatNonBid = append(seatNonBid, SeatNonBid{ + Seat: seat, + NonBid: nonBids, + }) + } + return seatNonBid +} + +func (snb *NonBidCollection) GetSeatNonBidMap() map[string][]NonBid { + if snb == nil { + return nil + } + return snb.seatNonBidsMap +} diff --git a/openrtb_ext/seat_non_bids_test.go b/openrtb_ext/seat_non_bids_test.go new file mode 100644 index 00000000000..b7eeed56c2f --- /dev/null +++ b/openrtb_ext/seat_non_bids_test.go @@ -0,0 +1,191 @@ +package openrtb_ext + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestNewNonBid(t *testing.T) { + tests := []struct { + name string + bidParams NonBidParams + expectedNonBid NonBid + }{ + { + name: "nil-bid-present-in-bidparams", + bidParams: NonBidParams{Bid: nil}, + expectedNonBid: NonBid{}, + }, + { + name: "non-nil-bid-present-in-bidparams", + bidParams: NonBidParams{Bid: &openrtb2.Bid{ImpID: "imp1"}, NonBidReason: 100}, + expectedNonBid: NonBid{ImpId: "imp1", StatusCode: 100}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nonBid := NewNonBid(tt.bidParams) + assert.Equal(t, tt.expectedNonBid, nonBid, "found incorrect nonBid") + }) + } +} + +func TestSeatNonBidsAdd(t *testing.T) { + type fields struct { + seatNonBidsMap map[string][]NonBid + } + type args struct { + nonbid NonBid + seat string + } + tests := []struct { + name string + fields fields + args args + want map[string][]NonBid + }{ + { + name: "nil-seatNonBidsMap", + fields: fields{seatNonBidsMap: nil}, + args: args{ + nonbid: NonBid{}, + seat: "bidder1", + }, + want: sampleSeatNonBidMap("bidder1", 1), + }, + { + name: "non-nil-seatNonBidsMap", + fields: fields{seatNonBidsMap: nil}, + args: args{ + + nonbid: NonBid{}, + seat: "bidder1", + }, + want: sampleSeatNonBidMap("bidder1", 1), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + snb := &NonBidCollection{ + seatNonBidsMap: tt.fields.seatNonBidsMap, + } + snb.AddBid(tt.args.nonbid, tt.args.seat) + assert.Equalf(t, tt.want, snb.seatNonBidsMap, "found incorrect seatNonBidsMap") + }) + } +} + +func TestSeatNonBidsGet(t *testing.T) { + type fields struct { + snb *NonBidCollection + } + tests := []struct { + name string + fields fields + want []SeatNonBid + }{ + { + name: "get-seat-nonbids", + fields: fields{&NonBidCollection{sampleSeatNonBidMap("bidder1", 2)}}, + want: sampleSeatBids("bidder1", 2), + }, + { + name: "nil-seat-nonbids", + fields: fields{nil}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.snb.Get(); !assert.Equal(t, tt.want, got) { + t.Errorf("seatNonBids.get() = %v, want %v", got, tt.want) + } + }) + } +} + +var sampleSeatNonBidMap = func(seat string, nonBidCount int) map[string][]NonBid { + nonBids := make([]NonBid, 0) + for i := 0; i < nonBidCount; i++ { + nonBids = append(nonBids, NonBid{ + Ext: ExtNonBid{Prebid: ExtNonBidPrebid{Bid: ExtNonBidPrebidBid{}}}, + }) + } + return map[string][]NonBid{ + seat: nonBids, + } +} + +var sampleSeatBids = func(seat string, nonBidCount int) []SeatNonBid { + seatNonBids := make([]SeatNonBid, 0) + seatNonBid := SeatNonBid{ + Seat: seat, + NonBid: make([]NonBid, 0), + } + for i := 0; i < nonBidCount; i++ { + seatNonBid.NonBid = append(seatNonBid.NonBid, NonBid{ + Ext: ExtNonBid{Prebid: ExtNonBidPrebid{Bid: ExtNonBidPrebidBid{}}}, + }) + } + seatNonBids = append(seatNonBids, seatNonBid) + return seatNonBids +} + +func TestSeatNonBidsMerge(t *testing.T) { + type target struct { + snb *NonBidCollection + } + tests := []struct { + name string + fields target + input NonBidCollection + want *NonBidCollection + }{ + { + name: "target-NonBidCollection-is-nil", + fields: target{nil}, + want: nil, + }, + { + name: "input-NonBidCollection-contains-nil-map", + fields: target{&NonBidCollection{}}, + input: NonBidCollection{seatNonBidsMap: nil}, + want: &NonBidCollection{}, + }, + { + name: "input-NonBidCollection-contains-empty-nonBids", + fields: target{&NonBidCollection{}}, + input: NonBidCollection{seatNonBidsMap: make(map[string][]NonBid)}, + want: &NonBidCollection{}, + }, + { + name: "append-nonbids-in-empty-target-NonBidCollection", + fields: target{&NonBidCollection{}}, + input: NonBidCollection{ + seatNonBidsMap: sampleSeatNonBidMap("pubmatic", 1), + }, + want: &NonBidCollection{ + seatNonBidsMap: sampleSeatNonBidMap("pubmatic", 1), + }, + }, + { + name: "merge-multiple-nonbids-in-non-empty-target-NonBidCollection", + fields: target{&NonBidCollection{ + seatNonBidsMap: sampleSeatNonBidMap("pubmatic", 1), + }}, + input: NonBidCollection{ + seatNonBidsMap: sampleSeatNonBidMap("pubmatic", 1), + }, + want: &NonBidCollection{ + seatNonBidsMap: sampleSeatNonBidMap("pubmatic", 2), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fields.snb.Append(tt.input) + assert.Equal(t, tt.want, tt.fields.snb, "incorrect NonBidCollection generated by Append") + }) + } +} diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 2f4c09fb7be..b6c8315e3d0 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -1,6 +1,6 @@ #!/bin/bash # Generate test coverage statistics for Go packages. -# +# # Works around the fact that `go test -coverprofile` currently does not work # with multiple packages, see https://code.google.com/p/go/issues/detail?id=6909 # @@ -91,4 +91,4 @@ case "$1" in show_cover_report html ;; *) echo >&2 "error: invalid option: $1"; exit 1 ;; -esac +esac \ No newline at end of file diff --git a/validate.sh b/validate.sh index ca4fbc1dfd7..1c44a26aaa4 100755 --- a/validate.sh +++ b/validate.sh @@ -19,11 +19,12 @@ done ./scripts/format.sh -f $AUTOFMT + # Run the actual tests. Make sure there's enough coverage too, if the flags call for it. if $COVERAGE; then ./scripts/check_coverage.sh else - go test -timeout 120s $(go list ./... | grep -v /vendor/) + go test -timeout 120s $(go list ./... | grep -v /vendor/) fi # Then run the race condition tests. These only run on tests named TestRace.* for two reasons.