From a2fe01b2409b434f45cd4495f5d0269eb021b782 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Mon, 15 Mar 2021 03:20:07 -0700 Subject: [PATCH] Debug warnings --- endpoints/openrtb2/amp_auction.go | 20 +-- endpoints/openrtb2/amp_auction_test.go | 4 +- endpoints/openrtb2/auction.go | 8 +- endpoints/openrtb2/auction_test.go | 52 ++++++- .../supplementary/us-privacy-invalid.json | 52 +++++++ errortypes/code.go | 2 + errortypes/errortypes.go | 22 +-- exchange/bidder.go | 14 +- exchange/bidder_test.go | 7 +- exchange/exchange.go | 69 ++++++++-- exchange/exchange_test.go | 130 ++++++++++++------ exchange/utils_test.go | 5 +- openrtb_ext/response.go | 7 +- privacy/ccpa/parsedpolicy.go | 5 +- 14 files changed, 301 insertions(+), 96 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 8cc8e9bd454..1f3d622b80d 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -35,10 +35,10 @@ import ( const defaultAmpRequestTimeoutMillis = 900 type AmpResponse struct { - Targeting map[string]string `json:"targeting"` - Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` - Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"errors,omitempty"` - Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"warnings,omitempty"` + Targeting map[string]string `json:"targeting"` + Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` + Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"errors,omitempty"` + Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"warnings,omitempty"` } // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing @@ -239,9 +239,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } - warnings := make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError) + warnings := extResponse.Warnings + if warnings == nil { + warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage) + } for _, v := range errortypes.WarningOnly(errL) { - bidderErr := openrtb_ext.ExtBidderError{ + bidderErr := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(v), Message: v.Error(), } @@ -528,8 +531,9 @@ func readPolicy(consent string) (privacy.PolicyWriter, error) { return ccpa.ConsentWriter{consent}, nil } - return privacy.NilPolicyWriter{}, &errortypes.InvalidPrivacyConsent{ - Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + return privacy.NilPolicyWriter{}, &errortypes.Warning{ + Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, } } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 08461d40da3..3c70f4b5ea6 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -478,7 +478,7 @@ func TestInvalidConsent(t *testing.T) { } // Assert Result - expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ + expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderReservedGeneral: { { Code: 10001, @@ -929,7 +929,7 @@ type mockAmpExchange struct { lastRequest *openrtb.BidRequest } -var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ +var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderName("openx"): { { Code: 1, diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 04e0c2cff95..3a4c0f5eaff 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -138,6 +138,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + warnings := errortypes.WarningOnly(errL) ctx := context.Background() @@ -178,6 +179,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http RequestType: labels.RType, StartTime: start, LegacyLabels: labels, + Warnings: warnings, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) @@ -360,8 +362,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { - if _, invalidConsent := err.(*errortypes.InvalidPrivacyConsent); invalidConsent { - errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) + if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) consentWriter := ccpa.ConsentWriter{Consent: ""} if err := consentWriter.Write(req); err != nil { return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 4411b4e3535..2e990bc4e4b 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1504,7 +1504,9 @@ func TestCCPAInvalid(t *testing.T) { errL := deps.validateRequest(&req) - expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"} + expectedWarning := errortypes.Warning{ + Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode} assert.ElementsMatch(t, errL, []error{&expectedWarning}) assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") @@ -2075,6 +2077,54 @@ func TestValidateBidders(t *testing.T) { } } +func TestAuctionWarnings(t *testing.T) { + reqBody := validRequest(t, "us-privacy-invalid.json") + deps := &endpointDeps{ + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + if recorder.Code != http.StatusOK { + t.Errorf("Endpoint should return a 200") + } + warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings + assert.Len(t, warnings, 1, "One warning should be returned from exchange") + + actualWarning := warnings[0].(*errortypes.Warning) + expectedMessage := "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)" + assert.Equal(t, expectedMessage, actualWarning.Message, "Warning message is incorrect") + + assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") +} + +// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings. +type warningsCheckExchange struct { + auctionRequest exchange.AuctionRequest +} + +func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { + e.auctionRequest = r + return nil, nil +} + // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { gotRequest *openrtb.BidRequest diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json new file mode 100644 index 00000000000..2ccdfb7ccdc --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json @@ -0,0 +1,52 @@ +{ + "description": "Well formed amp request with invalid CCPA consent value", + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "source": { + "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" + }, + "tmax": 1000, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "regs": { + "ext": { + "us_privacy": "{invalid}" + } + }, + "user": { + "ext": {} + } + }, + "expectedBidResponse": { + "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "bidid":"test bid id", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/errortypes/code.go b/errortypes/code.go index 80a5eb45542..2749b978006 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -17,6 +17,8 @@ const ( const ( UnknownWarningCode = 10999 InvalidPrivacyConsentWarningCode = iota + 10000 + AccountLevelDebugDisabledWarningCode + BidderLevelDebugDisabledWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index c953f9b7e08..1fed2d7da6e 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -167,7 +167,8 @@ func (err *BidderTemporarilyDisabled) Severity() Severity { // Warning is a generic non-fatal error. type Warning struct { - Message string + Message string + WarningCode int } func (err *Warning) Error() string { @@ -175,26 +176,9 @@ func (err *Warning) Error() string { } func (err *Warning) Code() int { - return UnknownWarningCode + return err.WarningCode } func (err *Warning) Severity() Severity { return SeverityWarning } - -// InvalidPrivacyConsent is a warning for when the privacy consent string is invalid and is ignored. -type InvalidPrivacyConsent struct { - Message string -} - -func (err *InvalidPrivacyConsent) Error() string { - return err.Message -} - -func (err *InvalidPrivacyConsent) Code() int { - return InvalidPrivacyConsentWarningCode -} - -func (err *InvalidPrivacyConsent) Severity() Severity { - return SeverityWarning -} diff --git a/exchange/bidder.go b/exchange/bidder.go index f916e205ace..76cdfd89f27 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -168,9 +168,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if accountDebugAllowed && bidder.config.DebugInfo.Allow { - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) + } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b829ddd787c..10fb94afa1d 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -20,6 +20,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -109,9 +110,13 @@ func TestSingleBidder(t *testing.T) { } // Make sure the returned values are what we expect - if len(errs) != 0 { + if len(errortypes.FatalOnly(errs)) != 0 { t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) } + + if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 { + t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs)) + } if len(seatBid.bids) != len(mockBidderResponse.Bids) { t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) } diff --git a/exchange/exchange.go b/exchange/exchange.go index b4fa3a614f6..edad2afaaf4 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -66,7 +66,8 @@ type exchange struct { // Container to pass out response ext data from the GetAllBids goroutines back into the main thread type seatResponseExtra struct { ResponseTimeMillis int - Errors []openrtb_ext.ExtBidderError + Errors []openrtb_ext.ExtBidderMessage + Warnings []openrtb_ext.ExtBidderMessage // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. HttpCalls []*openrtb_ext.ExtHttpCall @@ -106,6 +107,7 @@ type AuctionRequest struct { UserSyncs IdFetcher RequestType metrics.RequestType StartTime time.Time + Warnings []error // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -138,9 +140,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * debugLog = &DebugLog{Enabled: false} } - debugInfo := getDebugInfo(r.BidRequest, requestExt) + requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo = debugInfo && r.Account.DebugAllow + debugInfo := requestDebugInfo && r.Account.DebugAllow debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow if debugInfo { @@ -236,6 +238,27 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + generalWarningKey := openrtb_ext.BidderName(openrtb_ext.BidderReservedGeneral) + + if !r.Account.DebugAllow && requestDebugInfo { + accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ + Code: errortypes.AccountLevelDebugDisabledWarningCode, + Message: "debug turned off for account", + } + bidResponseExt.Warnings[generalWarningKey] = append(bidResponseExt.Warnings[generalWarningKey], accountDebugDisabledWarning) + } + + if len(r.Warnings) != 0 { + for _, warning := range r.Warnings { + typedWarning := warning.(*errortypes.Warning) + generalWarning := openrtb_ext.ExtBidderMessage{ + Code: typedWarning.WarningCode, + Message: typedWarning.Message, + } + bidResponseExt.Warnings[generalWarningKey] = append(bidResponseExt.Warnings[generalWarningKey], generalWarning) + } + } + // Build the response return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } @@ -397,11 +420,11 @@ func (e *exchange) getAllBids( // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start)) - serr := errsToBidderErrors(err) bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids) bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err) // Append any bid validation errors to the error list - ae.Errors = serr + ae.Errors = errsToBidderErrors(err) + ae.Warnings = errsToBidderWarnings(err) brw.adapterExtra = ae if bids != nil { for _, bid := range bids.bids { @@ -494,13 +517,29 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { return ret } -func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { - serr := make([]openrtb_ext.ExtBidderError, len(errs)) - for i := 0; i < len(errs); i++ { - serr[i].Code = errortypes.ReadCode(errs[i]) - serr[i].Message = errs[i].Error() +func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderMessage { + sErr := make([]openrtb_ext.ExtBidderMessage, 0) + for _, err := range errortypes.FatalOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(err), + Message: err.Error(), + } + sErr = append(sErr, newErr) } - return serr + + return sErr +} + +func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { + sWarn := make([]openrtb_ext.ExtBidderMessage, 0) + for _, warn := range errortypes.WarningOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(warn), + Message: warn.Error(), + } + sWarn = append(sWarn, newErr) + } + return sWarn } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester @@ -749,7 +788,8 @@ func getPrimaryAdServer(adServerId int) (string, error) { func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse { req := r.BidRequest bidResponseExt := &openrtb_ext.ExtBidResponse{ - Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)), + Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), ResponseTimeMillis: make(map[openrtb_ext.BidderName]int, len(adapterBids)), RequestTimeoutMillis: req.TMax, } @@ -771,6 +811,9 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb if debugInfo && len(responseExtra.HttpCalls) > 0 { bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } + if len(responseExtra.Warnings) > 0 { + bidResponseExt.Warnings[bidderName] = responseExtra.Warnings + } // Only make an entry for bidder errors if the bidder reported any. if len(responseExtra.Errors) > 0 { bidResponseExt.Errors[bidderName] = responseExtra.Errors @@ -800,7 +843,7 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B ext, err := json.Marshal(sbExt) if err != nil { - extError := openrtb_ext.ExtBidderError{ + extError := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(err), Message: fmt.Sprintf("Error writing SeatBid.Ext: %s", err.Error()), } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 7507ecef89d..a2e2ea007ef 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/errortypes" "io/ioutil" "net/http" "net/http/httptest" @@ -141,7 +142,7 @@ func TestCharacterEscape(t *testing.T) { adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1) adapterExtra["appnexus"] = &seatResponseExtra{ ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, + Errors: []openrtb_ext.ExtBidderMessage{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, } var errList []error @@ -180,59 +181,68 @@ func TestDebugBehaviour(t *testing.T) { } type aTest struct { - desc string - in inTest - out outTest - debugData debugData + desc string + in inTest + out outTest + debugData debugData + generalWarnings bool } testCases := []aTest{ { - desc: "test flag equals zero, ext debug flag false, no debug info expected", - in: inTest{test: 0, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag false, no debug info expected", + in: inTest{test: 0, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generalWarnings: false, }, { - desc: "test flag equals zero, ext debug flag true, debug info expected", - in: inTest{test: 0, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag true, debug info expected", + in: inTest{test: 0, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generalWarnings: false, }, { - desc: "test flag equals 1, ext debug flag false, debug info expected", - in: inTest{test: 1, debug: false}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag false, debug info expected", + in: inTest{test: 1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generalWarnings: false, }, { - desc: "test flag equals 1, ext debug flag true, debug info expected", - in: inTest{test: 1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag true, debug info expected", + in: inTest{test: 1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generalWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", - in: inTest{test: 2, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", + in: inTest{test: 2, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generalWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generalWarnings: true, }, { - desc: "test account level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + desc: "test account level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, false}, + generalWarnings: true, }, { - desc: "test bidder level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, + desc: "test bidder level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{false, true}, + generalWarnings: true, }, } @@ -243,9 +253,9 @@ func TestDebugBehaviour(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") - if error != nil { - t.Errorf("Failed to create a category Fetcher: %v", error) + categoriesFetcher, err := newCategoryFetcher("./test/category-mapping") + if err != nil { + t.Errorf("Failed to create a category Fetcher: %v", err) } bidRequest := &openrtb.BidRequest{ @@ -305,6 +315,13 @@ func TestDebugBehaviour(t *testing.T) { UserSyncs: &emptyUsersync{}, StartTime: time.Now(), } + if test.generalWarnings { + var errL []error + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent is invalid and will be ignored."), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) + auctionRequest.Warnings = errL + } // Run test outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) @@ -339,6 +356,35 @@ func TestDebugBehaviour(t *testing.T) { } else { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } + + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") + } + + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if test.generalWarnings { + assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") + } else { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + } + assert.NotNil(t, actualExt.Warnings["appnexus"], "bidder warning should be present") + assert.Equal(t, "debug turned off for bidder", actualExt.Warnings["appnexus"][0].Message, "account debug disabled message should be present") + } + + if test.generalWarnings { + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + CCPAWarningPresent := false + for _, warn := range actualExt.Warnings["general"] { + if warn.Code == errortypes.InvalidPrivacyConsentWarningCode { + CCPAWarningPresent = true + break + } + } + assert.True(t, CCPAWarningPresent, "CCPA Warning should be present") + } + } } @@ -393,7 +439,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher - debugLog := DebugLog{} + debugLog := DebugLog{Enabled: true} for _, testCase := range testCases { bidRequest := &openrtb.BidRequest{ @@ -762,7 +808,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ bidderName: { ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{ + Errors: []openrtb_ext.ExtBidderMessage{ { Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"", diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 0407b3c5e0e..415f7ab48c5 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -273,7 +273,10 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { description: "Invalid Consent", reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), - expectError: &errortypes.InvalidPrivacyConsent{"request.regs.ext.us_privacy must contain 4 characters"}, + expectError: &errortypes.Warning{ + Message: "request.regs.ext.us_privacy must contain 4 characters", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, { description: "Invalid No Sale Bidders", diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 02370d19376..d517704f44d 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -8,7 +8,8 @@ import ( type ExtBidResponse struct { Debug *ExtResponseDebug `json:"debug,omitempty"` // Errors defines the contract for bidresponse.ext.errors - Errors map[BidderName][]ExtBidderError `json:"errors,omitempty"` + Errors map[BidderName][]ExtBidderMessage `json:"errors,omitempty"` + Warnings map[BidderName][]ExtBidderMessage `json:"warnings,omitempty"` // ResponseTimeMillis defines the contract for bidresponse.ext.responsetimemillis ResponseTimeMillis map[BidderName]int `json:"responsetimemillis,omitempty"` // RequestTimeoutMillis returns the timeout used in the auction. @@ -47,8 +48,8 @@ type ExtUserSync struct { Type UserSyncType `json:"type"` } -// ExtBidderError defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. -type ExtBidderError struct { +// ExtBidderMessage defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. +type ExtBidderMessage struct { Code int `json:"code"` Message string `json:"message"` } diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 3c934e67822..7b9c2d1fa7c 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -43,7 +43,10 @@ func (p Policy) Parse(validBidders map[string]struct{}) (ParsedPolicy, error) { consentOptOut, err := parseConsent(p.Consent) if err != nil { msg := fmt.Sprintf("request.regs.ext.us_privacy %s", err.Error()) - return ParsedPolicy{}, &errortypes.InvalidPrivacyConsent{Message: msg} + return ParsedPolicy{}, &errortypes.Warning{ + Message: msg, + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + } } noSaleForAllBidders, noSaleSpecificBidders, err := parseNoSaleBidders(p.NoSaleBidders, validBidders)